You can have a perfectly valid DKIM signature and still fail DKIM.
I’ve watched people stare at dkim=pass in the Authentication-Results header and not understand why their DMARC reports say dkim=fail on the same message. The signature verified. The math checked out. The body hash matched. And DMARC threw it out anyway.
The word that explains the gap isn’t in “DKIM.” It’s alignment. And it’s the single most misunderstood thing in email authentication.
What DKIM actually proves
DKIM (RFC 6376) does one narrow thing well. A sending system hashes the body and a chosen set of headers, signs that hash with a private key, and drops a DKIM-Signature header into the message. The receiver pulls the public key from DNS — selector._domainkey.signingdomain.com — and verifies the signature.
If it verifies, you’ve learned exactly one fact: the domain in the d= tag took responsibility for this message. RFC 6376 calls that the SDID, the Signing Domain Identifier. That’s it. That’s the entire claim.
Notice what’s missing. The signature says nothing about the From: address the human reads. Nothing. d= can be any domain that holds a key in DNS. A message from ceo@yourcompany.com can carry a flawless signature from d=sendgrid.net, and DKIM is perfectly happy. The signature is valid. SendGrid really did sign it. Everything is true and nothing is aligned.
DMARC bolts alignment on top
DMARC (RFC 7489) is the layer that finally asks the question DKIM never does: does the domain that signed this mail have anything to do with the domain in the From: header?
Section 3.1.1 spells it out. For DKIM to count toward DMARC, the d= domain has to align with the RFC5322.From domain. In relaxed mode the Organizational Domains have to match. In strict mode the full domains have to match, exactly.
So there are two ways to fail. Your signature can be broken — bad key, mangled body, expired. That’s a verification failure. Or your signature can be flawless and the d= simply belongs to someone else. That’s an alignment failure. They produce the same DMARC outcome and they have nothing to do with each other, which is why “DKIM failed” is such a useless sentence on its own. Failed which way?
This is the part everyone gets wrong: people audit whether DKIM verifies. DMARC doesn’t care whether it verifies. DMARC cares whether it verifies and aligns. A pass on the first and a miss on the second is a fail.
Why your ESP is the usual culprit
Walk through the most common real setup. You send marketing mail from news@yourcompany.com through some platform. Out of the box, that platform signs with its own domain — d=esp-provider.net or a shared signing domain it owns. The signature is genuine. It verifies on every receiver. And it aligns with absolutely nothing, because esp-provider.net is not the Organizational Domain of yourcompany.com.
Your DMARC report comes back dkim=fail. You open the raw headers, see dkim=pass header.d=esp-provider.net, and conclude the report is broken. It isn’t. The header answers “did a signature verify?” The report answers “did an aligned signature verify?” Same word, different question — and you’ve been reading the easy one.
The fix is almost always the same and almost never obvious from the error: stop letting the ESP sign as itself. Every serious platform supports custom DKIM — you publish a couple of CNAMEs so the selector lives under your domain, and now the mail is signed with d=yourcompany.com. Same cryptography. Now it aligns. The thing you change isn’t the signature’s validity. It’s whose name is on it.
Relaxed mode is quietly saving you
Most people who have working DMARC have it working because of relaxed alignment, and they don’t know relaxed alignment exists.
Relaxed (the default for both adkim and aspf) matches on the Organizational Domain, computed through the Public Suffix List. So d=mail.yourcompany.com aligns with From: yourcompany.com, because both roll up to the same organizational domain. Strict would reject that — strict wants the labels identical. The PSL is also why yourcompany.co.uk is the org domain and co.uk is not, which matters the moment you operate under a multi-label suffix.
If you flip adkim=s without understanding this, you will break perfectly good mail that signs from a subdomain. Most people who turn on strict alignment are solving a problem they don’t have and creating one they do.
The reason alignment is worth the trouble
Here’s the argument for caring, and it’s the part the checklists skip.
Under DMARC you only need one aligned mechanism to pass — SPF or DKIM. So why obsess over DKIM? Because SPF is the one that dies the instant anyone forwards your mail. SPF authenticates the connecting IP against the envelope sender, and a forwarder is, by definition, a connecting IP you never authorized. First hop, SPF is gone.
DKIM survives a plain forward. The signature travels with the message; if the body and signed headers arrive intact, it still verifies, and if it was aligned when it left, it’s still aligned when it lands. For any mail that gets forwarded — and legitimate mail gets forwarded constantly — aligned DKIM is the only thing standing between a real message and the spam folder. If you fix exactly one mechanism this quarter, fix DKIM alignment, not SPF. SPF can’t be made to survive the thing that breaks most legitimate mail.
The exception worth naming: mailing lists. A list manager rewrites the Subject:, staples a footer onto the body, maybe rewrites From: entirely. That changes the bytes DKIM signed, so the body hash no longer matches and the signature fails verification — not alignment. Don’t debug an alignment problem when a footer ate your body hash. That whole class of breakage is why ARC (RFC 8617) exists: it lets an intermediary vouch for the authentication results it saw before it mangled the message. ARC is a patch on a patch, but it’s an honest one.
The one-line version
A valid DKIM signature answers “who signed this?” DMARC asks a different question — “is the signer allowed to speak for the From: address?” — and the answer to the second is not contained in the first.
If you only remember one thing: dkim=pass is not the finish line. header.d has to be yours.