SPF, DKIM, and DMARC: The Complete Guide to Email Authentication
A working engineer's guide to the three DNS-based protocols that decide whether your mail lands in the inbox or gets forged by someone else. Real records, real failure modes, and how to actually read a DMARC report.
EvilMail TeamJuly 1, 202613 min read
# SPF, DKIM, and DMARC: The Complete Guide to Email Authentication
Email was designed in an era when the people connected to the network mostly trusted each other. The protocol that moves your mail, SMTP, will happily let any server claim to be sending on behalf of any domain. Nothing in the original design stops me from connecting to a mail server and announcing that I'm sending as [email protected]. That single design gap is the reason phishing works, and it's the reason three separate authentication layers had to be bolted on afterward.
SPF, DKIM, and DMARC are those layers. They live entirely in DNS, they cost nothing to deploy, and getting them wrong is one of the most common reasons legitimate mail ends up in the spam folder. If you run a domain that sends email, you need all three, configured to agree with each other. This guide walks through what each one actually does, shows you real records, and explains the failure modes that trip up even experienced admins.
The problem all three protocols are solving
When a receiving mail server gets a message, it wants to answer two questions:
1. Did this message really come from a server authorized to send for this domain? 2. Has anything in the message been tampered with in transit?
SPF answers the first question by checking the sending server's IP address. DKIM answers the second by cryptographically signing parts of the message. DMARC ties both together, adds the concept of *alignment*, and — crucially — tells receivers what to do when a message fails. None of the three is sufficient alone. DMARC without SPF or DKIM has nothing to check. SPF and DKIM without DMARC produce signals that receivers are free to ignore.
Here's the short version before we go deep:
| Protocol | What it checks | Where it lives | What it protects | Breaks on | | --- | --- | --- | --- | --- | | SPF | Sending server IP | TXT record on domain | The envelope sender (Return-Path) | Forwarding | | DKIM | Cryptographic signature | TXT record on selector subdomain | Message headers + body integrity | Body modification | | DMARC | Alignment of SPF/DKIM with the visible From | TXT record on _dmarc subdomain | The From address users actually see | Nothing, if SPF/DKIM set up right |
SPF: authorizing servers by IP
Sender Policy Framework is a published list of the servers allowed to send mail for your domain. You put it in a TXT record at the root of your domain, and a receiving server looks it up, compares it against the IP that actually connected, and gets a pass or fail.
A record looks like this:
example.com. IN TXT "v=spf1 include:_spf.google.com include:sendgrid.net ip4:212.22.69.210 -all"
Reading it left to right:
v=spf1 — the version tag. Always first, always this value.
include:_spf.google.com — authorize whatever Google's own SPF record authorizes. This is how you delegate to a provider without hardcoding their IPs.
ip4:212.22.69.210 — a literal IP you're allowing, typically your own mail server.
-all — the enforcement mechanism. Everything not matched above fails hard.
That trailing qualifier matters more than anything else in the record:
-all (hard fail): mail from unlisted servers should be rejected. This is what you want once you're confident your record is complete.
~all (soft fail): treat as suspicious but usually still accept. A reasonable place to start.
+all: authorize the entire internet. Never do this. It's the same as having no SPF at all.
The 10-lookup limit that quietly breaks SPF
SPF has a hard rule that catches almost everyone eventually: a record may trigger no more than 10 DNS lookups during evaluation. Every include, a, mx, ptr, and redirect counts. Chain together Google, a CRM, a marketing platform, a helpdesk, and a payment provider, and each of *their* records may contain nested includes. Blow past ten and your SPF returns permerror — which many receivers treat as a fail.
You can't see this by eyeballing the record; you have to count recursively. If you're near the limit, flatten the record (replace includes with the resolved IP ranges, at the cost of manual maintenance) or drop senders you no longer use.
SPF's Achilles heel: forwarding
SPF checks the *envelope* sender (the Return-Path, also called MAIL FROM), not the address a human sees. When someone forwards your message — a mailing list, a .forward file, a corporate gateway — the forwarding server connects from *its* IP, which isn't in your SPF record. SPF fails, even though the mail is legitimate. This is by design and it's why SPF alone can never be the whole story. DKIM survives forwarding; SPF often doesn't.
DKIM: signing the message itself
DomainKeys Identified Mail attaches a digital signature to each outgoing message. Your mail server holds a private key; the matching public key is published in DNS. A receiver fetches the public key, recomputes the signature over the same headers and body, and confirms nothing was altered.
The public key record lives on a *selector* subdomain, which lets you rotate keys and run several in parallel:
selector1._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7...IDAQAB"
Every signed message carries a header pointing at that record:
d= — the signing domain. This is what DMARC will check for alignment.
s= — the selector, telling receivers which DNS record holds the public key.
h= — the list of headers covered by the signature. Anything not listed here isn't protected.
bh= — a hash of the message body.
b= — the actual signature.
Because DKIM signs content rather than checking an IP, it survives forwarding. A mailing list can relay your message across the internet and, as long as it doesn't modify the signed headers or body, the signature still verifies. This is exactly the gap SPF leaves open.
Where DKIM breaks
DKIM fails when something rewrites the signed content after signing. Mailing lists that append an unsubscribe footer to the body are the classic offender — the body hash no longer matches. Some security appliances rewrite links or inject disclaimers and produce the same result. Use relaxed canonicalization (c=relaxed/relaxed) rather than simple; it tolerates trivial whitespace changes that would otherwise break an otherwise-valid signature. And use at least a 2048-bit key — 1024-bit keys are considered weak, though the record's p= value gets long enough that some DNS UIs split it awkwardly.
DMARC: alignment, policy, and reporting
Here's the subtle problem SPF and DKIM share: neither actually checks the From address your users see. SPF checks the envelope sender. DKIM checks whatever domain it feels like putting in d=. An attacker can pass both while displaying From: paypal.com in the client — because the authenticated domain and the visible domain are different fields.
DMARC closes that hole with alignment. It requires that the domain authenticated by SPF or DKIM *matches* the domain in the visible From header. It also lets you publish a policy telling receivers what to do on failure, and it makes them send you reports.
p=reject — the policy. Options are none (monitor only), quarantine (spam folder), and reject (bounce outright).
rua= — where to send aggregate reports (daily rollups). This is the one that matters most.
ruf= — where to send forensic/failure reports (per-message). Many providers no longer send these for privacy reasons.
adkim / aspf — alignment mode, s for strict (exact domain match) or r for relaxed (subdomains allowed). Relaxed is almost always what you want.
pct=100 — apply the policy to this percentage of mail. Useful for gradual rollout.
fo=1 — request a report if *any* underlying check fails.
Alignment, concretely
A message passes DMARC if it passes either SPF or DKIM, *and* that passing mechanism is aligned. Two independent paths to success:
SPF alignment: the envelope-from domain matches the From domain, and SPF passes.
DKIM alignment: the d= signing domain matches the From domain, and the signature verifies.
Because you only need one, DKIM alignment is your safety net when SPF breaks on forwarding. This is why a robust setup signs *and* publishes SPF: forwarding kills SPF but DKIM carries the message through, and DMARC still passes.
Roll out with `p=none` first
Do not publish p=reject on day one. You will reject your own mail. Start at p=none, which changes nothing about delivery but starts the flow of aggregate reports. Watch them for a few weeks until you can account for every legitimate source, tighten to quarantine, watch again, and only then move to reject. pct= lets you ramp the enforced fraction gradually if you're nervous.
How to actually read a DMARC report
Aggregate reports arrive at your rua address as gzipped XML, one per receiver per day. They're not meant to be read by hand, but you should understand the structure because it tells you exactly which senders are failing and why.
Read it like this: 42 messages came from 198.51.100.24, claiming to be from example.com. SPF passed but DKIM failed. Now you investigate: is 198.51.100.24 a server you recognize? If it's your marketing platform, the DKIM failure means it isn't signing — fix that. If it's an IP you've never seen sending as your domain, that's a spoofer, and DMARC is working exactly as intended.
The pattern you're hunting for is legitimate sources that fail. Every one of those is a message that will get quarantined or rejected once you enforce. Reports let you find and fix them *before* you turn enforcement on. Feed the XML into any DMARC report parser — there are free and paid dashboards — so you're reading trends instead of raw markup.
Common mistakes, ranked by how often they bite
Two SPF records on one domain. You're allowed exactly one v=spf1 TXT record. Publish two and the result is permerror. Merge them into a single record with multiple includes.
Blowing the 10-lookup limit. Silent until it isn't. Count your includes recursively.
`p=reject` before monitoring. The fastest way to embargo your own newsletter.
Forgetting a sending source. Your transactional mail is signed, but the invoicing system, the CRM, and the support desk each send too — and each needs to be in SPF and ideally signing DKIM.
DKIM key too short or never rotated. Use 2048-bit and rotate periodically using selectors.
Strict alignment by accident.adkim=s will fail a message signed by mail.example.com when the From is example.com. Use relaxed unless you have a specific reason.
Putting it together
A correctly authenticated domain has three records working in concert: an SPF record listing every server that legitimately sends, DKIM signing on each of those senders with the public key in DNS, and a DMARC record that requires alignment and tells the world what to do with forgeries. When they agree, a receiver can trust that a message claiming to be from you actually is — and reject the ones that aren't.
This is also why throwaway and disposable addresses are a legitimately safer way to interact with services you don't trust: the authentication burden and the reputation risk stay on their infrastructure, not yours. A service like EvilMail handles the receiving side so you never expose your primary domain to whatever a sketchy signup form does next.
Set these up once, keep an eye on the DMARC reports, and you close the single biggest hole in email's forty-year-old design. The protocols aren't glamorous, but they're the difference between an inbox and a spam folder — and between your brand and a phisher's.