35. pepsi-stage-dkim-sign

Prepend DKIM signatures before delivery.

35.1. Role

pepsi-stage-dkim-sign DKIM-signs the message and advances it to a delivery stage. Separating signing from message construction lets pepsi-stage-bounce build an unsigned DSN and point its NEXT_STAGE here, so a generated bounce is signed before it is sent. Reference: pepsi-stage-dkim-sign(1).

35.2. Features

  • Dual-signature DKIM (RFC 6376): prepends both an RSA-2048 and an Ed25519 (RFC 8463) DKIM-Signature header by default; [pepsi] DKIM_ALGORITHMS narrows that to one (the largest receivers do not verify Ed25519 and report it as fail in DMARC aggregate reports).

  • Signing domain selection: SIGNING_DOMAIN if set, otherwise the domain of the message’s From: header.

  • Full body coverage, always: RFC 6376’s l= body-length tag is never emitted and cannot be configured. Strict verifiers (mail-auth, Stalwart, and Pepsi’s own) reject an l > 0 signature outright, and RFC 6376 §8.2 warns that appended content can replace the original in the reader’s eyes.

  • Prepend-only / single write: signatures are added at the top, so existing signatures lower in the message are undisturbed; only the headers column is rewritten.

  • Fail-closed: a message is never advanced unsigned. If no signing domain can be determined, or there is no key directory for it, the message is failed (terminal failed, with the reason in state.last_error), and pepsi-failure-bouncer or the operator decides what happens to it. If the keys exist but cannot be read or used (a half-done rotation, a changed permission), the host is at fault: the message is paused and retried, with the reason in state.last_error, until the stage’s MAX_LIFETIME (default 120 hours) — fix the key and the queue drains by itself.

35.3. Configuration

[stage-<name>]: PROGRAM = pepsi-stage-dkim-sign, NEXT_STAGE (required) and SIGNING_DOMAIN, plus the signature parameters HEADER_CANONICALIZATION / BODY_CANONICALIZATION (relaxed — the default — or simple, choosable independently), SIGNED_HEADERS (the h= set; must include From) and SIGNATURE_EXPIRATION_DAYS (the x= tag; none by default). Key material and selectors come from the shared [pepsi] section (KEY_DIR, DKIM_SELECTOR; the Ed25519 selector is that name with -ed25519 appended; DKIM_ALGORITHMS). See pepsi-stage-dkim-sign(1).

35.4. State

  • Inputs: none from state (domain from SIGNING_DOMAIN or the from_header column; body hash from the loaded message).

  • Outputs: none on the signing path — the whole state, including state.dsn, is preserved. Only a failure or a retry writes anything (state.last_error, and state.temporary_failures for a retry).

35.5. See also

pepsi-stage-bounce, pepsi-stage-relay-to-internet, Supported Features, pepsi-stage-dkim-sign(1).