SPF, DKIM and DMARC: The Complete Email Authentication Setup Guide
Step-by-step instructions for publishing SPF, DKIM and DMARC records correctly, understanding alignment, reading reports, and moving safely to p=reject.
Step-by-step instructions for publishing SPF, DKIM and DMARC records correctly, understanding alignment, reading reports, and moving safely to p=reject.
Email was designed in an era when nobody expected the sender address to be a lie. Nothing in the base SMTP protocol stops a machine from claiming to be billing@yourbank.com. Every anti-spoofing control in use today is a layer added on top, and all three of the important ones live in DNS: SPF, DKIM and DMARC.
Since February 2024, Gmail and Yahoo require bulk senders to authenticate with SPF and DKIM, publish a DMARC policy, and keep spam complaints under 0.3 percent. Microsoft followed with similar requirements for Outlook.com in 2025. Authentication is no longer a best practice you get to schedule for next quarter — it is the price of entry.
This guide walks through each record, how they interact, and the exact order to deploy them without breaking legitimate mail.
| Record | Question it answers | Where it lives |
|---|---|---|
| SPF | Is this IP allowed to send for this domain? | TXT at the domain root |
| DKIM | Was this message altered, and does the signature match a key the domain published? | TXT at selector._domainkey.domain |
| DMARC | What should a receiver do when SPF and DKIM fail, and where do I send reports? | TXT at _dmarc.domain |
SPF validates the *envelope* sender. DKIM validates the *message*. DMARC ties either of them back to the visible From header — which is the only address a human ever sees. That last point is why DMARC matters: without it, an attacker can pass SPF for their own domain while displaying your brand in the From field.
An SPF record is a single TXT record at your domain root listing the servers permitted to send on your behalf.
v=spf1 include:_spf.google.com include:sendgrid.net ip4:203.0.113.20 -allBreaking that down:
v=spf1 — version marker, always first.include: — delegates to another domain's SPF record; use the value your provider documents.ip4: / ip6: — literal addresses for servers you run yourself.-all — hard fail: anything not listed is unauthorised.### The rules people break
One SPF record per domain. Two v=spf1 TXT records is a permanent error and causes every check to fail. Merge them into one.
Ten DNS lookup limit. Each include, a, mx, ptr and redirect costs a lookup, and those nest. Exceed ten and receivers return permerror, which DMARC treats as a failure. Count them before you publish; if you are near the limit, flatten rarely-changing includes into literal IP ranges or use an SPF-flattening service.
Prefer `-all` over `~all`. Soft fail (~all) was useful while you were testing. Once DMARC is enforcing, hard fail is the honest signal.
Never use `ptr`. It is deprecated, slow, and many receivers ignore it entirely.
### Validating SPF
Query the record and count lookups:
dig +short TXT yourdomain.com
dig +short TXT _spf.google.comIf the returned string is split across multiple quoted chunks, that is normal — DNS strings cap at 255 characters and resolvers concatenate them.
DKIM adds a cryptographic signature header to every outgoing message. The receiver fetches your public key from DNS and verifies that the signed headers and body were not modified in transit.
Your provider generates the key pair. You publish the public half:
Name: s1._domainkey.yourdomain.com
Type: TXT
Value: v=DKIM1; k=rsa; p=MIIBIjANBgkqhki...The s1 part is the selector. It lets you publish several keys at once, which is what makes rotation painless: publish a new selector, switch signing to it, then remove the old record a week later.
### DKIM rules that matter
d= tag in the signature says sendgrid.net, DKIM passes but DMARC alignment fails.Verify a published key:
dig +short TXT s1._domainkey.yourdomain.comThen send a test message to a mailbox you control and inspect the raw headers for dkim=pass.
This is the concept that trips up almost everyone.
DMARC does not simply ask "did SPF pass?" It asks "did SPF pass for a domain that aligns with the From header?"
d= value in the DKIM signature.DMARC passes if *either* mechanism passes and is aligned. This is why a message can show spf=pass in its headers and still fail DMARC: the SPF check passed for the provider's bounce domain, not for yours.
Relaxed alignment (the default) accepts subdomains — mail.yourdomain.com aligns with yourdomain.com. Strict alignment requires an exact match. Start relaxed.
The fix for SPF misalignment is usually a custom return path or custom bounce domain in your provider's dashboard, which adds a CNAME under your domain so the envelope sender becomes bounces.yourdomain.com.
Never start at enforcement. Start here:
Name: _dmarc.yourdomain.com
Type: TXT
Value: v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com; fo=1Tags you should know:
p= — policy: none, quarantine, or reject.sp= — a different policy for subdomains.rua= — where aggregate XML reports are sent (daily, one per receiver).ruf= — forensic reports; most receivers no longer send these for privacy reasons.pct= — apply the policy to a percentage of mail, for gradual rollout.adkim= / aspf= — r for relaxed (default) or s for strict.fo=1 — request a report whenever any mechanism fails.Leave this in place for two to four weeks. You are looking for one thing: a complete inventory of every system that sends mail as your domain. Almost every organisation discovers a forgotten one — a helpdesk tool, a billing system, an old marketing platform, a monitoring script on a server nobody owns.
Aggregate reports arrive as gzipped XML. Each record looks roughly like this:
<record>
<row>
<source_ip>203.0.113.20</source_ip>
<count>412</count>
<policy_evaluated>
<disposition>none</disposition>
<dkim>pass</dkim>
<spf>fail</spf>
</policy_evaluated>
</row>
</record>Reading raw XML for more than a handful of sources is impractical; use a DMARC reporting service to aggregate them. What you are working towards is a report where every IP with meaningful volume shows dkim=pass and spf=pass, and the remaining failures are either spoofing attempts or forwarded mail.
Forwarding is the most common benign failure. When a recipient auto-forwards your message, SPF breaks because the forwarding server is not in your record, but DKIM survives if the body was not modified. This is precisely the scenario DMARC's either/or rule was designed for.
Once your legitimate sources all pass, tighten in stages:
p=quarantine; pct=10 — one week.p=quarantine; pct=50 — one week.p=quarantine; pct=100 — two weeks.p=reject; pct=100.At each step, watch the reports and your support inbox. If a legitimate source starts failing, roll back one step rather than pushing forward.
Also protect domains you never send from. A parked domain should carry:
v=spf1 -all
v=DMARC1; p=reject;Attackers actively look for unprotected sibling domains.
BIMI displays your logo next to authenticated messages in supporting clients. It requires p=quarantine or p=reject, a square SVG Tiny PS logo, and for Gmail a Verified Mark Certificate. It is a marketing win, not a deliverability one, but it is a strong incentive to finish DMARC enforcement.
MTA-STS tells receiving servers that mail to your domain must use TLS with a valid certificate, closing the downgrade-attack gap in opportunistic STARTTLS. It needs a DNS record plus a policy file served over HTTPS at mta-sts.yourdomain.com.
TLS-RPT delivers daily reports about TLS negotiation failures for your domain — a cheap early-warning system for certificate problems.
| Symptom | Likely cause | Fix |
|---|---|---|
spf=pass, dmarc=fail | Return-Path is the provider's domain | Configure a custom bounce domain |
dkim=pass, dmarc=fail | Signature uses provider's d= | Enable domain-level DKIM signing |
spf=permerror | More than 10 DNS lookups | Flatten or remove includes |
| Intermittent DKIM failures | Message modified by a list or gateway | Expected; rely on SPF alignment |
| Reports show unknown IPs | Shadow IT or spoofing | Inventory, then enforce |
p=none with aggregate reporting.p=quarantine with pct ramping from 10 to 100.p=reject. Lock down parked domains. Consider MTA-STS and BIMI.DNS changes propagate unevenly and typos are silent. After each edit, query the record directly, send a test message to a mailbox you control, and read the raw headers. Confirming that your SMTP relay itself still authenticates correctly with SMTPTester at the same time closes the loop: transport working, identity proven, reports flowing.
DMARC applies to a domain and, by inheritance, its subdomains. That inheritance is a powerful tool once you understand it.
Publishing p=reject at yourdomain.com protects every subdomain automatically unless you override with sp=. This matters because attackers frequently spoof plausible subdomains such as billing.yourdomain.com that you never created. With a root policy of reject and no override, those attempts fail.
A common staged approach uses sp= to move at different speeds:
v=DMARC1; p=reject; sp=none; rua=mailto:dmarc@yourdomain.comThis enforces on the root domain, where you control the traffic, while you continue collecting data on subdomains that may still have unidentified senders. Once subdomain reports are clean, remove the sp tag so inheritance takes over.
Almost every organisation sends through services it does not think of as email systems: helpdesk platforms, invoicing tools, CRM systems, survey tools, applicant tracking systems, calendar invitations. Each one needs to authenticate as your domain.
For each vendor, ask two questions:
If a vendor supports neither, its mail cannot pass DMARC as your domain. Options are to send from a subdomain with a looser policy, to move to a vendor that does support authentication, or to accept that the mail will be quarantined once you enforce.
Keep a living register of these senders. It is the single most useful artifact for the whole project and the thing DMARC reports will otherwise force you to reconstruct from scratch.
Authentication failures are frequently DNS failures wearing a disguise.
s1._domainkey.yourdomain.com in a panel that already appends the zone produces s1._domainkey.yourdomain.com.yourdomain.com. Always verify with dig after publishing.* TXT record can interfere with lookups on some resolvers.After a completed rollout you should be able to state, with evidence:
p=reject and has been for at least a month without incident.That last point is what separates a project from a practice. Authentication is not a task you complete; it is a state you maintain as new tools are adopted and old ones retired.
Apply what you just learned. Free, no signup, results in seconds.
Open the tool →A complete guide to testing SMTP servers — online testing, telnet and openssl commands, TLS verification, authentication checks and a repeatable diagnostic workflow.
DeliverabilityHow bounces work, the difference between hard and soft bounces, how to parse DSN messages, build a suppression list, and keep bounce rates under control.