Module 5 · Lesson 3
SPF: Sender Policy Framework
⏱ 14 min read
SPF: Sender Policy Framework
Someone is spoofing your domain. They're sending spam that looks like it comes from @yourcompany.com. Recipients see your domain in the From field. Your reputation tanks. Support tickets start coming in. You didn't configure SPF.
SPF gives you a TXT record where you publish exactly which IP addresses and mail servers are authorized to send email as your domain. Receiving servers check this record and decide whether to accept, flag, or reject messages that fail the check.
How SPF Works
When a sending server delivers email, the receiving server checks the MAIL FROM address (the envelope sender, not the From: header you see in your email client) and looks up the SPF TXT record for that domain.
dig TXT example.com +short
If the sending server's IP matches the policy, the check passes. If it doesn't, the outcome depends on the all mechanism qualifier in your record.
Record Syntax
v=spf1 <mechanisms> <qualifier>all
A real example:
v=spf1 ip4:203.0.113.0/24 ip6:2001:db8::/32 include:_spf.google.com ~all
Breaking this down:
v=spf1 — Required. Identifies this as an SPF record.
ip4:203.0.113.0/24 — Allow this IPv4 range to send as this domain.
ip6:2001:db8::/32 — Allow this IPv6 range.
include:_spf.google.com — Look up the SPF record at _spf.google.com and add all its allowed IPs to this policy. Used when you send email through third parties like Google Workspace, SendGrid, Mailchimp.
~all — The catch-all qualifier.
The all Qualifier Options
This is where most misconfiguration happens:
| Qualifier | Meaning | Behavior |
|---|---|---|
+all | Anyone can send | Passing. Equivalent to no SPF. Don't use this. |
~all | Soft fail | Mark as suspicious, but don't reject. |
-all | Hard fail | Reject messages from unauthorized senders. |
?all | Neutral | No policy. Treated like no SPF. |
+all is catastrophically wrong. It tells receiving servers that any IP in the world is authorized to send as your domain. It's the same as publishing no SPF at all, except it's an explicit authorization. This shows up in shared hosting configurations, copy-paste from bad examples, and legacy setups nobody audited. If you see +all in your SPF record, fix it now.
-all vs ~all: Use -all in production once you're confident you've listed every legitimate sending source. Use ~all while you're auditing. The practical difference is that some receiving servers that would reject on -all will only flag and pass on ~all. Moving from ~all to -all is a signal that you've done the work to enumerate your senders.
Other Mechanisms
a — Allow the A record(s) of the domain itself.
mx — Allow the MX record(s) of the domain. Convenient for domains that both send and receive through the same server.
ptr — Reverse DNS check. Slow, deprecated, avoid it.
exists — Complex conditional logic. Rarely used.
redirect — Use another domain's SPF record entirely (replaces the whole record, unlike include).
The 10-Lookup Limit
SPF evaluation triggers DNS lookups. Each include:, a, mx, ptr, exists, and redirect mechanism counts against a limit of 10. If the evaluation exceeds 10 lookups, the result is permerror — a permanent error that typically causes the message to be treated as failed SPF.
The trap: nested include: statements each trigger their own lookups, and those included records can themselves include others. Adding include:sendgrid.net might look like one lookup, but SendGrid's SPF record includes three more, which include others. You burn through the limit fast.
Audit your lookup count:
# Use mxtoolbox.com/spf.aspx to visualize the full lookup tree
# Or use the command line:
dig TXT example.com +short
# Then follow each include: manually
dig TXT _spf.google.com +short
# Count them up
If you're over 10, flatten your SPF record by resolving include: statements to their actual IP ranges and replacing the includes with direct ip4: and ip6: entries. The downside: if your provider changes their sending IPs, your flattened record becomes stale. Some services (Dmarcian, Valimail) automate this flattening with DNS-based macros.
The Multiple SPF Records Problem
You can only have one SPF TXT record per domain. Not two. One.
If you publish two TXT records starting with v=spf1, the result is permerror. The receiving server doesn't know which to use and fails the lookup.
This happens constantly when someone adds a new email provider ("just add their include: to your DNS") and creates a second record instead of editing the existing one.
# Check if you have multiple SPF records
dig TXT yourdomain.com | grep "v=spf1"
If that returns two lines, you have a problem. Merge them into one record.
The Forwarding Problem
SPF has a fundamental limitation with email forwarding. Here's what happens:
user@example.comhas an SPF record listing their mail server at198.51.100.1.- Someone sends them an email.
- The email is forwarded from a forwarding service at
198.51.100.50. - The receiving server checks SPF for
example.com. 198.51.100.50is not in the SPF record. SPF fails.- The forwarded email gets flagged or rejected.
The forwarding server changed the envelope sender's IP without changing the domain. SPF checks the sending IP against the domain's policy — and the forwarder's IP isn't authorized.
Solutions:
- SRS (Sender Rewriting Scheme): The forwarding server rewrites the envelope
MAIL FROMto use its own domain. SPF now checks the forwarder's domain, which it passes. Some forwarding services support this. - DKIM survives forwarding — which is why having both SPF and DKIM matters. If DKIM signs the message at the origin, the signature survives forwarding even when SPF fails.
Auditing an SPF Record
Run through this checklist:
- Do you have exactly one
v=spf1TXT record? - Does it include all services that send as your domain? (transactional email, marketing, CRM, ticketing, etc.)
- Does it use
~allor-all(not+all)? - Is it under the 10-lookup limit?
- Is the record under 255 characters? (DNS TXT records have a 255-character string limit, though multiple strings can be concatenated)
A realistic production record for a company using Google Workspace, SendGrid, and a custom MTA:
v=spf1 include:_spf.google.com include:sendgrid.net ip4:203.0.113.10 ~all
Once you've verified delivery from all sources, move to:
v=spf1 include:_spf.google.com include:sendgrid.net ip4:203.0.113.10 -all
Key Takeaways
- SPF is a TXT record at your domain root. One record per domain.
+allauthorizes every IP on the internet to send as you. Never use it.- Use
~allwhile auditing,-allonce you're confident in your sender list. - Each
include:triggers DNS lookups. Stay under 10 total. - Multiple SPF records = permerror = failed check.
- SPF breaks with email forwarding. DKIM is what survives forwarding.
Further Reading
- RFC 7208 — Sender Policy Framework (SPF) for Authorizing Use of Domains in Email
- kitterman.com/spf/validate.html — SPF validator
- mxtoolbox.com/spf.aspx — visualize the full lookup tree
Up Next
Lesson 04 covers DKIM: the cryptographic layer that signs email at the origin and survives forwarding — the mechanism that makes your emails verifiable end-to-end even when SPF fails.