A step-by-step guide to publishing SPF, DKIM and DMARC records, with the exact record values to paste, the DNS steps for Cloudflare and Route 53, and fixes for the SPF 10-lookup limit, a p=none policy, and duplicate records.
Key takeaways
SPF, DKIM and DMARC are three DNS records that together tell receiving mail servers whether a message claiming to come from your domain really did. Get them right and your mail is trusted. Get them wrong — or leave them half-configured, which is the more common state — and your legitimate mail gets filtered while anyone can still forge your domain.
This guide is the practical half. If you want the concepts first, read the deliverability guide. Here we publish each record, then work through the specific errors these three standards produce most often. You can check what your domain looks like right now with our free SPF, DKIM and DMARC checker — it reports each record, counts your SPF lookups, and links back to the section here that fixes what it finds.
All three are TXT records, added wherever your domain's DNS is hosted — usually your registrar, Cloudflare, or Route 53. Two details trip people up:
_dmarc.example.com), others want just the subdomain part (_dmarc) and append the domain for you. Cloudflare and most registrars take the short form; entering the full name in a short-form field produces _dmarc.example.com.example.com, which resolves to nothing.SPF is a single TXT record at the root of your domain listing every service allowed to send mail as you. Create a TXT record with the name @ (or your bare domain) and a value that starts with v=spf1, names each sender, and ends with an all mechanism:
v=spf1 include:_spf.google.com include:amazonses.com ~all
The include values come from each provider, and you need one for every service that sends on your behalf — your mailbox provider, your marketing platform, your transactional sender, your helpdesk, your invoicing tool. The most common values:
| Sender | Include |
|---|---|
| Google Workspace | include:_spf.google.com |
| Microsoft 365 | include:spf.protection.outlook.com |
| Amazon SES | include:amazonses.com |
| SendGrid | include:sendgrid.net |
| Mailgun | include:mailgun.org |
| Postmark | include:spf.mtasv.net |
Forgetting a sender is the usual cause of "our invoices go to spam" — the invoicing tool was never in the record.
A domain may publish exactly one SPF record. Two is not additive: receivers see more than one v=spf1 TXT record, treat it as a permanent error, and SPF fails entirely — including for the senders that were listed correctly. This happens when a new tool's setup wizard adds its own record rather than editing yours.
The fix is to merge them. Take the mechanisms from both records, put them in one, and delete the other:
Before (broken):
v=spf1 include:_spf.google.com ~all
v=spf1 include:sendgrid.net ~all
After:
v=spf1 include:_spf.google.com include:sendgrid.net ~all
The final mechanism tells receivers what to do about servers that are not on your list, and the qualifier in front of all is what carries the meaning:
| Ending | Meaning | When to use it |
|---|---|---|
-all | Fail — reject unlisted servers | Once you are confident every sender is listed |
~all | Softfail — accept but mark suspicious | While you are still rolling out |
?all | Neutral — no opinion | Effectively the same as having no policy |
+all | Pass — anyone may send as you | Never |
+all deserves a specific warning: it authorises the entire internet to send mail as your domain, which is worse than publishing no SPF record at all, because it actively vouches for forgeries. It usually appears by accident, copied from an example. If you find it on your domain, change it to ~all today.
Leaving the all mechanism off entirely has a similar effect to ?all: the check returns neutral, and you have told receivers nothing.
This is the error that catches out otherwise careful senders, because the record looks perfectly correct. SPF permits a maximum of 10 DNS-querying mechanisms per evaluation: include, a, mx, ptr, exists and redirect. Crucially they are counted recursively — every include pulls in the lookups inside the record it points at. Three includes can easily cost nine lookups.
Go over the limit and the result is permerror. SPF does not degrade gracefully; it stops passing, and if your DMARC policy relies on SPF alignment, DMARC starts failing with it. Nothing in your DNS looks wrong, which is why this one takes so long to diagnose. Our checker follows your includes and gives you the count.
Three ways down, in the order worth trying:
ip4: and ip6: ranges it currently resolves to. This works — IP mechanisms cost no lookups — but it is a maintenance commitment: when the provider changes its IPs, your record is silently wrong. Only flatten what you must, and re-check it on a schedule, or use a service that maintains the flattening for you.DKIM signs each outgoing message with a private key held by your sending platform, and publishes the matching public key in your DNS. Receivers verify the signature and learn that the message genuinely came from your domain and was not altered in transit. Because the signature travels with the message, DKIM survives forwarding in cases where SPF does not.
You do not write a DKIM record by hand. Your sending platform generates the key pair and gives you a record to publish:
The record lives at {selector}._domainkey.yourdomain.com, where the selector is a label the platform chooses — google, selector1, s1, pm and so on. Each sending service publishes its own selector, so a domain sending through three platforms will have three DKIM records, and that is correct.
One consequence worth knowing: because selectors are arbitrary and DNS gives no way to list them, no tool can prove a domain has no DKIM key. Ours probes the selectors the major platforms use and tells you when it finds none, but a custom selector is invisible to it. If you know DKIM is signing, check the platform's own status screen rather than trusting any external checker on this one point.
DMARC ties the other two together. It publishes a policy telling receivers what to do with mail that fails authentication, and it adds alignment: the requirement that the domain which passed SPF or DKIM matches the domain your recipient sees in the From field. Alignment is the part that actually stops spoofing, because a forger can pass SPF for their own domain while displaying yours.
Create a TXT record with the name _dmarc and this value:
v=DMARC1; p=none; rua=mailto:[email protected]
Start at p=none deliberately. It changes nothing about delivery, which means you cannot break your own mail while you find out what is actually sending as your domain.
The rua tag is where receivers send daily aggregate reports: XML summaries of every source sending as your domain and whether it passed. Without it, p=none accomplishes nothing at all — you have asked receivers to report to nobody, and you will move to enforcement blind.
Those reports are how you discover the invoicing tool nobody documented. Raw DMARC XML is unpleasant to read, so point rua at a mailbox you can stand to check and, if you are enforcing at any scale, use a report parser.
This is where most domains stop, and a domain sitting at p=none indefinitely is not protected. The policy says: tell me about mail that fails, then deliver it anyway. A forger's message still lands in the inbox.
The path to enforcement:
p=none for two to four weeks and read the aggregate reports. You are looking for your own senders that fail — every one of them is mail that will start being quarantined when you tighten.p=quarantine once the only failures left are ones you do not recognise. Failing mail now goes to spam rather than the inbox.p=reject after a few more clean weeks. Failing mail is now refused outright, which is the point of the exercise.Do not skip from none to reject on a domain whose reports you have not read. That is how a company discovers, on a Monday morning, that its payroll notifications were being sent by a service nobody had added to SPF.
pct= applies your policy to only a percentage of failing mail — pct=25 means three quarters of forgeries are still delivered. It exists as a ramp: set p=quarantine; pct=10, watch, raise it. The failure mode is forgetting it. A record reading p=reject; pct=10 looks like strict enforcement in a summary and is doing almost nothing. Once your reports are clean, remove the tag or set pct=100.
One more tag worth setting deliberately: sp= controls the policy for subdomains. If you do not set it, subdomains inherit your main policy — which is usually what you want, since invoices.yourdomain.com is a favourite of spoofers. A record with p=reject; sp=none leaves every subdomain unprotected.
For a domain sending through Google Workspace and Amazon SES, fully rolled out:
Name: @
Type: TXT
Value: v=spf1 include:_spf.google.com include:amazonses.com -all
Name: google._domainkey
Type: TXT
Value: v=DKIM1; k=rsa; p=MIIBIjANBgkq... (generated by Google)
Name: _dmarc
Type: TXT
Value: v=DMARC1; p=reject; rua=mailto:[email protected]
Run your own domain through the checker to confirm all three resolve as you expect.
Worth being clear about the boundary. Authentication decides whether receivers trust mail that claims to be from you. It has no bearing on whether the addresses you send to exist. A perfectly authenticated campaign to a stale list still hard bounces, and those bounces still damage the sender reputation your DNS records were protecting.
The two work together: authenticate your domain so receivers trust you, and verify your addresses so you are not sending to mailboxes that were closed two years ago.
Hussam Abd is the founder and engineer behind BounceShift. He has been building software since 2013 — full-stack engineering, then founding and leading engineering teams, including CTO and founder roles at MSAAQ and product and front-end work on customer-support tooling at Hsoub. He built BounceShift to make email verification that is accurate and honest about what it cannot know.
Related terms
Get 100 free validations to test our service. No credit card required.
Start free trial