Delivers one job (a message to a group of recipients with the same
destination). Runs in a task started by Sovite.Core.QueueManager.
The destination's transport (see Sovite.Core.Router) decides how:
:smtp- to another server, described below.:lmtp- to a mailbox server such as Dovecot over LMTP (RFC 2033), on a Unix socket or a host. Each recipient gets its own status code after the data. LMTP connections are reused like SMTP ones.:localand:mailbox- into a Maildir folder, from themaildir.localormaildir.mailboxtemplate.:pipe- to the command of a[pipe.<name>]config section, once per recipient, throughSovite.Pipe.
These four are final deliveries. A recipient that a Delivered-To:
field of the message already names is a mail loop and fails with
5.4.6 (RFC 9228). Maildir and pipe deliveries get Return-Path: and
Delivered-To: fields at the top (RFC 5321 §4.4); an LMTP server adds
its own.
SMTP
For each SMTP job it:
- Finds the destination's addresses: MX hosts in preference order
(
Sovite.DNS.MX), then each host's addresses in the configured IP version order, at mostdelivery.max_addressesin total. - Tries them in turn until one accepts a connection and completes
the
EHLO. A failure to connect, a rejected greeting, or a connection lost before the end of the data moves on to the next address (RFC 5321 §4.5.4.1). - Runs the transaction and turns each recipient's reply into a result: 2xx delivered, 4xx deferred, 5xx failed.
A connection that ends in a clean state is handed back with the result, and the queue manager may give the worker another job for the same destination to send over it.
A server that greets with this server's own host name is a mail loop,
and the job fails with 5.4.6.
TLS
Each destination has a TLS level, from delivery.tls_policy or else
delivery.tls:
:none- never use TLS.:may- opportunistic TLS (RFC 7435):STARTTLSwhen offered, without checking the certificate. If the handshake fails, the address is tried again without TLS.:encrypt- TLS is required; the certificate is not checked.:verify- TLS is required, and the certificate must be valid for the MX (or relay) host name, from a trusted CA.:dane(the default) - DANE (RFC 7672): when DNSSEC authenticated the host's MX and address records and it has TLSA records, TLS is required and the certificate must match them; otherwise as:may. A failed TLSA lookup skips the host.
With :may and :dane, mail to a domain's MX hosts also follows the
domain's MTA-STS policy (RFC 8461, see Sovite.Core.MTASTS), for
hosts where DANE does not apply: DANE wins (RFC 8461 §2). In
enforce mode, only MX hosts the policy lists are used, with TLS and
a certificate valid for the host name from a trusted CA. In testing
mode, delivery is opportunistic and problems are only reported.
When TLS is required and fails, the address is skipped with 4.7.4
(not offered) or 4.7.5 (handshake or certificate failure). Relay
hosts on port 465 get implicit TLS.
A message received with REQUIRETLS (RFC 8689) is only sent over TLS
verified with DANE or an MTA-STS policy (testing mode counts as
enforce), or for relay hosts with a trusted certificate, and only to
servers that offer REQUIRETLS; otherwise it fails with 5.7.30. A
message with TLS-Required: No ignores the DANE and MTA-STS policies.
Extensions
With delivery.chunking (the default), messages go to servers that
offer CHUNKING in BDAT chunks (RFC 3030). A binary message
(BINARYMIME) can only go to servers that offer BINARYMIME; to
others it fails with 5.6.3, as it is not converted. The message's
DSN parameters (RFC 3461) go to servers that offer DSN; a recipient
delivered to another server is then reported to the sender as
"relayed" when it asked for success notifications, except where that
server got the parameters and reports itself.
The MT-PRIORITY (RFC 6710) of a message goes to servers that offer
it. So does a DELIVERBY deadline (RFC 2852), with the time left; a
message that must be returned when it passes (R) only goes to such
servers, and fails with 5.4.7 when no MX host offers it, while one
with N that goes to another server is reported as "relayed" (also
with T, trace). A recipient with RRVS (RFC 7293) goes with it to
servers that offer it; to others, it fails with 5.7.19 (R) or goes
without it (C).
With tls_rpt.reports, each session to an MX host is recorded for
TLS-RPT (RFC 8460): the policy that applied and whether TLS worked.
When the destination has credentials (see Sovite.Core.Router), the
client authenticates to the next hop, but only over TLS. With a source
address for the address family, the connection is made from it.
Summary
Types
An open connection and the host it goes to, for reuse.
Work for one delivery: built by the queue manager.
Worker options
Functions
Runs job, reusing connection if given. Returns one result per
recipient and the connection, if it can carry another message.
Types
@type connection() :: {Sovite.SMTP.Client.t(), String.t()}
An open connection and the host it goes to, for reuse.
@type job() :: %{ queue_id: String.t(), destination: Sovite.Core.Router.destination(), recipients: [String.t(), ...], sender: String.t(), body_type: :"7bit" | :"8bitmime" | :binarymime | nil, path: Path.t(), message_offset: non_neg_integer(), message_size: non_neg_integer(), prefix: binary(), requiretls: boolean(), smtputf8: boolean(), xforward: Sovite.SMTP.Server.Session.xforward() | nil, dsn: Sovite.SMTP.Client.dsn() | nil, chunking: boolean(), priority: -9..9, deliver_by: Sovite.Queue.Envelope.deliver_by() | nil, rrvs: %{required(String.t()) => Sovite.SMTP.Params.rrvs()} }
Work for one delivery: built by the queue manager.
@type opts() :: %{ hostname: String.t(), resolver: Sovite.DNS.resolver(), port: :inet.port_number(), families: [:a | :aaaa, ...], max_addresses: pos_integer(), client: keyword(), tls: %{ default: tls_level(), policy: %{required(String.t()) => tls_level()}, cacerts: [binary()] | nil }, maildir: %{optional(:local | :mailbox) => String.t() | nil}, pipes: %{required(String.t()) => map()}, delimiter: String.t(), tmp_dir: Path.t(), mta_sts: GenServer.server() | nil, tls_reports: Sovite.Core.Repo.t() | nil, ip_pools: atom() | nil }
Worker options:
:maildir-%{local: template, mailbox: template}, Maildir path templates with{user},{domain}, and{address}, ornil.:pipes- pipe commands by name, from the[pipe]config section.:delimiter- the address extension delimiter.:tmp_dir- where pipe deliveries write the message for the command to read.:hostname- this server's name, forEHLOand loop detection.:resolver- aSovite.DNSresolver.:port- SMTP port for MX deliveries. 25 except in tests.:families-[:aaaa, :a]and similar, seeSovite.DNS.MX.resolve/3.:max_addresses- addresses to try per job.:client- options forSovite.SMTP.Client.connect/3.:tls-%{default, policy, cacerts}: the default level, a map of destination (domain, relay host, or address literal) to level, and the CAs for:verify(nilfor the system's).:mta_sts- theSovite.Core.MTASTSserver, ornilto ignore MTA-STS policies.:tls_reports- theSovite.Core.Reporeference to record TLS-RPT outcomes in, ornil.
@type result() :: {String.t(), Sovite.Queue.Record.status(), Sovite.Queue.Record.details()}
@type tls_level() :: :none | :may | :encrypt | :verify | :dane
Functions
@spec run(job(), connection() | nil, opts()) :: {[result()], connection() | nil}
Runs job, reusing connection if given. Returns one result per
recipient and the connection, if it can carry another message.