An SPF record is a single DNS TXT record, published at your domain's apex, that begins with v=spf1, lists the mail servers authorized to send on your behalf, and ends with an all mechanism telling receivers what to do with everything else. v=spf1 include:_spf.google.com ~all is a complete, valid record. Every other piece of SPF record syntax is a variation on those three parts - plus a set of hard limits that silently break records which otherwise look correct.
Those limits matter more than most people expect. In a scan of 5,499,028 domains from the Tranco list carried out on 27 February 2026, DMARCguard found that 56.0% publish an SPF record - ahead of DMARC (30.4%) and DKIM (22.7%) - but that 4.8% of those SPF records, 148,655 domains, exceed the 10-lookup ceiling set by RFC 7208 and therefore fail evaluation outright.
The three parts of every SPF record
Every valid record breaks down the same way:
v=spf1 ip4:203.0.113.10 include:_spf.google.com -all
| | | |
version mechanism mechanism catch-all
-
The version tag.
v=spf1must be the first term and must match exactly. Anything else means the record is not an SPF record at all. - Mechanisms. Evaluated strictly left to right. The first mechanism that matches the connecting IP decides the result and evaluation stops immediately - order is not cosmetic.
-
The catch-all. An
allmechanism as the final term. Anything published afterallis ignored.
One thing worth being precise about: SPF checks the envelope sender used in the SMTP MAIL FROM command (the Return-Path) and the HELO identity. It does not check the From: header your recipient actually sees. Closing that gap is the job of DMARC, which is why the two are always deployed together. If you are still mapping out the wider setup, our DNS security configuration guide covers how SPF, DKIM, DMARC and DNSSEC fit together.
SPF once had a dedicated DNS record type (type 99). RFC 7208 §3.1 retired it in April 2014 - publish SPF as a TXT record only.
SPF mechanisms: the full reference
| Mechanism | Matches when the sending IP… | DNS lookups | Notes |
|---|---|---|---|
all |
always matches | 0 | Must be the last term |
ip4: |
falls inside the given IPv4 address or CIDR range | 0 |
ip4:203.0.113.0/24 - costs nothing against the lookup limit |
ip6: |
falls inside the given IPv6 address or prefix | 0 | ip6:2001:db8::/32 |
a |
matches an A or AAAA record of the domain | 1 | Bare a means the current domain; a:mail.example.com names another |
mx |
matches an address record of one of the domain's MX hosts | 1 | Each MX host must not resolve to more than 10 address records |
include: |
passes the included domain's own SPF evaluation | 1, plus whatever the included record itself spends | The most-used mechanism and the usual cause of lookup blowouts |
exists: |
the expanded domain name has any A record | 1 | Used with macros for per-sender rules; rare in ordinary records |
ptr |
reverse DNS for the IP resolves back into the domain | 1 | RFC 7208 §5.5 states plainly: "This mechanism SHOULD NOT be published." Remove it |
A subtlety that trips people up: include: is not a textual paste. It runs a full, separate SPF evaluation against the included domain and matches only if that evaluation returns pass. A -all inside an included record does not reject your mail - it simply makes the include: not match, and evaluation continues to your next mechanism.
Qualifiers: four characters that decide the verdict
Any mechanism can carry a qualifier prefix. RFC 7208 §4.6.2 defines exactly four, and the default is +.
| Qualifier | Result | What it asks the receiver to do | When to use it |
|---|---|---|---|
+ (default) |
pass | Treat the sender as authorized | Implicit - +mx and mx are identical |
~ |
softfail | Accept, but mark as suspicious |
~all during rollout, or where forwarding is common |
- |
fail | Treat as unauthorized; reject or bin |
-all once every legitimate sender is listed |
? |
neutral | Make no statement either way | Practically the same as publishing nothing |
Never publish +all. It authorizes the entire internet to send as your domain and is strictly worse than having no SPF record.
Modifiers: redirect and exp
Modifiers are name/value pairs rather than mechanisms, and they may appear only once each.
-
redirect=example.comreplaces your record's result wholesale with the target domain's SPF result. It costs one DNS lookup and is ignored entirely if anallmechanism is present, so the two should not appear together. -
exp=explain.example.compoints at a TXT record supplying a human-readable explanation string returned on a fail. It costs no lookup at evaluation time.
The limits that break otherwise-valid records
This is where most real-world SPF failures live. All of the following come straight from RFC 7208 §3.4 and §4.6.4:
-
10 DNS-querying terms. The
include,a,mx,ptrandexistsmechanisms and theredirectmodifier each consume one. Exceeding 10 MUST returnpermerror- a hard failure, not a warning.all,ip4,ip6andexpare free. -
2 void lookups. A query returning NXDOMAIN or an empty answer is a "void lookup". Implementations should cap these at two; exceeding the cap also produces
permerror. Staleinclude:entries for services you no longer use are the usual culprit. -
10 address records per
mxorptr. Beyond the overall budget, each individual MX host is capped at 10 A/AAAA records. -
One record per domain. If a lookup returns more than one
v=spf1record, the result ispermerror. Merge them into a single record; never publish two. - 255 characters per string, 450 octets recommended overall. A TXT record can hold multiple quoted strings, concatenated without spaces. RFC 7208 §3.4 recommends keeping the whole DNS answer under 450 octets so it fits in a UDP packet.
-
A 20-second evaluation budget. Receivers should allow at least 20 seconds and return
temperrorpast that.
Where your ten lookups actually go
Each third-party sender you add spends at least one lookup, and some spend more because their own records nest further includes.
| Sender | Typical include:
|
Lookups consumed |
|---|---|---|
| Google Workspace | _spf.google.com |
1 |
| Microsoft 365 | spf.protection.outlook.com |
1 |
| Mailchimp | servers.mcsv.net |
1 |
| SendGrid | sendgrid.net |
1 |
| Amazon SES | amazonses.com |
1 |
| Zendesk | mail.zendesk.com |
1 |
| Salesforce | exacttarget.com |
2 |
Six or seven services and you are at the ceiling. The fixes, in order of preference: remove includes for services you have retired, move senders onto dedicated subdomains with their own SPF records, and only then consider flattening includes into literal ip4: ranges - flattening removes lookups but means you must track your providers' IP changes yourself.
Share of domains publishing an SPF record, by top-level domain. Source: DMARCguard, "State of Email Authentication 2026", scan of 5,499,028 Tranco domains, 27 February 2026.
Five syntax mistakes that cause PermError
-
Two
v=spf1records on the same name. Common after a migration. Merge, do not stack. -
A qualifier on
allthat contradicts intent -?alllooks cautious but tells receivers nothing, so spoofed mail sails through. -
Terms after
all. Everything to the right ofallis dead text. -
Leftover
ptr. Deprecated since 2014, slow, and it burns a lookup for no benefit. - Includes for retired vendors. They cost a lookup each and can turn into void lookups when the vendor removes the record.
How to check your own record
Read your record straight from DNS with a single query:
dig +short TXT example.com
On Windows, the equivalent is nslookup -type=TXT example.com. Look for exactly one string starting with v=spf1, then count the lookup-consuming terms by hand - and remember to count the ones hidden inside each include:.
Since February 2024, Google and Yahoo have required bulk senders (roughly 5,000 messages a day or more) to authenticate with both SPF and DKIM and to publish a DMARC record of at least p=none. A permerror from a broken SPF record puts that compliance at risk, so it is worth verifying rather than assuming.
Run a free 60-second FortifyNet scan and we will check your SPF record alongside DKIM, DMARC, your TLS configuration, security headers and dark-web exposure - no signup needed.
Frequently asked questions
Can a domain have two SPF records?
No. If a lookup returns more than one v=spf1 record, RFC 7208 §4.5 requires receivers to return permerror, which fails SPF entirely. Combine the mechanisms into one record.
Should I use -all or ~all?
Start with ~all while you confirm every legitimate sender is listed, then move to -all. -all is the stronger anti-spoofing signal, but only once your inventory of senders is genuinely complete.
Do ip4: entries count toward the 10-lookup limit?
No. ip4:, ip6:, all and exp cause no DNS queries at evaluation time and are exempt. Only include, a, mx, ptr, exists and redirect count.
Does SPF on its own stop spoofing?
No. SPF validates the envelope sender, not the From: header a recipient reads. An attacker can pass SPF on a domain they control while displaying yours. DMARC ties the two identities together, which is what actually closes the gap.
How long can an SPF record be?
Each individual string in a TXT record is capped at 255 characters, though multiple strings are concatenated. RFC 7208 recommends keeping the whole DNS answer under 450 octets so it survives in a UDP packet.
Related guides
- SPF record checker: how to test, read and fix your SPF record
- How to set up DMARC, step by step
- What is DKIM? DomainKeys Identified Mail explained
Sources: RFC 7208, Sender Policy Framework (SPF) version 1, IETF, April 2014 (§3.1, §3.4, §4.5, §4.6.2, §4.6.4, §5.5, §6.1, §6.2) · DMARCguard, "State of Email Authentication 2026", scan of 5,499,028 Tranco domains, 27 February 2026 · Google and Yahoo bulk sender requirements, effective February 2024.
Originally published at fortifynet.com/blog/spf-record-syntax. I'm the founder of FortifyNet, a website security scanner; this article comes from our blog, so factor in that founder bias when you read any tool recommendations here.




