Anatomy of a record
v=spf1 ip4:203.0.113.0/24 include:_spf.google.com mx -all
v=spf1: the version. Must come first, exactly like this.ip4:203.0.113.0/24: allow this range.include:_spf.google.com: also allow whatever Google's SPF record allows.mx: allow the servers in this domain's MX records.-all: fail everything else.
Receivers read the terms left to right and stop at the first match.
Validate a domain's SPF record and count its lookups:
Mechanisms
| Mechanism | Matches when | DNS lookup? |
|---|---|---|
ip4: | The sender's IPv4 address is in the address or range | No |
ip6: | The sender's IPv6 address is in the address or range | No |
a | The sender is one of the domain's A or AAAA addresses | Yes |
mx | The sender is one of the domain's MX hosts | Yes |
include: | The other domain's SPF record passes | Yes |
exists: | The given name has an A record (used for advanced macros) | Yes |
ptr | Reverse DNS matches. Deprecated; don't use | Yes |
all | Always. Goes last | No |
Two modifiers can appear too: redirect=otherdomain uses another domain's record instead of this one (only when there is no all), and exp= points to an explanation text for failures.
Qualifiers
Each mechanism can carry a prefix that decides the result when it matches. Without one, it means +.
| Qualifier | Result | Typical use |
|---|---|---|
+ | Pass | Default; never write +all |
- | Fail | -all: strict, once every sender is listed |
~ | Softfail | ~all: suspicious but accept, while you are still checking |
? | Neutral | No opinion; rarely useful |
The 10-lookup limit
Checking an SPF record may trigger at most 10 DNS lookups (RFC 7208, section 4.6.4), counting include, a, mx, exists, ptr and redirect, including those inside included records. Go over and SPF returns a permanent error, which receivers treat as a failure. ip4, ip6 and all don't count.
Each large provider's include often uses several lookups on its own, so a domain with five or six services can hit the limit quickly. To get back under it:
- Remove services you no longer use.
- Drop
aandmxif those servers don't send mail. - Move a service that sends a lot to a subdomain (for example
news.example.com) with its own SPF record. - Replace an include with the provider's published
ip4:ranges only if the provider says they are stable.
Rules that trip people up
- One record only. Two TXT records starting with
v=spf1is a permanent error. Merge them. - Long records are split into strings. A TXT string holds 255 characters; longer records are written as several quoted strings that receivers join without spaces:
"v=spf1 include:a.example " "include:b.example -all". - Put it on the domain that sends. The record belongs at the domain in the return-path address, which is usually your domain but can be a subdomain your provider sets up.
- Domains that never send mail should say so:
v=spf1 -all. - SPF alone doesn't protect the From address. Pair it with DKIM and DMARC; see SPF, DKIM and DMARC explained.
Examples
Google Workspace only
v=spf1 include:_spf.google.com ~all
Microsoft 365 only
v=spf1 include:spf.protection.outlook.com -all
Your own server plus a mailbox provider
v=spf1 ip4:203.0.113.25 include:_spf.google.com -all
A domain that sends no mail
v=spf1 -all
Always copy a provider's include from its own documentation. A mistyped include silently stops matching, and mail from that service starts failing.
After you publish
DNS changes take time to reach every resolver; see what is DNS for why. Then run the SPF checker to confirm the record parses, stays under 10 lookups and has no duplicates, and set up DMARC so you hear about failures.