SPF, DKIM and DMARC, explained by what a receiver actually does
Three DNS records, one verdict. What each one proves, the order a receiving server applies them, and why a message can pass SPF and still fail DMARC.
On this page
SPF, DKIM and DMARC are three DNS records that together let a receiving mail server decide whether a message really came from your domain. Here is SPF, DKIM and DMARC explained in the order a receiver applies them: SPF checks the connecting server’s IP address against a list you publish, DKIM checks a cryptographic signature carried inside the message, and DMARC checks whether either of those results belongs to the same domain your reader sees in the From header, then tells the receiver what to do when neither does.
Why there are three records and not one
Each record closes a hole the previous one leaves open. SPF authorises machines, so it says nothing about the content and breaks the moment a message is forwarded. DKIM authorises the message, so it survives forwarding, but it says nothing about which domain the reader sees. DMARC is the only one of the three that looks at the From header at all, and the only one that carries a policy telling the receiver to reject or quarantine anything.
Read alone, each is close to useless against spoofing. Read together, in that order, they are what every large mailbox provider now expects.
SPF: which servers may send for your domain
SPF is a TXT record at your domain root listing the hosts allowed to send mail for it:
example.com. IN TXT "v=spf1 include:_spf.example-esp.com ip4:203.0.113.7 ~all"
The critical detail: the receiver evaluates that record against the envelope sender, the
address in the SMTP MAIL FROM command that ends up in Return-Path. That is not the From:
address your reader sees. Someone spoofing you can put anything in From: and still collect an
SPF pass on a domain they control.
Two operational traps. First, RFC 7208 limits evaluation to 10 DNS lookups, and every include:
counts. Chain three providers together and you go over, which produces a permerror, not a pass.
Second, ~all (softfail) and -all (hardfail) are treated very differently by receivers, and a
record ending in ?all tells the world you do not care either way.
DKIM: a signature the message carries
DKIM signs selected headers plus the body with a private key and publishes the matching public
key in DNS at <selector>._domainkey.<domain>. The receiver reads the DKIM-Signature header,
fetches that key, and recomputes the hash.
Two tags in that header matter. d= is the signing domain, the identity DKIM actually asserts.
s= is the selector, which is what lets you run several keys at once and rotate them without an
outage. When you configure a new sending platform, the thing to verify is not “is DKIM on” but
“what is in d=”. Plenty of platforms sign with their own domain by default, which passes DKIM
and does nothing at all for you.
DMARC: alignment plus a policy
The DMARC record lives at _dmarc.example.com:
_dmarc.example.com. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com"
DMARC passes when SPF or DKIM passes and the domain that passed is aligned with the domain
in the From header. RFC 7489 calls this identifier alignment, and it is the entire point of the
protocol. Relaxed alignment (the default) accepts an organisational domain match, so
mail.example.com aligns with example.com. Strict alignment (aspf=s, adkim=s) demands an
exact match.
The p= tag takes none, quarantine or reject. Deploy at p=none with an rua address,
read the aggregate reports until every legitimate source is authenticating in alignment, then
tighten. There is no prize for reaching reject early and a real cost for getting there blind.
The failure that catches everyone: an unaligned pass
The most common confusion in this area looks like a contradiction. The headers say spf=pass,
the record is valid, and DMARC still fails.
What happened is that your provider used its own bounce domain in the Return-Path. SPF passed,
correctly, for bounce.provider.net. Your From header says example.com. Those are different
organisational domains, so DMARC discards the SPF result and falls back to DKIM. If the provider
also signs with its own d=, there is nothing left to align and the message fails.
The fix is a DKIM key with d=example.com, or a custom Return-Path subdomain, or both. This is
also why “we checked our DNS with an online tool” proves less than it seems: the tool sees your
records, not the mail your application actually emits. That distinction is the one worth applying
when you compare email testing tools by what they actually check,
since a DNS lookup and a delivered message answer different questions.
Reading the verdict in Authentication-Results
The receiving server stamps its findings into a header:
Authentication-Results: mx.example.net;
spf=pass smtp.mailfrom=bounce.provider.net;
dkim=pass header.d=example.com;
dmarc=pass header.from=example.com
Trust only the header added by your own boundary. Anything stamped further upstream is unverified text and can be forged by whoever sent the message.
Test all three before your users do
Because this is a property of real received mail, the honest test is to receive real mail. An
email testing API with SPF, DKIM and DMARC assertions hands you a
disposable inbox, takes your application’s mail, and reports what the receiving hop concluded.
Every message exposes authResults with a value for spf, dkim and dmarc, and you can
assert on them directly:
const run = await client.assertEmail(inbox.id, [
{ type: 'auth', check: 'spf', expect: 'pass' },
{ type: 'auth', check: 'dkim', expect: 'pass' },
{ type: 'auth', check: 'dmarc', expect: 'pass' },
]);
console.log(run.passed, run.results);
If a mechanism produced no result at all, the assertion fails rather than quietly passing, which
is what you want in CI: “no DKIM result” and “DKIM pass” are not the same finding. The same three
checks run through the assert endpoint if you would rather call HTTP directly, and the
WordPress deliverability test runs them against wp_mail output without any
code at all.
Wire it into the suite that already covers your password reset flow. A signup mail that arrives fast, reads correctly and fails DMARC is still a broken mail. Catch it on the branch, not in a spam folder.
Disclosure: InboxDome publishes this blog and makes the products it mentions. We describe what they do rather than promise results.
Key takeaways
- SPF authorises sending servers by IP, DKIM signs the message itself, and DMARC decides what to do when neither result lines up with the From domain.
- SPF is checked against the envelope sender (Return-Path), not the From address your reader sees, which is why SPF alone proves very little.
- DMARC needs a passing SPF or DKIM result that is aligned with the From domain. An unaligned pass counts as a fail.
- RFC 7208 caps SPF at 10 DNS lookups. Going over is a permerror, and a permerror is not a pass.
- A DNS record checker cannot tell you whether the mail your app actually sends is signed correctly. Only receiving a real message can.
Sources
- RFC 7208: Sender Policy Framework (SPF) version 1 — Section 4.6.4 sets the 10 DNS lookup limit; section 2.6.7 defines permerror.
- RFC 6376: DomainKeys Identified Mail (DKIM) Signatures — Section 3.5 defines the DKIM-Signature header, including the d= and s= tags.
- RFC 7489: Domain-based Message Authentication, Reporting and Conformance (DMARC) — Section 3.1 defines identifier alignment; section 6.3 defines the p= policy tag.
- Google email sender guidelines — Gmail's authentication requirements for all senders and for bulk senders.
Links last checked August 27, 2026.
Frequently asked questions
Do I need all three, or is one enough?
For DMARC to produce a verdict you need at least one of SPF or DKIM to pass in alignment with your From domain, so in practice you publish all three. Google's sender guidelines ask every sender for SPF or DKIM, and ask senders of 5,000 or more messages a day to Gmail for SPF, DKIM and DMARC together.
Why does my mail pass SPF but fail DMARC?
Almost always alignment. If your email service provider uses its own bounce domain in the Return-Path, SPF passes for that domain rather than yours, so DMARC ignores the result. Signing with DKIM using a d= value on your own domain fixes it.
Should I publish p=reject straight away?
No. Start at p=none with an rua address, read the aggregate reports until every legitimate sending source is authenticating in alignment, then move to quarantine and finally reject. Jumping to reject silently kills mail from systems you forgot about.
Does DKIM survive forwarding?
Usually yes, because the signature travels with the message. It breaks when something modifies a signed header or the body, which is what mailing lists that append a footer or rewrite the subject do. SPF breaks on plain forwarding because the forwarding server's IP is not in your record.