Once you rewrite SMTP headers, you either need to re-DKIM-sign the message or stop checking DKIM

A client found a single recipient domain where every Marketo email went to Spam due to failing DKIM validation. (A bad DKIM signature should really result in the message being dropped completely, so sending it to Spam was dangerously generous!)

We knew the DKIM DNS record (m1._domainkey.example.com) for Marketo was correct, and of course were reassured by only one company reporting problems. But still, you have to track things down.

What DKIM signs

DKIM signs the important parts of outbound SMTP messages: the SMTP body and s set of common SMTP headers.

If a DKIM signature is valid, the recipient knows those parts of the message were generated on a server controlled by the signer, which increases trust.

(It also works both ways: the signer can’t claim they didn’t generate the message. This is known as “non-repudiation” and is important in regulated industries.)

A sample DKIM-Signature header looks like this:

DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1754422263;
	s=m1; d=example.com; i=@example.com;
	h=List-Unsubscribe:List-Unsubscribe-Post:Content-Type:MIME-Version:Subject:To:From:Date;
	bh=+Sv3HC8ADc5Pw461LNuM4JotacUIplmGEq0oW2dh958=;
	b=djmGGvM8Pyh+Wb8pjFRDu1laQpirD7eKY+3QamZTBoRpt0ugu3XlqV82rjVnNxG1
	eVE98WsxcKp7mrS85tt1/AhWjyidytICcu9ijb5NGTIEyeXO92heaIczEN0c1wI8AeU
	839pMpPiCW6LOvJCTTkeLRNIkSEP3RnV/G1+Jblw=

The h= parameter lists the message headers covered by the signature: Subject, To, From, Date and a few others. These headers cannot be changed without invalidating the signature. Makes sense, right? If a hacker could change the Subject and From and still have a valid signature, that would undermine protection against impersonation + repudiation. (The message body is always signed and can’t be altered without invalidating the sig, but the body isn’t the only important part.)

Any header not in that list can be safely changed in transit: for example, the standard Message-ID and nonstandard X- headers like X-MSFBL.[1] Setting a new Message-ID can cause other confusion[2] of course, but it won’t make the message bad from a DKIM perspective.

Sometimes the clues are in [ALL CAPS]

It turned out the company had a bad combo of configs.

They had a mail scanner prepending[MARKETING] to the Subject of Marketo emails after accepting them — something you’ve probably seen before. But when the Subject line is altered, that by definition breaks the DKIM signature if it includes (and it almost always does) the Subject.

What a server should do, in this order, is:

  1. validate any DKIM signature(s) against the original content
  2. assuming signatures were valid, add another header like X-DKIM-Originally-Valid: example.com, mktomail.com
  3. remove allDKIM-Signature headers; alternately, leave the original DKIM-Signature headers behind, but only if the message isn’t processed by any other servers that validate DKIM
  4. change the Subject line or anything else (sometimes those servers add a WARNING to the body too)
  5. optionally, re-sign the message using the new body + header content; in this case you’ll be signing it on behalf of your domain, not the original sender domain, but the sig can be used by other internal systems

What this company was doing instead was changing the Subject line, leaving the DKIM-Signature headers as-is, then forwarding to an SMTP server elsewhere in their infrastructure. That SMTP server was validating DKIM as if the messages were from the outside world. Naturally, all the altered messages failed DKIM!

The solution was to bypass DKIM validation on that next-hop server, since that check isn’t appropriate if you ever alter content after delivery.

Notes

[1] In this example, copied from a real Marketo email, an intermediate server could also tweak Reply-To. Make of that what you will!

[2] The Message-ID header is key to accurate threading in email clients. When you reply to a message, the original Message-ID is automatically copied to the In-Reply-To of your reply. So if new Message-IDs get assigned they won’t be grouped into the same conversation.