Most of the confusion around SPF, DKIM and DMARC comes down to one bad assumption: that an email has a single sender. It does not. A single email carries three separate domain identities, plus the IP address it was actually delivered from, and nothing requires any of them to match each other. They get checked by different mechanisms. A message can pass DMARC while two of the three point somewhere else entirely.

Take an invoice email sent through an ESP, the way most SaaS billing systems actually send mail. It is a constructed example, not a pulled-from-a-ticket incident, but it is the shape of ESP mail you will find in your own aggregate reports if you go looking.

The mail client shows a customer of acme-example.com an invoice from "Acme Billing". Underneath that, four values determine whether the message authenticates, and only one of them is a domain name a person would recognise:

Return-Path: <bounces+4471-acmecorp=acme-example.com@bounce.esp-example.net>
Received: from mta7.esp-example.net (mta7.esp-example.net [198.51.100.23])
From: "Acme Billing" <billing@acme-example.com>
DKIM-Signature: v=1; a=rsa-sha256; d=acme-example.com; s=s2; ...
Authentication-Results: mx.recipientdomain.com;
  spf=pass (sending IP 198.51.100.23) smtp.mailfrom=bounce.esp-example.net;
  dkim=pass header.d=acme-example.com header.s=s2;
  dmarc=pass (p=REJECT sp=REJECT) header.from=acme-example.com

Three domain identities and one supporting IP, one message:

  • Visible From: billing@acme-example.com, read from the From header. The only identity a human looks at. The only one DMARC cares about aligning to.
  • Envelope sender: bounce.esp-example.net, read from Return-Path (equivalently, the SMTP MAIL FROM). This is where bounces go, and it belongs to the ESP, not Acme. SPF checks this domain, not the visible From.
  • DKIM domain: acme-example.com, read from d= in the DKIM-Signature header. Acme configured a custom signing domain with its ESP rather than using the ESP's default. Separate, independent identity from both the envelope and the visible From.
  • Sending IP: 198.51.100.23, a shared IP on the ESP's platform, read from the Received header. Not a domain identity. It is the delivery source, and SPF's job is to check whether it is authorised to send for the envelope domain, not for acme-example.com at all.

Three separate checks run on that data, and none of them talk to each other until the very end.

SPF (RFC 7208) asks whether sending IP 198.51.100.23 is authorised to send for the envelope domain, bounce.esp-example.net. The ESP publishes an SPF record for that subdomain listing its own sending IPs, so the answer is yes. acme-example.com does not appear in this check anywhere, and DMARC uses the SPF-authenticated MAIL FROM identity for alignment, not the IP itself.

DKIM (RFC 6376) is a different question entirely: does the cryptographic signature in DKIM-Signature verify against the public key published at s2._domainkey.acme-example.com? Acme generated that keypair when it set up custom DKIM with its ESP, so it does. This check never looks at the envelope or the sending IP. It proves that the signed portions of the message, the header fields and body content actually covered by the signature, match what the holder of the private key for acme-example.com signed, and were not altered in transit. Whatever is excluded from the signature is not protected by it, which matters more than it sounds like it should once mailing lists and forwarders start rewriting headers.

DMARC (RFC 9989) is not a third check. It reads whichever of those two already produced a result and asks whether that result's domain aligns with the visible From domain, acme-example.com. SPF's authenticated MAIL FROM domain, bounce.esp-example.net, does not align with acme-example.com under any DMARC alignment mode, so the SPF leg fails despite SPF itself passing. DKIM's authenticated domain matches exactly, so the DKIM leg passes. One aligned leg is enough. That is why the header block above shows spf=pass next to a domain that is not Acme's, and dmarc=pass regardless.

flowchart TD
  M[Email] --> V[Visible From: acme-example.com]
  M --> E[Envelope sender: bounce.esp-example.net]
  M --> K[DKIM d=: acme-example.com]
  M --> I[Delivery IP: 198.51.100.23]
  E --> SPF{SPF authenticates envelope}
  I --> SPF
  SPF -->|pass, but envelope does not align to From| SPFALIGN[SPF leg: fails alignment]
  K --> DKIM{DKIM signature verifies}
  DKIM -->|pass, d= matches From exactly| DKIMALIGN[DKIM leg: passes alignment]
  SPFALIGN --> DMARC{Either leg aligned?}
  DKIMALIGN --> DMARC
  DMARC -->|yes, DKIM leg| PASS([DMARC pass])

The same message, evaluated on two independent legs. SPF passes but does not align. DKIM passes and aligns. DMARC needs only one.

One missing setting away from failing

Swap one detail and the outcome flips. Say Acme had never configured custom DKIM with its ESP. The message would carry the ESP's default signature, d=esp-example.net, instead. That signature still verifies. The ESP still legitimately sent the mail. But esp-example.net does not align with acme-example.com either, so now neither leg aligns, and an entirely genuine invoice fails DMARC. Nobody forged anything. It is a correctly authenticated message whose only aligned identity was never set up.

From the mail client's side it looks identical either way: a legitimate email, sent through a paid-for legitimate provider, landing in spam or getting rejected outright. The difference is entirely in d=, a field the sender's marketing team never sees and has no reason to know exists.

Diagnosing it means reading the DKIM-Signature header directly, or, in a DMARC aggregate report, the DKIM authentication result's domain field, checked against the organisational domain in identifiers/header_from. Not the sending IP. Not the envelope. Both of those will look fine regardless.

An aggregate report (RFC 9990) actually carries two separate answers, and mixing them up is where most people misread this. The auth_results element records the underlying SPF and DKIM authentication outcomes on their own terms, uninterpreted with respect to DMARC: auth_results/dkim/domain is the d= value that was validated, auth_results/spf/domain is the MAIL FROM domain that was checked. A separate policy_evaluated element records whether those results aligned with the visible From domain for DMARC purposes, in policy_evaluated/spf and policy_evaluated/dkim. Before custom DKIM is configured, a report record for that ESP's source IP can show both underlying checks passing in auth_results, SPF against the ESP's envelope domain, DKIM against the ESP's own signing domain, while policy_evaluated shows both spf and dkim as fail, because neither authenticated domain aligns with acme-example.com. Configure custom DKIM and auth_results/dkim/domain reads acme-example.com, policy_evaluated/dkim flips to pass, and the overall disposition follows it. The sending IP never changes. Neither does the volume. Only the signing domain does.

Both legs above were checked under DMARC's default relaxed alignment, where a subdomain counts as aligned with its parent organisational domain: a DKIM signature from d=mail.acme-example.com would still align with a From of acme-example.com. Set adkim=s or aspf=s in the DMARC record and alignment tightens to an exact match, and that same subdomain signature fails. Most senders should stay on relaxed unless they have a specific reason not to. Strict mode breaks legitimately structured mail, subdomains signing on behalf of the parent, more often than it stops anything, and an attacker who controls a lookalike domain is unaffected by either mode anyway.

If you run a domain that sends through any ESP, marketing platform or transactional mail service, this is worth fifteen minutes: check whether that provider is signing with your domain in d= or with its own. Signing with its own means your mail authenticates for the provider, not for you, and DMARC only passes if SPF happens to align instead, which plenty of ESP setups never achieve either. The fix is configuring custom DKIM, most ESPs call it "domain authentication" or "branded links and domains", so d= carries your domain. Do this before moving that sender's policy anywhere near p=reject. The sender inventory in a DMARC aggregate report will show you the sending source, but only the raw DKIM-Signature header, or the DKIM authentication result's domain field in the report itself, tells you which identity actually aligned.

See which of your senders actually align, and which only look like they do.

SenderLedger breaks down SPF and DKIM alignment per sending source, so you can find the ESP that never got custom DKIM before it costs you real mail.

Request access