· Kamal Mustafa Kamal Mustafa

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:

  1. Gmail sent the message with reply-to signed as absent in their DKIM h=
  2. We added Reply-To to the message — the header now exists
  3. Gmail's DKIM verifier sees a Reply-To that the signature says shouldn't be there
  4. Gmail's DKIM fails
  5. DMARC checks: any passing DKIM that aligns with kafkai.com? No
  6. 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 against kafkai.com
  • KaiMail's DKIM is for mail.kaimail.net → doesn't help DMARC for kafkai.com
  • kafkai.com's DKIM is broken by our Reply-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:

  1. Rewrites From: to "Kamal Mustafa via discuss" <[email protected]>
  2. Sets Reply-To: [email protected]
  3. Saves the original sender in X-Original-Sender and X-Original-From
  4. Signs with the customer's domain DKIM key — DKIM now aligns with the rewritten From:
  5. 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-To pointing to the list — otherwise replies go to the original sender, not the list.
  • Adding Reply-To breaks 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.