Mailing List Feature And Making Reply-To Work
We recently shipped mailing list support — and what looked like a straightforward feature turned into a deep dive into DKIM signature mechanics and DMARC alignment. This post covers how we built it, the problem we introduced, and how we fixed it.
We recently shipped mailing list support. The use case: you have [email protected] or [email protected] and want emails sent there distributed to a list of subscribers.
Three modes:
- Public — anyone can send, all members receive.
- Broadcast — only the owner can post (via authenticated SMTP on port 587), members receive. Good for newsletters.
- Discussion — members can post, everyone receives. A proper discussion list.
Each mode got its own UI and the delivery side fans out to each member address. Straightforward to build. The problems started after shipping.
The Reply Problem
Once it was working, I noticed something annoying with discussion lists. When you reply to an email from the list, the reply goes to the original sender — not back to the list. So if Alice posts to [email protected] and Bob hits reply, Bob's message goes to Alice directly instead of going to the list for everyone to see.
This is standard email behavior. The From: header has Alice's address, so the mail client replies to Alice. But for a discussion list, you want replies to go back to the list.
The fix is a Reply-To header pointing to the list address. Mail clients use Reply-To over From when deciding where to send a reply. So adding Reply-To: [email protected] to every forwarded message routes replies back to the list.
Simple enough. I added the header before the ARC/DKIM signing step and shipped it.
DMARC Started Failing
I noticed DMARC failures on discussion list emails. The authentication results from Gmail looked like this:
dkim=pass [email protected]
dkim=fail [email protected]
dmarc=fail (p=REJECT sp=REJECT dis=QUARANTINE) header.from=kafkai.com
The original sender's DKIM — kafkai.com — was failing.
Before the Reply-To change, the same sender's emails were passing DMARC:
dkim=pass [email protected]
dkim=pass [email protected]
dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=kafkai.com
So adding Reply-To broke something.
Why Adding a Header Breaks DKIM
DKIM signatures cover a specific list of headers declared in the h= field of the signature.
When Gmail signs an outgoing message, it includes reply-to in that list even when the
message has no Reply-To header. This is intentional — it's a protection against header
injection attacks. By signing reply-to as absent, any later attempt to add one invalidates
the signature.
So when we added Reply-To: [email protected] before our ARC/DKIM signing step:
- Gmail sent the message with
reply-tosigned as absent in their DKIMh= - We added
Reply-Toto the message — the header now exists - Gmail's DKIM verifier sees a
Reply-Tothat the signature says shouldn't be there - Gmail's DKIM fails
- DMARC checks: any passing DKIM that aligns with
kafkai.com? No - DMARC fails
My first attempt at a fix was to move the Reply-To injection to after our ARC signing step.
That fixed our ARC-Message-Signature (we no longer incorrectly included reply-to in the
headers we signed), but DMARC was still failing. The reason: it doesn't matter when we add
the header relative to our signing. The final delivered message still has Reply-To present, and kafkai.com's DKIM said it should be absent. Their DKIM fails either way.
What Actually Fixes It
The real problem is DMARC alignment. DMARC checks whether a passing DKIM signature is for
the same domain as the From: header. In this case:
From: [email protected]→ DMARC checks againstkafkai.com- KaiMail's DKIM is for
mail.kaimail.net→ doesn't help DMARC forkafkai.com kafkai.com's DKIM is broken by ourReply-To→ also doesn't help
For DMARC to pass, we need a DKIM signature from a domain that aligns with From:. The
only way to do that without kafkai.com's private key is to change what From: says.
This is exactly what Google Groups and Mailman do. Look at an email from a Google Groups mailing list:
From: 'Sxxx Abxxxx' via Wagtail support <[email protected]>
Reply-To: [email protected]
X-Original-Sender: [email protected]
X-Original-From: Sxxx Abxxxx <[email protected]>
DKIM-Signature: d=googlegroups.com; ...
They rewrite From: to their own domain (googlegroups.com), sign with their DKIM, and
DMARC aligns against googlegroups.com. The original sender's DKIM doesn't matter anymore.
The original identity is preserved in X-Original-Sender and X-Original-From.
We implemented the same approach for KaiMail discussion lists. When a customer has a DKIM key configured for their domain, the forwarding pipeline now:
- Rewrites
From:to"Kamal Mustafa via discuss" <[email protected]> - Sets
Reply-To: [email protected] - Saves the original sender in
X-Original-SenderandX-Original-From - Signs with the customer's domain DKIM key — DKIM now aligns with the rewritten
From: - ARC-signs with KaiMail's key to preserve the authentication chain
The authentication results on Yahoo mail after the fix:
dkim=pass [email protected] header.s=kaimail
dkim=perm_fail [email protected]
dmarc=pass(p=REJECT) header.from=mydomain.com
DMARC pass against p=REJECT. The original kafkai.com DKIM still fails — that's expected,
we modified the message — but it no longer matters. DMARC is satisfied by our signature on
the list's domain.
If a customer hasn't set up a domain DKIM key, we fall back to ARC signing plus Reply-To
injection. DMARC may still fail for strict senders, but the ARC chain pass softens the
disposition from REJECT to QUARANTINE — the message gets delivered, just possibly to spam
rather than inbox.
One More Small Bug
The From display name came out as Kamal Mustafa via <[email protected]> — double
space because the list name was empty. Fixed by falling back to the local-part of the list
address when the name is blank: "Kamal Mustafa via discuss" instead of "Kamal Mustafa via ".
Summary
- Discussion lists need
Reply-Topointing to the list — otherwise replies go to the original sender, not the list. - Adding
Reply-Tobreaks the original sender's DKIM if they pre-signed it as absent (Gmail does this for all outgoing mail). - Moving the injection after our signing step fixes the ARC signature but not DMARC — the original DKIM still breaks either way.
- The correct fix is From header rewriting. Shift the
From:domain to one you control, sign with your DKIM, and DMARC aligns against that domain. This is what Google Groups and Mailman 3 do.
To get DMARC passing end to end on your discussion list, make sure you have a DKIM key configured for your domain in KaiMail settings.
Kamal Mustafa