An SMTP client (RFC 5321) for relaying messages to another server.
{:ok, client} = Client.connect({192, 0, 2, 25}, 25, helo: "mx.example.com")
{:ok, client, results} = Client.deliver(client, "a@example.com", ["b@example.net"], body)
:ok = Client.quit(client)connect/3 reads the greeting and sends EHLO, falling back to HELO
when the server rejects EHLO with a 5xx reply. One connection can
carry any number of deliver/5 transactions.
With PIPELINING (RFC 2920), MAIL, every RCPT, and DATA are sent
in one batch. SIZE (RFC 1870) and BODY=8BITMIME (RFC 6152) are sent
when the server supports them. The body is dot-stuffed while streaming
with Sovite.SMTP.DataEncoder.
Replies are parsed with Sovite.SMTP.Reply.decode/2, so a hostile
server cannot make the client buffer without limit. Every wait has a
timeout; the defaults are those of RFC 5321 §4.5.3.2.
starttls/2 upgrades a connection (RFC 3207) and authenticate/3
logs in with SASL (RFC 4954). With the :tls option the connection is
encrypted from the start instead (implicit TLS, RFC 8314).
LMTP
With protocol: :lmtp the client speaks LMTP (RFC 2033) instead: it
greets with LHLO, and after the data the server answers once for
each accepted recipient, so every recipient gets its own result at
:data_end. LMTP servers usually listen on a Unix socket: pass
{:local, path} as the address (the port is ignored).
Options
:helo- name to send inEHLO/HELO(orLHLO). Required.:protocol-:smtp(default) or:lmtp.:connect_timeout- milliseconds. Defaults to 30 seconds.:greeting_timeout- for the220greeting. Defaults to 5 minutes.:command_timeout- forEHLO,MAIL,RCPT,RSET, andQUITreplies. Defaults to 5 minutes.:data_timeout- for the reply toDATA. Defaults to 2 minutes.:send_timeout- for each block of message data. Defaults to 3 minutes.:data_end_timeout- for the reply after the final dot. Defaults to 10 minutes.:local_address- local IP address to connect from. Not used for Unix sockets.:max_line_length/:max_lines- reply limits, seeSovite.SMTP.Reply.decode/2.:tls-:sslclient options to use implicit TLS: the handshake runs right after connecting, before the greeting.:tls_timeout- for TLS handshakes. Defaults to 60 seconds.
Summary
Types
Where to connect: an IP address, or a Unix socket.
A failure that ends the connection. The reason is a rejection reply
(at :greeting, :ehlo, :helo, or :lhlo), :timeout, :closed, a
Sovite.SMTP.Reply.decode_error(), a socket error, or {:tls, reason}
for a failed TLS handshake (at :tls or :starttls).
The outcome for one recipient: the reply that decided it, and the
command it answered. A 2xx reply at :data_end means the server took
the message for this recipient.
The command a reply or error belongs to. :data_end is the final dot:
after an error there, the server may or may not have accepted the
message.
Functions
Authenticates with SASL (RFC 4954), using the first of mechanisms
the server offers.
Closes the connection without QUIT.
Connects, reads the greeting, and sends EHLO (or HELO, or LHLO).
Sends one message to recipients.
Returns the extensions the server announced: upper-cased keywords
mapped to their parameters ("" if none).
Returns the server's address and port.
Sends QUIT and closes the connection. Errors are ignored.
Returns the name the server gave in its EHLO/HELO reply, for
example to detect a connection to itself.
Upgrades the connection with STARTTLS and sends EHLO again, as
RFC 3207 requires.
Returns the TLS details if the connection is encrypted, else nil.
Types
@type address() :: :inet.ip_address() | {:local, Path.t()}
Where to connect: an IP address, or a Unix socket.
@type error() :: {stage(), Sovite.SMTP.Reply.t() | :timeout | :closed | Sovite.SMTP.Reply.decode_error() | {:tls, term()} | atom()}
A failure that ends the connection. The reason is a rejection reply
(at :greeting, :ehlo, :helo, or :lhlo), :timeout, :closed, a
Sovite.SMTP.Reply.decode_error(), a socket error, or {:tls, reason}
for a failed TLS handshake (at :tls or :starttls).
@type refusal() :: {:message_too_large, pos_integer()} | :eight_bit_not_supported | {:invalid_address, String.t()}
Why deliver/5 did not start a transaction. The connection stays
usable.
{:message_too_large, limit}- larger than the server'sSIZE.:eight_bit_not_supported- an 8-bit message (body_type: :"8bitmime"), but the server does not support8BITMIME.{:invalid_address, address}- not a valid mailbox.
@type result() :: {recipient :: String.t(), stage(), Sovite.SMTP.Reply.t()}
The outcome for one recipient: the reply that decided it, and the
command it answered. A 2xx reply at :data_end means the server took
the message for this recipient.
@type stage() ::
:connect
| :tls
| :greeting
| :ehlo
| :helo
| :lhlo
| :starttls
| :auth
| :mail
| :rcpt
| :data
| :data_end
| :rset
| :quit
The command a reply or error belongs to. :data_end is the final dot:
after an error there, the server may or may not have accepted the
message.
@opaque t()
Functions
@spec authenticate(t(), map(), [String.t()]) :: {:ok, t()} | {:error, t(), term()} | {:error, error()}
Authenticates with SASL (RFC 4954), using the first of mechanisms
the server offers.
credentials has :username and :password (or :token for
OAUTHBEARER). Mechanisms default to ["SCRAM-SHA-256", "PLAIN", "LOGIN"]. Do not send passwords over an unencrypted connection.
Returns {:error, client, reason} with the connection still usable:
:no_mechanism (none offered in common), {:rejected, reply}, or a
{:sasl, reason} from the mechanism (such as a server whose
SCRAM-SHA-256 signature is wrong).
@spec close(t()) :: :ok
Closes the connection without QUIT.
@spec connect(address(), :inet.port_number(), keyword()) :: {:ok, t()} | {:error, error()}
Connects, reads the greeting, and sends EHLO (or HELO, or LHLO).
@spec deliver(t(), String.t(), [String.t(), ...], Enumerable.t(), keyword()) :: {:ok, t(), [result()]} | {:error, t(), refusal()} | {:error, error()}
Sends one message to recipients.
body is an enumerable of iodata chunks with CRLF line endings, not
dot-stuffed. Use sender "" for the null reverse-path.
Returns one result per recipient, in order. The transaction is reset
when it fails, so the connection can be reused after {:ok, ...} and
{:error, client, refusal}.
Options
:size- the message size in bytes, sent withSIZEand checked against the server's limit.:body_type-:"7bit",:"8bitmime", ornil(not declared).
Returns the extensions the server announced: upper-cased keywords
mapped to their parameters ("" if none).
@spec peer(t()) :: {address(), :inet.port_number()}
Returns the server's address and port.
@spec quit(t()) :: :ok
Sends QUIT and closes the connection. Errors are ignored.
Returns the name the server gave in its EHLO/HELO reply, for
example to detect a connection to itself.
@spec starttls(t(), [:ssl.tls_client_option()]) :: {:ok, t()} | {:error, t(), :not_offered | {:refused, Sovite.SMTP.Reply.t()}} | {:error, error()}
Upgrades the connection with STARTTLS and sends EHLO again, as
RFC 3207 requires.
Returns {:error, client, reason} when the connection stays usable
without TLS: :not_offered (no STARTTLS extension), or
{:refused, reply}. Returns {:error, {:starttls, reason}} when it is
gone, for example after a failed handshake ({:tls, ssl_reason}).
@spec tls(t()) :: Sovite.TLS.info() | nil
Returns the TLS details if the connection is encrypted, else nil.