SPF, DKIM, and DMARC explained for developers
Email authentication is three mechanisms that answer three different questions, and almost every guide blurs them together. Once you see which question each one answers, the DNS stops looking arbitrary and the failures become diagnosable.
5 min read
The three questions
| Mechanism | Question it answers | Where it lives |
|---|---|---|
| SPF | Was this message sent by a server allowed to send for this domain | A TXT record on the domain |
| DKIM | Has the message been altered since it was signed, and does the signature belong to this domain | A public key in DNS, a signature in the headers |
| DMARC | What should the receiver do when SPF and DKIM disagree with the visible From address | A TXT record at _dmarc.yourdomain.com |
SPF checks the envelope. DKIM checks the content. DMARC ties both back to the address a human actually sees, and tells the receiver what to do when they do not line up. That last part is the whole point, and it is called alignment.
SPF
SPF publishes a list of servers permitted to send for your domain. The receiver takes the envelope sender's domain, looks up its SPF record, and checks whether the connecting IP is authorised.
v=spf1 include:_spf.emails.sh ~allThree details that cause real outages:
One SPF record per domain. Two v=spf1 records is a permanent error and fails everything. If you already have one, add an include: to it rather than publishing a second.
Ten DNS lookups, maximum. Every include:, a, mx, ptr, and redirect costs a lookup, and nested includes count too. Exceed ten and the result is permerror, which most receivers treat as a failure. Adding a second provider is the classic way to cross the line. Count yours with the SPF lookup checker.
`~all` versus `-all`. A soft fail marks unauthorised mail as suspicious; a hard fail asks the receiver to reject it. Start soft, move to hard once your DMARC reports show nothing legitimate is failing.
SPF has a structural weakness: it breaks on forwarding. When a mailing list or a forwarding rule relays your message, the connecting IP is the forwarder's and SPF fails through no fault of yours. That is why DKIM matters more.
DKIM
DKIM signs selected headers and the body with a private key held by your sending provider. The public key is published in DNS at <selector>._domainkey.yourdomain.com. The receiver fetches it, verifies the signature, and learns two things at once: the message was authorised by whoever holds that key, and nothing covered by the signature has been modified in transit.
esh1._domainkey.acme.com. CNAME esh1.dkim.emails.sh.Providers publish DKIM as CNAMEs so they can rotate the underlying key without asking you to touch DNS again. Publish the selectors your provider gives you exactly as given. A single character wrong in a key produces a signature failure with no other symptom.
DKIM survives forwarding, as long as the forwarder does not modify the body. Some mailing lists append a footer, which breaks the signature, which is exactly why DMARC evaluates SPF and DKIM together and needs only one of them to pass.
DMARC and alignment
This is the concept everything else hangs on. SPF authenticates the envelope sender, which is often a bounce address at your provider's domain. DKIM authenticates the signing domain. Neither is necessarily the From: address the recipient reads, and the From: address is the one that gets spoofed.
DMARC requires that at least one of SPF or DKIM both pass and align with the visible From domain.
- SPF alignment: the envelope sender domain matches the From domain.
- DKIM alignment: the signing domain (
d=in the signature) matches the From domain.
Relaxed alignment, the default, accepts an organisational match, so mail.acme.com aligns with acme.com. Strict alignment requires an exact match.
This is why a message can pass SPF and still fail DMARC: SPF passed for the provider's bounce domain, not for yours. Sending from a subdomain you control, with DKIM signed by that same domain, resolves it.
_dmarc.acme.com. TXT "v=DMARC1; p=none; rua=mailto:dmarc@acme.com; pct=100; adkim=r; aspf=r"| Tag | Meaning |
|---|---|
p | Policy for failures: none, quarantine, or reject |
rua | Where aggregate reports are sent |
sp | Policy for subdomains, if different from p |
pct | Percentage of failing mail the policy applies to |
adkim, aspf | Alignment mode, r for relaxed or s for strict |
Build one without hand-editing the syntax using the DMARC record generator.
Getting to enforcement without breaking anything
p=none protects nobody. It only collects data. The point of publishing it is to move off it.
- Publish `p=none` with a `rua` address. You now receive aggregate reports from the major mailbox providers, usually daily.
- Read the reports for a few weeks. They are gzipped XML and unreadable by eye. Feed them to the DMARC report parser.
- Find every legitimate sender. Almost every organisation discovers something it forgot: a billing system, a CRM, a status page, a survey tool. Each one needs to be authenticated or moved.
- Move to `p=quarantine`, optionally with
pct=25first so only a quarter of failing mail is affected. Watch the reports again. - Move to `p=reject` once the reports show only genuine forgeries failing.
Rushing to p=reject before step three is how a company stops receiving its own invoices. There is no hurry; take the weeks.
Verify before you trust it
DNS caches, registrars mangle TXT values, and a copied record often carries a stray quote. Check the published state rather than what you meant to publish.
dig +short TXT acme.com
dig +short TXT _dmarc.acme.com
dig +short CNAME esh1._domainkey.acme.comThen send a real message and read the received headers, because that is the only place you see all three results at once.
Authentication-Results: mx.example.com;
spf=pass smtp.mailfrom=bounces.emails.sh;
dkim=pass header.d=acme.com;
dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=acme.comPaste a full header block into the email header analyzer if you want it broken down, or run the whole domain through the DMARC checker.
Why this is the first thing to fix
Authentication is not the only input to inbox placement, but it is the only one that is a hard gate. Major mailbox providers now require authenticated mail from bulk senders, and unauthenticated mail is increasingly rejected outright rather than filtered. Everything else about deliverability, content, engagement, list quality, is downstream of getting these records right.
Once they are correct and your mail still lands in spam, the cause is elsewhere. Start with why your password reset email goes to spam.
Questions
- Do I need all three of SPF, DKIM, and DMARC?
- Yes. DMARC requires SPF or DKIM to pass and align, so it does nothing without at least one of them, and SPF alone breaks on forwarding. Publish all three and treat DKIM as the one that must never be broken.
- Why does my email pass SPF but fail DMARC?
- Because SPF passed for the envelope sender domain, which is usually your provider's bounce domain, not the domain in your From header. DMARC requires alignment with the visible From address, which SPF alone often does not provide. DKIM signed by your own domain fixes it.
- How long should I stay on p=none?
- Long enough to read several weeks of aggregate reports and account for every legitimate sender. For a small app with one sending source that can be a fortnight. For an organisation with a marketing stack, a billing system, and a support tool, plan on a month or more.
- Can I have two SPF records?
- No. Two
v=spf1TXT records on the same name is a permanent error and causes SPF to fail entirely. Merge them into one record with multipleinclude:mechanisms, and watch the ten lookup limit while you do it.
Give your agent an address it can answer from.
Create an inbox