Changes for version 0.20 - 2026-10-14

  • DKIM2 for stock Mailman, with dkim2-milter and Postfix routing only.
  • New: dkim2-milter --wrap-single-part (inbound mode, needs --snapshot-dir). A single-part post is wrapped as a one-part multipart/mixed (its Content-* fields and body unchanged), the wrap recorded as its own unsigned Message-Instance, and the wrapped message kept as the snapshot the outbound milter diffs against. Stock Mailman then adds its footer as an extra part rather than re-encoding the body. See docs/dkim2-postfix-stock-mailman-guide.md in the interop repository.
  • New: Mail::DKIM2::Wrap (wrap_single_part).
  • Behaviour change: dkim2-milter in outbound mode adds no Message-Instance to mail it does not sign (no key for the sender, a refused chain, a signer failure), and removes every Message-Instance no DKIM2-Signature covers -- instances hops on this host added for a signature that is not going to be made -- recording it as X-DKIM2-Info: action=removed-unsigned-mi=...;. It used to add an unsigned Message-Instance and leave the others, which receivers report as permerror ("m=N is not signed"). An unsigned post now leaves with no DKIM2 at all, and a signed one fails honestly against the sender's signature. DKIM2Sign does the same on a refusal, a domain with no signing configuration, or a Signer failure.
  • Fix: dkim2-milter in outbound mode computes its own Message-Instance (the diff against the snapshot) before checking the upstream chain, and checks the message as it will be signed. It used to check first, so a list manager that changed a message without recording an instance of its own -- every list copy whose changes only the snapshot diff records -- was refused.
  • Security fix: dkim2-milter no longer finds snapshots by Message-Instance value. A post that replayed a sender's genuine m=1 over an edited body found the genuine post's snapshot, and the edit was recorded and signed as this host's own change. The inbound milter now snapshots only a message it stamps m=1 or whose top instance matches the content, stores the snapshot under a random token and names it in a new X-DKIM2-Snapshot header; the outbound milter diffs only against the snapshot that token names. Every mode strips X-DKIM2-Snapshot from the mail it sees, so a token neither comes from outside nor leaves the host. The inbound and outbound milters must share --snapshot-dir (as before) and be upgraded together. The authentication_milter handlers do the same: DKIM2Verify names its snapshot in X-DKIM2-Snapshot (and removes any that arrives), DKIM2Sign finds it only through that token, and both must be upgraded together.
  • Behaviour change: since the outbound milter's own diff instance is now gated as an unsigned instance, a diff that falls back to a null body Recipe is signed only with --allow-null-body-recipe.
  • Fix: dkim2-milter registers SMFIF_CHGHDRS, so removing a broken intermediate Message-Instance (which calls chgheader) no longer dies in Sendmail::PMilter. The removals now go out before the new Message-Instance is inserted, so an MTA that counts inserted fields in a header index still removes the right one.

Documentation

Standalone DKIM2 milter for Postfix

Modules

DKIM2 signing and verification for email
Canonicalization, hashing, folding and key handling for DKIM2
DKIM2-signed Delivery Status Notifications
decide whether a front end may sign a message
Streaming message parser base for Signer and Verifier
Compute, verify and undo Message-Instance headers
Keep message snapshots keyed by Message-Instance
verify-and-reflect DKIM2 demonstration logic for dkim2.com
One DKIM2-Signature header, parsed or under construction
Sign a message with a DKIM2-Signature header
Bcc-safe recipient grouping before DKIM2 signing
The tag=value list a DKIM2 header is made of
structured per-level DKIM2 and Message-Instance report
Verify the DKIM2-Signature chain on a message
wrap a single-part message in a one-part multipart/mixed
Handler class for DKIM2 signing
Handler class for DKIM2 signature verification