Sovite.Core.Delivery (sovite v0.2.0)

Copy Markdown View Source

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.
  • :local and :mailbox - into a Maildir folder, from the maildir.local or maildir.mailbox template.
  • :pipe - to the command of a [pipe.<name>] config section, once per recipient, through Sovite.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:

  1. 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 most delivery.max_addresses in total.
  2. 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).
  3. 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): STARTTLS when 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

connection()

@type connection() :: {Sovite.SMTP.Client.t(), String.t()}

An open connection and the host it goes to, for reuse.

job()

@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.

opts()

@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}, or nil.

  • :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, for EHLO and loop detection.

  • :resolver - a Sovite.DNS resolver.

  • :port - SMTP port for MX deliveries. 25 except in tests.

  • :families - [:aaaa, :a] and similar, see Sovite.DNS.MX.resolve/3.

  • :max_addresses - addresses to try per job.

  • :client - options for Sovite.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 (nil for the system's).

result()

tls_level()

@type tls_level() :: :none | :may | :encrypt | :verify | :dane

Functions

run(job, connection, opts)

@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.