Mua (Mua v0.2.5)

View Source

Minimal SMTP client.

Summary

Functions

Sends AUTH extension command and authenticates the sender.

Closes connection to the SMTP server.

Connects to an SMTP server and receives its banner.

Sends DATA command that specifies the beginning of the mail, and then sends the message.

Utility function to send a message to a list of recipients on a host.

Sends EHLO command which provides the identification of the sender i.e. the host name, and receives the list of extensions the server supports.

Sends HELO command which provides the identification of the sender i.e. the host name.

Sends MAIL FROM command that specifies the originator of the mail.

Utility function to lookup MX servers for a domain.

Sends NOOP command that does nothing.

Utility function to pick a supported auth method from a list of extensions.

Sends QUIT command that make the server close the connection.

Sends RCPT TO command that specify the recipient of the mail.

Sends RSET command that aborts the current mail transaction but keeps the socket open.

Sends STARTTLS extension command and starts TLS session negotiation.

Sends VRFY command that confirms or verifies the user name.

Types

auth_credentials()

@type auth_credentials() :: [username: String.t(), password: String.t()]

auth_method()

@type auth_method() :: :login | :plain

error()

@type error() :: {:error, Mua.SMTPError.t() | Mua.TransportError.t()}

host()

@type host() :: :inet.socket_address() | :inet.hostname() | String.t()

option()

@type option() ::
  {:timeout, timeout()}
  | {:mx, boolean()}
  | {:protocol, :tcp | :ssl}
  | {:auth, auth_credentials()}
  | {:port, :inet.port_number()}
  | {:tcp, [:gen_tcp.connect_option()]}
  | {:ssl, [:ssl.tls_client_option()]}

socket()

@type socket() :: :gen_tcp.socket() | :ssl.sslsocket()

Functions

auth(socket, kind, opts \\ [], timeout \\ 30000)

@spec auth(socket(), auth_method(), auth_credentials(), timeout()) :: :ok | error()

Sends AUTH extension command and authenticates the sender.

:ok = auth(socket, :login, username: username, password: password)
:ok = auth(socket, :plain, username: username, password: password)

close(socket)

@spec close(socket()) :: :ok | {:error, Mua.TransportError.t()}

Closes connection to the SMTP server.

:ok = close(socket)

connect(protocol, address, port, opts \\ [], timeout \\ 30000)

@spec connect(
  :tcp,
  host(),
  :inet.port_number(),
  [:gen_tcp.connect_option()],
  timeout()
) ::
  {:ok, :gen_tcp.socket(), banner :: String.t()} | error()
@spec connect(
  :ssl,
  host(),
  :inet.port_number(),
  [:ssl.tls_client_option()],
  timeout()
) ::
  {:ok, :ssl.sslsocket(), banner :: String.t()} | error()

Connects to an SMTP server and receives its banner.

{:ok, socket, _banner} = connect(:tcp, host, _port = 25)
{:ok, socket, _banner} = connect(:ssl, host, _port = 465, versions: [:"tlsv1.3"])

data(socket, message, timeout \\ 30000)

@spec data(socket(), iodata(), timeout()) :: {:ok, receipt :: String.t()} | error()

Sends DATA command that specifies the beginning of the mail, and then sends the message.

{:ok, _receipt} = data(socket, "Date: Sat, 24 Jun 2023 13:43:57 +0000\r\n...")

easy_send(host, sender, recipients, message, opts \\ [])

@spec easy_send(String.t() | :inet.ip_address(), String.t(), [String.t()], iodata(), [
  option()
]) ::
  {:ok, receipt :: String.t()} | error()

Utility function to send a message to a list of recipients on a host.

# sending via a relay
{:ok, _receipt} =
  easy_send(
    _host = "icloud.com",
    _sender = "ruslandoga+mua@icloud.com",
    _recipients = ["support@gmail.com"],
    _message = "Date: Sat, 24 Jun 2023 13:43:57 +0000\r\n...",
    auth: [username: "ruslandoga+mua@icloud.com", password: "some-app-password"],
    port: 587
  )

# sending directly (usually requires SPF/DKIM/DMARC on the sender domain)
{:ok, _receipt} =
  easy_send(
    _host = "gmail.com",
    _sender = "ruslandoga+mua@domain.com",
    _recipients = ["support@gmail.com"],
    _message = "Date: Sat, 24 Jun 2023 13:43:57 +0000\r\n...",
    port: 25
  )

ehlo(socket, hostname, timeout \\ 30000)

@spec ehlo(socket(), String.t(), timeout()) :: {:ok, [String.t()]} | error()

Sends EHLO command which provides the identification of the sender i.e. the host name, and receives the list of extensions the server supports.

{:ok, _extensions = ["STARTTLS" | _rest]} = ehlo(socket, _our_hostname = "icloud.com")

helo(socket, hostname, timeout \\ 30000)

@spec helo(socket(), String.t(), timeout()) :: :ok | error()

Sends HELO command which provides the identification of the sender i.e. the host name.

:ok = helo(socket, _our_hostname = "icloud.com")

mail_from(socket, address, timeout \\ 30000)

@spec mail_from(socket(), String.t(), timeout()) :: :ok | error()

Sends MAIL FROM command that specifies the originator of the mail.

:ok = mail_from(socket, "ruslandoga+mua@icloud.com")

mxlookup(domain)

@spec mxlookup(String.t()) :: [String.t()]

Utility function to lookup MX servers for a domain.

["gmail-smtp-in.l.google.com" | _rest] = mxlookup("gmail.com")

noop(socket, timeout \\ 30000)

@spec noop(socket(), timeout()) :: :ok | error()

Sends NOOP command that does nothing.

:ok = noop(socket)

pick_auth_method(extensions)

@spec pick_auth_method([String.t()]) :: auth_method() | nil

Utility function to pick a supported auth method from a list of extensions.

{:ok, extensions} = ehlo(socket, hostname)
maybe_method = pick_auth_method(extensions)
true = maybe_method in [nil, :plain, :login]

quit(socket, timeout \\ 30000)

@spec quit(socket(), timeout()) :: :ok | error()

Sends QUIT command that make the server close the connection.

:ok = quit(socket)

rcpt_to(socket, address, timeout \\ 30000)

@spec rcpt_to(socket(), String.t(), timeout()) :: :ok | error()

Sends RCPT TO command that specify the recipient of the mail.

:ok = rcpt_to(socket, "world@hey.com")

rset(socket, timeout \\ 30000)

@spec rset(socket(), timeout()) :: :ok | error()

Sends RSET command that aborts the current mail transaction but keeps the socket open.

:ok = rset(socket)

starttls(socket, address, opts \\ [], timeout \\ 30000)

@spec starttls(:ssl.socket(), host(), [:ssl.tls_client_option()], timeout()) ::
  {:ok, :ssl.sslsocket()} | error()

Sends STARTTLS extension command and starts TLS session negotiation.

{:ok, sslsocket} = starttls(socket, host, versions: [:"tlsv1.3"], middlebox_comp_mode: false)

vrfy(socket, address, timeout \\ 30000)

@spec vrfy(socket(), String.t(), timeout()) :: {:ok, boolean()} | error()

Sends VRFY command that confirms or verifies the user name.

{:ok, true} = vrfy(socket, "ruslandoga+mua@icloud.com")