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- DANE (RFC 7672): when the host has DNSSEC-authenticated TLSA records, TLS is required and the certificate must match them; otherwise as:may. A failed TLSA lookup skips the host.
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.
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" | nil, path: Path.t(), message_offset: non_neg_integer(), message_size: non_neg_integer() }
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() }
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).
@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.