A practical guide to SPF, DKIM, DMARC and Sieve filtering for anyone who owns a domain
The Problem Nobody Fixes
Everyone complains about spam. It clogs inboxes, wastes time, and fools just enough people that it stays wildly profitable. The math works for spammers: send a billion emails, fool 0.01% of the people who get them, and you've got 100,000 victims.
Here's the uncomfortable part: we have tools that make a real dent in this. They've existed for years, they aren't hard to set up, and most domain owners, including plenty who should know better, still haven't bothered.
That isn't a technical limitation. It's a collective choice.
Two different things are going on here, and this article keeps them apart:
- Stopping people from impersonating your domain. That's what SPF, DKIM and DMARC do.
- Cutting down the spam that lands in your inbox. That's what filtering does (the Sieve section near the end).
This article covers what each tool is, why it works, why so few people use it, and exactly how to set it up. The examples use Cloudflare for DNS, but the records are the same anywhere.
Who this is for: personal domains and small setups with a single outbound mail provider. Big companies with lots of senders have extra complications, and I'll point those out, but the main audience is someone running one domain and one inbox who has no good excuse for weak defaults.
What Authentication Does (and Doesn't Do)
Email authentication is not a spam filter. It does one thing: it proves that mail claiming to come from your domain really did.
That matters because a huge amount of spam and phishing relies on spoofing, forging the sender address so the message looks like it came from someone you trust. Authentication shuts that door.
It doesn't stop a spammer who registers their own domain, sets up authentication properly and sends from there. A perfectly authenticated spam email is still spam. That's the job of content filtering, which we'll get to. And when filtering doesn't catch it, you can fight back and automatically report spam.
It also doesn't stop look-alike domains (examp1e.com). That takes monitoring or buying the obvious look-alikes yourself, and it's outside the scope here.
If You Only Read One Section
Three DNS records, plus one habit: check it works before you turn on enforcement.
Remember the split from above. These records stop other people from forging your address. They don't reduce the spam you receive. (The Sieve section does that.) Both are worth doing.
Copy-paste defaults
Replace example.com with your domain. This assumes one outbound mail provider.
| Record | Name | Value |
|---|---|---|
| SPF (TXT) | @ |
v=spf1 include:<provider's include> -all |
| DKIM (TXT or CNAME) | <selector>._domainkey |
Exactly what your provider gives you (use a 2048-bit key if offered) |
| DMARC (TXT) | _dmarc |
v=DMARC1; p=reject; sp=reject; adkim=r; aspf=r; rua=mailto:dmarc@example.com |
Common SPF includes (confirm against your provider's docs, since these change):
| Provider | SPF include |
|---|---|
| Google Workspace | include:_spf.google.com |
| Microsoft 365 | include:spf.protection.outlook.com |
| Proton Mail | include:_spf.protonmail.ch |
| Fastmail | include:spf.messagingengine.com |
| Amazon SES | include:amazonses.com |
A domain can only have one SPF record. If you already have one, merge the includes into it instead of adding a second.
Order of operations
- Publish SPF and turn on DKIM signing at your provider.
- Send yourself a message at a Gmail or Outlook account. Open "View Original" and confirm you see
spf=passanddkim=pass. - Publish DMARC. Not sure about every service that sends as your domain (a scanner, a billing tool, a newsletter)? Start with
p=none, read the reports for two weeks, then move top=reject. One provider, and you verified it in step 2? Go straight top=reject. - Re-check with mail-tester.com or MXToolbox.
A domain that never sends mail
SPF: v=spf1 -all
DMARC: v=DMARC1; p=reject; sp=reject;
MX: 0 . (a "null MX": tells senders you accept no mail)
No DKIM is needed, because nothing is being signed.
Default inbound filter
If your mail host supports Sieve, use the Junk-first script in the filtering section below, and run it for a week before changing anything to discard.
The Three Pillars
SPF: the guest list
SPF is a list of the servers allowed to send mail for your domain. When a message claims to be from you, the receiving server checks your list. Not on the list, not authorized.
It lives in a DNS TXT record on your root domain:
v=spf1 include:_spf.example.com -all
The part that matters is the ending. -all means "anyone not on the list fails". ~all ("softfail") means "be suspicious, but deliver it anyway", which tells receivers your rule is optional.
For a personal or single-provider domain, aim for -all. Big providers often default to ~all because broken outbound mail generates support tickets, and that's a fair stance while you're still working out what sends as you. But it should be a waypoint, not a destination. If you run one domain out of one inbox, you don't have that excuse.
The usual objection is that forwarding breaks SPF: when someone forwards your mail, the forwarding server's IP isn't on your list. True, but that's a reason to also set up DKIM and DMARC, not a reason to stay on softfail forever.
Common SPF mistakes:
- Using
~allinstead of-all - Using
mxto authorize senders. It's convenient, but it ties who can send to where you receive, so changing your MX records can silently change your SPF. - Going over the 10-DNS-lookup limit by including too many third parties
Nerd corner: how the three fit together. Each one answers a different question:
- SPF: "Is the server that delivered this allowed to send mail for the domain in the envelope (bounce) address?" It's about the delivery path.
- DKIM: "Was this message signed by a domain, and is it unchanged since?" It's about the message itself.
- DMARC: "Does at least one of those passing checks belong to the same domain as the
From:line the reader sees, and if not, what should happen?" It's about the visible identity and the policy.The key point: SPF and DKIM can each pass for a domain that has nothing to do with the
From:address. A spammer can pass both on their own domain and put yours in theFrom:line. Neither check stops that on its own. DMARC does, by requiring that the passing check be aligned with the From domain.Why have both SPF and DKIM? Because they fail in opposite situations. Forwarding breaks SPF (the forwarder's IP isn't on your list) but usually leaves DKIM intact. A mailing list that edits the body breaks DKIM but not SPF. DMARC only needs one aligned pass, so between them most legitimate mail gets through.
That also explains
-all. Once DMARC is enforcing, an SPF failure alone isn't what decides the outcome, and the RFC advises receivers not to reject on SPF alone when a DMARC policy exists.-allstill helps receivers that don't evaluate DMARC, and it's the whole defence for a domain that never sends mail. A receiver that hard-rejects on-allbefore checking DKIM can still bounce forwarded mail, which is why forwarders that rewrite the envelope sender (SRS) matter.
DKIM: the tamper-proof seal
DKIM puts a cryptographic signature on every message you send. The receiver looks up your public key in DNS and checks the signature. If anyone changed the message on the way, the seal breaks.
Its big advantage is forwarding. SPF fails when mail is forwarded, but the DKIM signature travels with the message, so it generally still passes. That's what makes a strict DMARC policy workable.
It isn't a 100% guarantee. Forwarders that rewrite subjects or inject banners into the body will still break the signature. But modern forwarding preserves DKIM far better than old mailing-list software did.
Your mail provider does the signing. Your only job is to publish the DNS record they give you. Use 2048-bit keys where offered.
Nerd corner. DKIM is two halves: your server holds a private key and signs outgoing mail, and you publish the matching public key at a selector name like
cf2024-1._domainkey.example.com. Large providers also increasingly use ARC (Authenticated Received Chain), which preserves authentication results across forwarding hops and mailing lists. It doesn't replace DKIM or DMARC. For most personal domains it just works in the background, but the name is worth knowing if you're debugging forwarding.
DMARC: the policy that ties it together
SPF and DKIM each check something. DMARC is where you tell receivers what to do when a message fails.
It also checks alignment: the domain that passed SPF or DKIM has to match the From: address people actually see. That closes the loophole where a spammer passes SPF on their own domain while putting yours in the From line.
v=DMARC1; p=reject; sp=reject; adkim=r; aspf=r;
-
p=reject: ask receivers to reject mail that fails -
sp=reject: same policy for subdomains -
adkim=r/aspf=r: relaxed alignment, meaning a subdomain of your domain counts as a match
Why p=reject won't wreck forwarded mail: DMARC needs either SPF or DKIM to align, not both. Forwarding breaks SPF but not DKIM, so DMARC still passes.
The policy ladder:
-
p=none: monitoring only. Fine for a short stretch while you set up. -
p=quarantine: send failures to spam. A reasonable middle step. -
p=reject: the goal. Most major receivers honor it; a few apply local overrides.
Most domains that publish DMARC at all are sitting on p=none, which is the equivalent of putting up a "no trespassing" sign without a fence.
Nerd corner. Relaxed alignment is the ecosystem default and is what you want unless you're sure otherwise. Strict alignment (
adkim=s; aspf=s) is a deliberate hardening step that breaks things when your provider sends through a tracking or bounce subdomain, which email-marketing platforms and Salesforce-style senders commonly do. Only go strict if your provider sends from your exact root domain.
Setting It Up
Where your provider hands you the values
| Provider | Setup Documentation |
|---|---|
| Google Workspace | Admin Console → Apps → Gmail → Authenticate Email |
| Microsoft 365 | Security Portal → Email Auth Settings |
| Proton Mail | Settings → Domain Names → Review |
| GoDaddy Email | Email & Office Dashboard → Manage |
| Amazon SES | SES Console → Identities → Easy DKIM |
| Cloudflare + any provider | Add records in DNS dashboard → DNS → Add Record |
For DKIM, copy the string your provider gives you exactly. Don't type it by hand and don't tidy up the tags. Providers differ (Microsoft 365 uses CNAMEs, some add extra tags), and the exact string they give you is the one that works.
Before you add DMARC, make sure SPF exists, DKIM is signing outbound mail, and you've seen both pass on a real message.
What it looks like in Cloudflare
The mail provider doesn't matter here. These records are the same wherever your mail is hosted:
| Type | Name | Content |
|---|---|---|
| MX | @ | mail.example.com |
| TXT | @ | v=spf1 include:_spf.example.com -all |
| TXT | selector._domainkey | v=DKIM1; h=sha256; k=rsa; p=[key from your provider] |
| TXT | _dmarc | v=DMARC1; p=reject; sp=reject; adkim=r; aspf=r; |
DMARC reports
Add a rua tag and receivers will email you daily summaries of who's sending as your domain and whether any of it fails:
v=DMARC1; p=reject; sp=reject; adkim=r; aspf=r; rua=mailto:dmarc@example.com;
The reports arrive as zipped XML, which is charitably not user-friendly. Free tools like Postmark's DMARC monitor or Dmarcian turn them into something readable. They're most useful while you're rolling things out; after that, they're noise unless you're watching them.
There's also a second kind, forensic reports (ruf), but many receivers have stopped sending them for privacy reasons. Don't worry if they never show up. Focus on the aggregate (rua) reports.
Check that it worked
DNS changes usually take minutes, occasionally a few hours depending on caching. Then:
- MXToolbox: checks all three records
- mail-tester.com: send a test email, get a score
- DMARC Analyzer: validates SPF specifically
One more step worth doing: send a real email to a personal Gmail or Outlook account and open the raw headers ("View Original" in Gmail, "View Message Source" in Outlook). Confirm dmarc=pass and that the domains match yours. mail-tester is an isolated test; real alignment problems sometimes only show up in actual delivery.
Changing an existing record, for example swapping a ~all SPF for -all? Lower its TTL to 300 seconds a day or so beforehand, make the change, then raise it again. Otherwise resolvers may keep serving the old value for the old TTL. (A brand-new record has nothing stale cached, so this only applies to edits.)
Why Most Domains Don't Do This
It's worth understanding, because the setup genuinely isn't hard.
The defaults are wrong. Most mail providers generate an SPF record ending in ~all. Most DNS providers never prompt you for DMARC. Most registrars don't mention authentication at all. The easy path leads to a weak setup.
The coordination excuse is overused. "If I enforce strictly I might break forwarded mail, so I'll wait for everyone else." Everyone waits, and the result is collective inaction dressed up as caution.
The pain falls on other people. If your domain gets spoofed in a phishing campaign, the victims suffer, not you. The cost of doing nothing is pushed onto others, which is exactly why voluntary adoption has been slow.
The big players haven't used all their leverage. Google and Microsoft handle most of the world's email. Their 2024 requirement that bulk senders use DKIM and DMARC moved more domains into compliance than a decade of documentation. But it stopped at p=none, monitoring only. Requiring enforcement would change the ecosystem overnight.
A word on Cloudflare. They can't safely force strict defaults without breaking legacy setups: scanner-to-email gadgets, forgotten SaaS integrations, automated billing systems. But they could add validation wizards that nudge people off abandoned p=none policies and warn about broken SPF. That gap is a product decision, not a technical limit. If you use Cloudflare, tell them.
Advanced filtering ahead. Everything above is baseline domain security for any domain owner, whatever mail client or provider you use. Everything below is optional, for people whose mail host exposes Sieve scripting. If yours doesn't, or you'd rather not edit server-side scripts, the authentication stack above is enough.
Filtering What Lands in Your Inbox
Authentication protects your outbound identity. For inbound spam that passes authentication, because the spammer owns a real domain, you need filtering.
Sieve is a mail-filtering language that runs on the server, before mail reaches any of your devices, so the filtering works whichever client you use. Many hosts support it without advertising it, so check your provider's docs or ask support.
Start with Junk, not discard. Send suspect messages to Junk with fileinto "Junk" and run that way for at least a week. Only switch a rule to discard once it has produced no false positives and you're sure you'd never want to see what it catches. discard can't be undone: no copy, no bounce, no recovery. It can silently swallow an invoice from a vendor with broken email setup.
Here's a practical script. Everything goes to Junk first, which you can recover from:
require ["fileinto", "regex"];
# rule:[[JUNK] Provider spam flag]
if header :is "x-junk-flag" "YES"
{
fileinto "Junk";
stop;
}
# rule:[[JUNK] Rspamd score >= 6.0]
# The ^ anchor means negative scores (trusted mail) never match.
if header :regex "x-rspamd-score" "^[+]?([6-9]|[1-9][0-9]+)\\."
{
fileinto "Junk";
stop;
}
# rule:[[JUNK] Broken DKIM with no passing signature]
# The "not dkim=pass" guard keeps mail that has one failing and one passing
# signature (e.g. mailing lists) out of Junk.
if allof (
anyof (
header :contains "authentication-results" "dkim=fail",
header :contains "authentication-results" "dkim=invalid"
),
not header :contains "authentication-results" "dkim=pass"
)
{
fileinto "Junk";
stop;
}
# OPTIONAL: exempt your own forwarding domains from the next rule.
# Uncomment and replace example.com. SRS-forwarded mail often has
# spf=fail and no DKIM. Only do this if you see it landing in Junk.
# if address :domain :is "from" ["example.com", "example.net"]
# {
# keep;
# stop;
# }
# rule:[[JUNK] No DKIM and SPF failed]
if allof (
header :contains "authentication-results" "dkim=none",
header :regex "authentication-results" "spf=(fail|softfail|permerror)"
)
{
fileinto "Junk";
stop;
}
# rule:[[JUNK] DMARC unknown]
# "unknown" is not a standard RFC 8601 result; it depends on how your host
# writes the header. Check Junk to confirm it catches real spam, or delete it.
if header :contains "authentication-results" "dmarc=unknown"
{
fileinto "Junk";
stop;
}
Three notes on the script:
-
The DKIM guard. A message can carry two signatures, for instance a mailing list that rewrites the body and adds its own. The original fails and the list's passes, so
dkim=failanddkim=passboth appear. Without the guard, that legitimate mail goes to Junk. -
The forwarding exemption. If other domains of yours forward into this mailbox, SRS rewriting can produce
spf=failwith no DKIM, which the "No DKIM and SPF failed" rule would junk. Check Junk for your own domains before turning the exemption on. -
dmarc=unknownis host-specific. The standard result values arenone,pass,fail,policy,neutral,temperrorandpermerror.unknownis a custom string your host may write. It's a weak signal on its own, so keep the rule only if you've seen it catch real spam.
What the DKIM results mean
-
dkim=none: no signature at all. Combined with an SPF failure, that's a strong spam signal. -
dkim=fail: a signature was there but didn't verify, so the message changed after it was signed. Legitimate causes include old mailing-list software, corporate gateways that append disclaimers, "safe links" URL rewriting and MIME bugs. If you're not on mailing lists or corporate forwarding chains, it's a strong spam signal. -
dkim=pass: the signature checked out. That proves the message wasn't tampered with, but says nothing about whether it's spam.
The spam-score problem
Rspamd (the spam scorer many hosts use) stamps each message with a score in X-Rspamd-Score. Trusted mail can score negative (like -2.1), and Sieve has no clean, universal way to compare decimals and negatives. Here are three ways to write the rule.
Tier 1: Regex (precise). The script above. The ^ anchor means a negative score like -6.5 can never match. The catch is that the regex extension is optional and not every host supports it.
Tier 2: Glob patterns (works everywhere). No extensions needed, and because :matches anchors the whole value, a leading - can never match:
if header :matches "x-rspamd-score" ["6.*","7.*","8.*","9.*","1?.*","2?.*","3?.*"]
{
fileinto "Junk";
stop;
}
This covers scores up to 39.x, which is plenty. Don't write it as :contains "6.": that matches -6.5, so your most trusted mail gets caught.
Tier 3: Relational comparison (only with a guard). This is the tidiest to read, but there's a trap. Under i;ascii-numeric (RFC 4790), a value that doesn't start with a digit, like -1.5, counts as larger than any number. An unguarded ge "6" therefore matches negative scores, the opposite of what you want. Guard it:
require ["fileinto", "relational", "comparator-i;ascii-numeric"];
if allof (
not header :matches "x-rspamd-score" "-*",
header :value "ge" :comparator "i;ascii-numeric" "x-rspamd-score" "6"
)
{
fileinto "Junk";
stop;
}
Recommendation: try Tier 1; if your host doesn't support regex, use Tier 2; prefer Tier 2 over Tier 3.
Junk or discard?
Both are silent. Neither tells the sender anything, so neither "confirms your address exists". The real difference is whether you can recover a mistake: Junk lets you rescue a false positive, discard doesn't. (Bouncing is the one option that actually hurts people, since spam usually forges the return address and the bounce lands in an innocent person's inbox. Never bounce.) Plenty of people never need to go beyond Junk.
Two things to check before you trust any of this
Header formats vary. These rules match the wording of Authentication-Results as it appears from one particular host. Providers format it differently, and some rewrite or strip upstream results. Send yourself a few test messages and look at the raw headers your provider produces. Your patterns need to match what your host writes, not what the RFC says it should.
Thresholds vary. A score of 6.0 is a reasonable starting point, but scores differ between systems. Watch for false positives and adjust.
What Sieve won't catch
Sophisticated spam from authenticated domains with low scores: mail that passes every technical check because the sender set things up correctly and keeps the content just under detection thresholds. Apple Mail, Outlook and tools like SpamSieve use content analysis and learning to catch more of it. Server-side Sieve and client-side filtering work best as two layers.
When Softer Settings Are Legitimate
This article pushes for the strongest settings: SPF -all, p=reject, DKIM signing. For a home user or hobbyist on a single provider, those are right and there's no good reason to hold back.
But there are real situations where something softer is a genuine constraint rather than neglect:
Mailing lists. Software like Mailman often rewrites headers or strips DKIM signatures when it redistributes mail, which breaks DMARC alignment. Organizations running busy lists sometimes stay on p=quarantine or p=none so list delivery doesn't break.
Complicated forwarding. If a third-party service forwards your domain's mail onward, its IP isn't in your SPF record and DKIM may not survive the trip. That needs careful handling before you enforce strictly.
Big organizations with many senders. A company using Salesforce, Marketo, a ticketing system, a payment processor and an HR platform, all sending as the same domain, has to audit every one of them before enforcing. Going to p=reject first means silently dropping legitimate business mail. p=none during the audit is correct. The trouble is that the audit often never ends.
Domains mid-migration. p=none is the right starting point when you genuinely don't know every sender yet. The ladder (none, quarantine, reject) exists for a reason. The mistake is treating p=none as the destination.
None of this applies to a home user or hobbyist with one domain and one provider. If you send through exactly one service and your reports look clean, there's no technical justification for softfail SPF or p=none DMARC. The exceptions are real, but they belong to a kind of organizational complexity most readers of this article don't have.
The Ceiling
With SPF -all, DKIM, DMARC p=reject and Sieve filtering in place, you've reached the ceiling of what email authentication can do. You'll still get some spam: the sophisticated kind, from properly authenticated domains with low scores. That's not a failure of your setup. It's the unsolved remainder of a problem the whole industry is still working on.
What you've done:
- Made straightforward spoofing of your domain fail at receivers that honor DMARC
- Filtered most inbound spam out of your inbox
- Configured your domain better than most commercial organizations bother to
I applied this configuration to a real personal domain in a single afternoon. The filtering rules, not the DNS records, are what cut inbound spam: roughly 160 fewer messages a day reached the inbox, and that only counts what got as far as my server. What still gets through is the sophisticated, fully authenticated kind.
The tools exist. The setup takes an afternoon. And most people still won't do it.
Want to fight back? The Counteroffensive: Automated Spam Reporting with Spamhaus
Top comments (0)