85.1.14. pepsi-stage-relay-to-lmtp

deliver a message to a local MDA (Dovecot) over LMTP

Manual section:

1

85.1.14.1.1. Name

pepsi-stage-relay-to-lmtp - the LMTP local-delivery stage of the Pepsi pipeline.

85.1.14.1.2. Synopsis

pepsi-stage-relay-to-lmtp [GLOBAL-OPTIONS] worker

85.1.14.1.3. Description

pepsi-stage-relay-to-lmtp is a stage program run by pepsi-dispatch(1). It loads the pepsi.workqueue row (refusing to act unless its status is running), reads its [stage-<stage>] section, and hands the message to a Mail Delivery Agent — typically Dovecot — over LMTP (RFC 2033) for final local delivery. The MDA applies each recipient’s Sieve (RFC 5228) script as it files the mail, so server-side filtering (fileinto, vacation, redirect, flag setting, …) is provided by the MDA rather than by Pepsi.

The stage is LMTP-native: every envelope recipient is offered in a single LMTP transaction and the MDA returns a separate reply per recipient after end-of-DATA. The stage itself never builds a delivery-status notification — it delivers, and otherwise routes each recipient onward to a configured stage, recording why under state.bounce so a downstream pepsi-stage-bounce(1) (or a relay) can act on it. Each recipient’s fate follows its reply:

  • 2xx — delivered; the recipient leaves the row. When the recipient asked for NOTIFY=SUCCESS and [pepsi] ORIGINATE_SUCCESS_DSN is set, a positive notification is routed to the optional NOTIFY_STAGE.

  • a permanent reject is routed by reason, classified from the reply’s RFC 3463 enhanced status: a bad destination mailbox (x.1.x, or x.2.x other than x.2.2, or a bare 550 user-unknown with no enhanced status) goes to UNKNOWN_MAILBOX_STAGE when the recipient is on one of LOCAL_DOMAINS, else to NEXT_STAGE; an over-quota reject (x.2.2) to QUOTA_LIMIT_STAGE (else NEXT_STAGE); a Sieve / local policy reject (x.7.x) to SIEVE_REJECT_STAGE (else NEXT_STAGE); anything else to NEXT_STAGE.

  • 4xx (e.g. server busy) — a transient failure: the recipient is retried with exponential backoff until MAX_LIFETIME, then routed by the same reason classification.

A message can therefore be partially handled: delivered, routed and deferred recipients are reconciled in one fan-out — the delivered recipients leave the row, each routed recipient becomes a sibling pending row (sliced to that one recipient, so a relay target never re-delivers to its siblings) at its target stage, and the original row is reduced to just the still-deferred recipients for the next retry. A DELAY warning (when due and NOTIFY=DELAY was requested) is likewise routed to NOTIFY_STAGE. The fan-out and the row’s own end (deleted, or paused for the retry) are one statement: the MDA has already filed the delivered recipients, and a split committed without its end would leave them on a row for the retry to deliver again. Should that statement fail (the database is unreachable), the error is retried and the recipients the MDA accepted are delivered a second time — delivery is at least once.

A failure of the whole session — connecting, the greeting, STARTTLS, authentication or the data transfer, before any per-recipient reply — is not recipient-specific: a transient one retries the whole message with backoff, and a permanent one (or any once MAX_LIFETIME has passed) routes every recipient to NEXT_STAGE without reason classification.

Because the MDA decides which addresses are local, this stage performs no locality test of its own (unlike pepsi-stage-relay-to-maildir(1)): every recipient is offered to the MDA. LOCAL_DOMAINS only decides where an unknown mailbox goes afterwards. On one of our own domains it must not reach a relay stage — the relay would look up our own MX and post the message straight back to this host — so it goes to UNKNOWN_MAILBOX_STAGE; on any other domain (an alias target that expanded off-site) it goes on to NEXT_STAGE. Place it where only intended-local recipients arrive — usually after alias expansion (pepsi-stage-aliases(1)). It is not terminal: NEXT_STAGE is mandatory — set it to a pepsi-stage-bounce(1) to bounce undeliverable recipients, or to a relay stage to forward them off-site. A message that reaches the stage with no NEXT_STAGE configured is marked failed before the MDA is contacted, so nothing is delivered.

Unlike local Maildir writing, LMTP needs no privileged helper: the MDA performs the per-user delivery itself, so this stage runs as the unprivileged pepsi user and carries no setuid/setgid bit. It is folded into the unified pepsi binary.

85.1.14.1.4. Options

Run mode:

worker

Run as a persistent pepsi-dispatch(1) worker, reading workqueue row ids on standard input.

Configuration (in the stage’s own [stage-<name>] section):

PROGRAM

Must be pepsi-stage-relay-to-lmtp.

NEXT_STAGE

Required. Where a recipient the MDA could not deliver is routed (a bad destination mailbox, and the fallback for the reasons below). Set it to a pepsi-stage-bounce(1) to bounce undeliverable recipients, or to a relay stage (e.g. pepsi-stage-srs(1) → a smarthost) to forward them off-site. pepsi-setup rejects a configuration without it.

QUOTA_LIMIT_STAGE

Where to route a recipient the MDA refused for being over quota (RFC 3463 x.2.2). Defaults to NEXT_STAGE. Set it to a bounce stage when NEXT_STAGE is a relay, so quota rejects bounce rather than relay off-site.

SIEVE_REJECT_STAGE

Where to route a recipient refused by Sieve / local policy (RFC 3463 x.7.x). Defaults to NEXT_STAGE.

UNKNOWN_MAILBOX_STAGE

Where to route a recipient on one of LOCAL_DOMAINS that the MDA refused as an unknown mailbox (x.1.x, a bare 550). Defaults to NEXT_STAGE. Set it to a bounce stage whenever NEXT_STAGE can reach a relay: relaying an unknown address on our own domain delivers it back to this host. pepsi-setup --wizard sets it to bounce.

LOCAL_DOMAINS

The domains whose mailboxes the MDA holds (space- or comma-separated). Defaults to [pepsi-ingress] ACCEPTED_DOMAINS. Read for UNKNOWN_MAILBOX_STAGE, and by pepsi-ingress(1)’s recipient check, which asks the MDA whether an address on these domains exists before accepting it (see VERIFY_RECIPIENTS).

NOTIFY_STAGE

Where positive SUCCESS delivery notifications and DELAY warnings are routed (as a cloned message carrying state.bounce.kind); set it to a pepsi-stage-bounce(1), which renders the report. When unset, no positive or delay notifications are originated. The stage never builds a notification itself.

SOCKET

Path of the MDA’s LMTP UNIX-domain socket (e.g. /run/dovecot/lmtp). The common local-delivery layout; no TLS or authentication. Exactly one of SOCKET and HOST must be set.

HOST / PORT

Reach the MDA over TCP instead (PORT defaults to 24). Use for an MDA on another host.

TLS

Transport security for a HOST target: off (default), starttls, or tls (implicit). TLS_VERIFY (default yes) and TLS_CA control PKIX verification; TLS_CLIENT_CERT/TLS_CLIENT_KEY present a client certificate for mutual TLS / SASL EXTERNAL.

AUTH

SASL mechanism for a TCP target: none (default), plain, login or external. Authentication is refused over cleartext, so any non-none AUTH requires TLS = starttls or tls (and external requires a client certificate). USERNAME/PASSWORD supply the credential.

SERVER_NAME

Name announced in LHLO and stamped in the Received: header. Defaults to [pepsi-ingress] HOSTNAME.

MAX_LIFETIME, RETRY_INITIAL, RETRY_MAX_INTERVAL, RETRY_FACTOR

Retry/give-up timing for transiently-deferred recipients.

DELAY_DSN_AFTER

Emit a one-shot “delayed” DSN once a still-undelivered message has been queued this long and the sender asked for NOTIFY=DELAY.

CONNECT_TIMEOUT, COMMAND_TIMEOUT, DATA_TIMEOUT

Per-operation LMTP timeouts.

85.1.14.1.5. See Also

pepsi-dispatch(1), pepsi-stage-relay-to-maildir(1), pepsi-stage-aliases(1), pepsi-stage-bounce(1), pepsi.conf(5), pepsi.state(7).

RFC 2033 (LMTP), RFC 5228 (Sieve).