guides
SPF, DKIM, and DMARC setup: the three DNS records that get mail delivered
May 20, 2026

Your mail lands in spam, you Google the cause, and you are staring at three acronyms. SPF, DKIM, and DMARC are not magic. They are three DNS records that prove the mail is really yours, and you can set all three in about twenty minutes.
This guide is tightly scoped to those three records: what each one proves, the exact syntax, how to add them on Cloudflare DNS, and how to confirm they actually pass at the receiver. Everything around the records (warmup, reputation, bounce handling, list hygiene) is a separate topic covered in how warmup, reputation, and bounces work beyond the records. Set the records here; build the reputation there. If the surrounding jargon is the blocker, every email authentication acronym in one sentence each is a quick primer to read alongside this.
What do SPF, DKIM, and DMARC actually do?
In one paragraph: SPF says which servers are allowed to send mail for your domain. DKIM cryptographically signs each message so a receiver can prove the body and key headers were not altered in transit. DMARC ties the first two to your visible From address, tells receivers what to do when they fail (nothing, quarantine, or reject), and asks them to send you reports. SPF and DKIM each prove one thing; DMARC is the policy and feedback layer that makes them enforceable. You need all three.
Here is the mental split:
SPF → "These IPs are allowed to send as me." (authorized senders)
DKIM → "This message is signed and untampered." (tamper-proof signature)
DMARC → "If SPF/DKIM don't align with From, do X, (policy + reporting)
and email me a report either way."
The rest of this guide is one section per record, then how to set and verify them on Cloudflare.
SPF: who is allowed to send as you
SPF (Sender Policy Framework) is a single TXT record at your root domain that lists the servers permitted to send mail using your domain in the envelope sender. A receiver checks the connecting server's IP against this list. The record looks like this:
Type: TXT
Name: @ (the root, e.g. acme.com)
Value: v=spf1 include:_spf.yourprovider.com ~all
Reading it left to right: v=spf1 declares the version, each include: pulls in another domain's authorized IPs (your sending provider publishes one), and ~all is the catch-all for everything not listed.
Three things trip people up:
There can be only one SPF record. A domain must publish exactly one v=spf1 TXT record. If you have two (a common result of pasting a new provider's record without removing the old one), receivers see a permerror and SPF fails outright. Merge multiple senders into a single record with multiple include: mechanisms:
v=spf1 include:_spf.provider-a.com include:_spf.provider-b.com ~all
The 10-DNS-lookup limit. SPF evaluation may not exceed 10 DNS lookups, and each include:, a, mx, and redirect counts (recursively). Chain a few providers that each include: their own sub-records and you blow the budget; SPF then returns permerror and silently fails. Keep your include: list short, and prefer providers that flatten their record.
~all versus -all. ~all (softfail) marks unlisted senders as suspicious but usually still accepts them. -all (hardfail) tells receivers to reject anything not authorized. Start with ~all while you confirm you have caught every legitimate sender (your app, your marketing tool, your helpdesk), then tighten to -all. Switching to -all before you have listed every sender will hardfail your own mail.
DKIM: a signature receivers can verify
DKIM (DomainKeys Identified Mail) attaches a cryptographic signature to every message you send. The receiver fetches your public key from DNS and verifies that the signed headers and body were not modified. If anything changed in transit, the signature fails. This is what proves a message is genuinely yours and intact, regardless of which server relayed it.
DKIM has two halves. The private key lives with whatever signs your mail (your sending service). The public key lives in a DNS TXT record at a name built from a selector:
Type: TXT
Name: tinbox._domainkey (selector is "tinbox")
Value: v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
When you send, the signing service writes a DKIM-Signature header that names the selector and domain:
DKIM-Signature: v=1; a=rsa-sha256; d=acme.com; s=tinbox; ...
The receiver reads s=tinbox and d=acme.com, looks up the public key at tinbox._domainkey.acme.com, and verifies the signature.
This is the number one silent failure in all of email authentication: the selector in your signature must match the selector you published. If your service signs with s=tinbox but you published the key at mail._domainkey, DNS resolves to nothing, the signature cannot be verified, and DKIM fails with no obvious error. Whenever DKIM fails, check the s= value in a real DKIM-Signature header against the TXT record name you actually created. They have to be the same word.
DMARC: the policy that ties it together
DMARC (Domain-based Message Authentication, Reporting, and Conformance) is the record that makes SPF and DKIM mean something for your From address. Without DMARC, an attacker can pass SPF and DKIM for their own domain while spoofing your From line, and receivers have no instruction to stop them. DMARC requires that a passing SPF or DKIM check align with the domain in your visible From, and it tells receivers what to do when nothing aligns.
The record is one TXT at a fixed name:
Type: TXT
Name: _dmarc (i.e. _dmarc.acme.com)
Value: v=DMARC1; p=none; rua=mailto:dmarc@acme.com; adkim=r; aspf=r
The fields:
p=is the policy:none(monitor only, do nothing),quarantine(send failures to spam), orreject(refuse them outright).rua=is where aggregate XML reports are sent. This is your visibility into who is sending as you.adkim/aspfset alignment mode,rfor relaxed (a subdomain counts) orsfor strict. Relaxed is the right default.
Always start at p=none. This publishes the record and turns on reporting without affecting delivery. Let reports come in for a week or two and read them: confirm your real mail (app, marketing, helpdesk) passes and aligns, find any legitimate sender you forgot. Only once your own mail is clean do you tighten the policy:
p=none → p=quarantine → p=reject
Jumping straight to p=reject is how people block their own password-reset emails. The progression exists so you discover misconfigurations from a report instead of from an angry customer.
How do I set SPF, DKIM, and DMARC on Cloudflare DNS?
Add three TXT records in your Cloudflare zone's DNS settings. In the Cloudflare dashboard, open your domain, go to DNS → Records, and click Add record three times. Each is type TXT:
| Type | Name | Content |
|---|---|---|
| TXT | @ | v=spf1 include:_spf.yourprovider.com ~all |
| TXT | tinbox._domainkey | v=DKIM1; k=rsa; p=MIIBIjANBgkq... |
| TXT | _dmarc | v=DMARC1; p=none; rua=mailto:dmarc@acme.com |
Set the proxy status to DNS only (the grey cloud); TXT records are not proxied anyway. TTL can stay automatic.
You do not have to guess the values. When you scaffold a Tinbox stack, npx tinbox init <domain> prints the exact records for your domain, with the real DKIM public key it generated, ready to paste:
$ npx tinbox init acme.com
Add these three DNS records in your Cloudflare zone (DNS → Records):
SPF TXT @ v=spf1 include:_spf.acme.com ~all
DKIM TXT tinbox._domainkey v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFA...
DMARC TXT _dmarc v=DMARC1; p=none; rua=mailto:dmarc@acme.com
These are yours to add in the dashboard. Tinbox does not edit DNS in your zone.
That last line is the honest part: Tinbox generates and prints the records, but adding them is your action in the Cloudflare dashboard. The CLI does not reach into your zone and create them. If you also want inbound mail to land in the inbox, that is the Email Routing side, covered in the inbound side, where DMARC reports arrive.
How do I verify SPF, DKIM, and DMARC are actually passing?
Verify two ways: confirm the records resolve in DNS, then confirm a real message passes at the receiver. The resolve check is a plain dig you can run from any terminal.
Check SPF:
dig +short TXT acme.com
# "v=spf1 include:_spf.acme.com ~all"
Check the DKIM selector (this is where mismatches surface):
dig +short TXT tinbox._domainkey.acme.com
# "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."
Check DMARC:
dig +short TXT _dmarc.acme.com
# "v=DMARC1; p=none; rua=mailto:dmarc@acme.com"
If all three return the values you added, DNS is correct. The second half is confirming an actual send passes alignment, and the simplest real-world test is to send a message to a Gmail account, open it, and use Show original: it prints SPF: PASS, DKIM: PASS, and DMARC: PASS with the domains it checked.
Inside a Tinbox deploy, npx tinbox doctor gives you the read-only status picture of the rest of the stack (D1, R2, queues, secrets, and whether inbound Email Routing is pointed at your worker) so you can confirm the sending side is wired before you trust the auth records end to end. doctor never mutates anything; it just reports what is set up and what still needs doing.
Failure then fix: a DKIM selector mismatch
Here is the most common failure caught by dig. You published a DKIM record but your test mail still fails DKIM. You check the selector your service is actually signing with:
# The DKIM-Signature header on a real message says s=tinbox, but you published
# the key under the wrong name. The lookup returns nothing:
dig +short TXT mail._domainkey.acme.com
# (empty)
Empty output means the record does not exist at that name. The signature says s=tinbox, so the public key must live at tinbox._domainkey, not mail._domainkey. Add (or rename) the record to match the selector, wait for the TTL, and re-check:
dig +short TXT tinbox._domainkey.acme.com
# "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."
Now the selector in the signature and the published record agree, and DKIM passes. The same shape of error (a dig returning empty or the wrong value) catches a missing SPF record or a typo'd _dmarc name.
What are the most common SPF, DKIM, and DMARC mistakes?
Four mistakes account for most authentication failures:
- Selector mismatch. The
s=in yourDKIM-Signaturedoes not match the TXT record name you published. Fix: make the published name<selector>._domainkeyuse the exact selector your service signs with. - Two SPF records. A second
v=spf1TXT triggers apermerrorand SPF fails entirely. Fix: keep exactly one SPF record and merge senders with multipleinclude:mechanisms. -alltoo early. Hardfailing before you have listed every legitimate sender bounces your own mail. Fix: stay on~alluntil reports confirm all your senders are covered, then tighten.- DMARC
rejectbefore monitoring. Jumping top=rejectblocks any mail you forgot to align, including your own transactional sends. Fix: start atp=none, read the reports, then move toquarantineandrejectin steps.
The common thread: change one thing, verify with dig or a Gmail Show original, then move on. Authentication is unforgiving about exact strings and forgiving about pace.
Where to go next
These three records prove your mail is yours; they do not make receivers trust you. A correctly authenticated message from a cold domain can still land in spam, because placement is a reputation verdict, not an authentication flag. Once the records pass, the work shifts to warming the domain, watching bounces, and keeping the list clean, all of which is covered in the full deliverability guide on warmup, reputation, and bounces. And the moment you start sending from the edge, these records are what stand between your transactional mail and the spam folder, which is exactly why they matter the second you send from a Worker.
SPF, DKIM, and DMARC are three of the records a sending domain needs, but not all of them; the seven DNS records every sending domain needs is the full list. And if you send any real volume to consumer inboxes, these records are now mandatory under the Gmail and Yahoo bulk sender checklist.
If you would rather have the exact records generated for your domain instead of assembling them by hand, init prints these records for you on first run, with a real DKIM key, ready to paste into Cloudflare DNS.
Deploy this yourself: npx tinbox init <domain> prints your SPF, DKIM, and DMARC records on the first run, and npx tinbox doctor confirms the rest of the stack is wired. See the docs for the full setup walkthrough.
Frequently asked questions
Do I need all three of SPF, DKIM, and DMARC?
Yes, for any domain that sends mail to the major providers. SPF and DKIM each prove a piece of authenticity, but neither is bound to the visible From address on its own, so spoofing is still possible with them alone. DMARC is the record that requires SPF or DKIM to align with your From domain and tells receivers to act on failures. Gmail and Yahoo's bulk sender rules require all three for anyone sending volume, and a published DMARC record (even at p=none) is now effectively mandatory.
What is a DKIM selector?
A DKIM selector is a label that points receivers to the specific public key for a signature. When you sign a message, the DKIM-Signature header includes s=selector and d=yourdomain.com, and the receiver looks up the public key at selector._domainkey.yourdomain.com. The selector lets you publish multiple keys (for rotation or different senders) under one domain. The single most common DKIM failure is a selector in your signature that does not match a TXT record you actually published.
Why is my email still going to spam after setting up SPF, DKIM, and DMARC?
Because authentication is necessary but not sufficient. SPF, DKIM, and DMARC prove the mail is genuinely from you; they say nothing about whether receivers trust you. A brand-new domain has no reputation, so it can pass all three checks and still land in spam until it earns trust through consistent sending, low bounce rates, and recipient engagement. Records are the entry ticket. Reputation and warmup are a separate, ongoing discipline.
What is the difference between ~all and -all in an SPF record?
Both appear at the end of an SPF record and define what happens to mail from servers not listed. ~all is softfail: mail from an unlisted server is marked suspicious but usually still accepted. -all is hardfail: receivers are told to reject anything not explicitly authorized. Start with ~all while you confirm you have listed every legitimate sender, then move to -all once you are certain. Going to -all too early bounces your own mail if you forgot a sender.
What does DMARC alignment mean?
Alignment means the domain that passed SPF or DKIM matches the domain in your visible From address. DMARC only counts a pass if at least one of SPF or DKIM both passes and aligns with the From domain. This is what stops spoofing: an attacker can pass SPF for their own domain, but that domain will not align with your From, so DMARC fails. By default DKIM uses relaxed alignment (a subdomain counts), which is usually what you want.


