SPF explained: how to read, write and audit an SPF record
By James Bayliss · Updated · Reviewed
In this guide
What SPF actually checks
When a mail server connects to deliver a message, it announces an envelope sender with the SMTP MAIL FROM command. That address usually ends up in the message's Return-Path header. SPF, defined in RFC 7208, lets the owner of that envelope domain publish which IP addresses may use it.
Two details trip people up:
- SPF checks the envelope domain, not the From: address people see. A newsletter platform often uses its own bounce domain in
MAIL FROM, so SPF passes for their domain, not yours. That's why SPF passing doesn't guarantee DMARC passing; see DMARC alignment, explained. - SPF is evaluated against the IP that connected to the receiver. If mail is forwarded, the forwarder's IP connects, and SPF for the original domain usually fails. DKIM survives forwarding; SPF doesn't.
Anatomy of a record
Here is a typical record for an organisation that uses Microsoft 365 for staff mail and a marketing platform for newsletters:
example.com. 3600 IN TXT "v=spf1 include:spf.protection.outlook.com include:_spf.newsletter-vendor.example ip4:203.0.113.25 -all"
Read it left to right:
| Term | Meaning |
|---|---|
v=spf1 | Version tag. Must be first, exactly once. |
include:spf.protection.outlook.com | Evaluate Microsoft's SPF record; if it returns pass, this term matches. |
include:_spf.newsletter-vendor.example | The same for the marketing platform. |
ip4:203.0.113.25 | This single IPv4 address matches. |
-all | Everything else fails. |
Each term is a mechanism, optionally prefixed with a qualifier that sets the result when it matches:
| Qualifier | Result | Typical use |
|---|---|---|
+ (default, usually omitted) | pass | Authorised senders |
- | fail | -all at the end |
~ | softfail | ~all while auditing, or with DMARC enforcement |
? | neutral | Rarely useful |
The mechanisms
ip4:andip6:match an address or CIDR range (ip4:198.51.100.0/24). They cost no DNS lookups, so they're the cheapest way to authorise stable infrastructure.aandmxmatch the A/AAAA addresses of a host, or the addresses of a domain's MX hosts. Each costs one lookup, andmxcan trigger up to 10 more address lookups behind the scenes.include:evaluates another domain's SPF record and matches only if that record returns pass. It's how you authorise third-party senders.exists:matches if a DNS name resolves. It's used with macros by large providers and rarely needed by anyone else.ptrmatches by reverse DNS. RFC 7208 says it should not be used: it's slow, and some receivers ignore it.allalways matches. It belongs at the end as the catch-all.
Two modifiers can also appear: redirect= (use another domain's record if nothing else matched) and exp= (an explanation string for failures). redirect= is useful for pointing many domains at one shared policy.
How receivers evaluate it
The receiver runs a procedure called check_host():
- Fetch TXT records at the envelope domain and keep those starting with
v=spf1. None means the result is none; more than one is a permerror. - Walk the mechanisms in order. The first one that matches decides the result, using its qualifier.
- If nothing matches and there's a
redirect=, evaluate that domain instead. Otherwise the result is neutral.
Along the way, the receiver enforces hard limits. More than 10 terms that need DNS lookups (include, a, mx, ptr, exists, redirect) gives a permerror, and so do more than 2 lookups that return no data ("void lookups"). The too-many-lookups guide covers that limit in depth.
The all mechanism
The last term tells receivers what to do with everyone you didn't list.
-all(fail) says unlisted senders are not authorised. It's the clearest signal.~all(softfail) says they're probably not authorised. Many large senders use it deliberately. When DMARC is atp=quarantineorp=reject, DMARC drives the outcome anyway, and softfail avoids SPF-only rejections of forwarded mail by receivers that act on SPF directly.?all(neutral) gives receivers nothing to act on.+allauthorises the entire internet. It is never correct.
If you're unsure, use ~all while you discover senders, then decide between ~all and -all once DMARC is enforced. The difference matters far less once DMARC is in place than most debates suggest.
Writing a record from scratch
- Inventory every system that sends as your domain. Include the mail platform, marketing tools, the CRM, help desk, billing system, website contact forms, scanners and copiers, and monitoring alerts. DMARC aggregate reports are the best way to find the ones nobody remembers.
- Decide which ones should use your domain in the envelope. Many SaaS senders use their own bounce domain by default, which means they need DKIM alignment rather than an SPF entry. Check each vendor's documentation for "custom return-path" or "custom bounce domain".
- Add each sender using the vendor's published include, or
ip4:/ip6:for your own servers. - Count lookups. Each
includecounts as one, plus whatever that include contains. - Publish one TXT record, then test it against a known sending IP with the SPF validator.
A domain that never sends mail should still publish a record, so spammers can't use it:
parked-example.com. TXT "v=spf1 -all"
Common mistakes
- Two SPF records. Adding a second
v=spf1TXT record for a new service, instead of editing the existing one, makes every check a permerror. Merge them. - Over the 10-lookup limit. This is the most common cause of SPF failing after a new tool is added.
- Using
include:for a domain that has no SPF record. That's a permerror for every message that reaches that term. - Treating SPF as anti-spoofing on its own. SPF doesn't look at the visible From: address. Only DMARC ties authentication to the address people see.
- Listing a vendor that doesn't use your envelope domain. It adds lookups and achieves nothing; that sender needs aligned DKIM instead.
- Typos in the qualifier or mechanism.
ip4:203.0.113.0/33,inlcude:, or a stray quote character all cause permerrors. - Splitting a long record incorrectly. DNS TXT strings are limited to 255 characters each, but one record can hold several strings. Receivers join them with no separator, so put the space inside the quotes:
"v=spf1 ip4:192.0.2.1 " "include:_spf.example.net -all".
From the field: Three of the most frequent issues run into are: too many lookups, adding an email sending service and not adding it to the SPF, and having two SPF records. A Simple fix, but easily overlooked a lot of times. Remember: SPF is the list of servers authorized to send email on your behalf. You want to guard this closely for your company's email reputation.
Checking your record
The Email Record Lookup shows your record, its lookup count and any syntax problems. The SPF validator runs the full RFC 7208 evaluation for a specific IP, or for a whole CIDR range. That's useful when onboarding a new mail relay or confirming a provider's published range is covered.
Sources
Check it now
SPF Validation Is this IP address, or this whole range, allowed to send mail for the domain?
Related guides
- Fixing "SPF too many DNS lookups" (PermError)
Why SPF fails at 10 DNS lookups, how to count them correctly, and five safe ways to get back under the limit without breaking mail.
- DMARC alignment, explained: why SPF passes but DMARC fails
Which domains DMARC compares, relaxed vs strict alignment, and how to fix the most common "SPF pass, DMARC fail" and "DKIM pass, DMARC fail" cases.
- DKIM selectors: finding, verifying and rotating keys
What a DKIM selector is, how to find the one your mail platform uses, how to verify the published key, and how to rotate keys without failed signatures.