Replying to the clean one! There are two bits of DSN handling - here's some detail, partly me, partly my Claude instance which did most of the implementation and testing.
*1. Originating a DSN for a message that failed at your edge. * This is a brand-new DKIM2 message: MAIL FROM <>, one Message-Instance, one DKIM2-Signature (§12). There is no recipe to undo — it's a fresh message, so this is just "sign an outbound message," which a milter can do, provided the milter runs over the MTA's own generated bounces. On Postfix that's internal_mail_filter_classes = bounce, which routes bounce(8) output through non_smtpd_milters. We do exactly this on the demo server (the [email protected] address). Two caveats from experience: • Postfix's "generally not safe" warning is aimed at content filters that re-inject or rewrite and can loop. A sign-only milter on non_smtpd_milters is fine — it doesn't re-inject. • You must stop the MTA from re-encoding the message after signing, or the signature breaks. We were bitten by the 8bit→7bit downgrade rewriting Content-Transfer-Encoding on a bounce after the milter signed it; disable_mime_output_conversion = yes fixes it. (This is the "Preventing Transport Conversions" section of the spec, and it's worth flagging that MTA-originated bounces are genuinely harder here than normal outbound mail.) • I do think a patch to postfix to allow or prefer 7 bit bounce(8) generation makes sense, there's no need to put Content-Transfer-Encoding to 8 bit where there's no 8 bit in the message. *2. Propagating a DSN back up a forwarding chain (§12.1). * When you forwarded a message and it bounced further downstream, you receive a DSN, undo your recipe, drop your signature, and re-emit to the previous hop. This is where the undo/re-sign logic lives — but note it's triggered by an inbound message, arriving over SMTP, so there's no async-bounce problem: a milter/filter sees it, and your library does the undo + re-sign. It's ordinary inbound processing, not the bounce(8) path. So the recipe-undoing you were worried about never applies to the MTA's own freshly-generated bounce; it only applies to case 2, which isn't in the async path anyway. On the pipe transport: yes, that's what the reference implementation does for its reflector-dsn address — a pipe(8) transport that shells out to the DKIM2 library and re-injects on a milter-free port. It's convenient for the propagation case and for a demo, but you're right that it's fragile and per-MTA. It's a stopgap, not the intended end state. The point of doing it with a pipe + a milter today is to prove the whole thing is implementable on an unmodified MTA and to let the implementations interoperate now. The expectation is absolutely that DKIM2 support lands natively in the MTAs (Postfix/Exim/Sendmail) — the same path DKIM1 took from milter to native — and that operators shouldn't have to hand-roll a pipe per box. *For PhoenixDKIM specifically (milter + library), Claude suggests:* • Sign your MTA's own bounces in the milter via internal_mail_filter_classes = bounce + non_smtpd_milters (sign only), plus disable_mime_output_conversion = yes. No per-operator pipe needed. • Handle DSN propagation (undo + re-sign) as ordinary inbound processing in your library; it's an inbound SMTP message, not a bounce-path callback. Bron. On Thu, Jun 25, 2026, at 06:00, Edmund Lodewijks wrote: > ~~ Sending this again, as the previous message got horribly mangled! > Apologies for the noise! ~~ > > > Hello all! > > I am trying to implement §12 of spec-03 in PhoenixDKIM (an OpenDKIM fork > — milter plus library), and my question is where DSN generation is > supposed to live. > > A milter cannot do it, I think: Once we've answered `250`, the session > is gone and there is no milter in the async bounce path. And the MTA's > own bounce generator knows nothing about DKIM2— it won't undo recipe > changes or sign the DSN as a fresh message with `mf=<>`. > > It was suggested earlier on this ML (Bron?) that you can do this on > Postfix today without core changes, presumably a pipe/ transport that > shells out to a DKIM2 library and re-injects. This seems to be what the > reference implementation on github is doing? > > That would work, but is the expectation indeed that every operator wires > their own pipe, per MTA? That feels fragile and inefficient, if everyone > is supposed to go that way with DKIM2. Is the intent that this > eventually lands in the MTAs (Postfix/ Exim/ Sendmail), with the pipe > just a stopgap? > > Thank you for your time, sharing of knowledge, and energy! > > Kind regards, > > Edmund Lodewijks > > > -- > Edmund Lodewijks <[email protected]> > TZ: UTC+2 / GMT+2 > > _______________________________________________ > Ietf-dkim mailing list -- [email protected] > To unsubscribe send an email to [email protected] > -- Bron Gondwana, CEO, Fastmail Pty Ltd / Fastmail US LLC [email protected]
_______________________________________________ Ietf-dkim mailing list -- [email protected] To unsubscribe send an email to [email protected]
