What SPF lookups actually count (and what doesn't)
The RFC 7208 10-lookup limit is the single most misunderstood thing about SPF. Almost everyone sums their
include: statements, sees they're under 10, and assumes they're fine.
You're probably not fine. The limit counts far more than include: lines, and getting it wrong is what silently turns your SPF
record into a PermError. That is not the same as SPF fail, but many receivers treat it badly enough that deliverability takes a
hit (some effectively behave as if you had no usable SPF).
This is the exact breakdown of what counts toward the 10-lookup limit, and what's free.
The rule that everyone quotes
"An SPF implementation MUST limit the total number of DNS lookups to 10."
That's RFC 7208 section 4.6.4. But the word that trips people up is total. It's not 10 include: directives;
it's 10 DNS lookups, and several things generate more than one.
What counts toward the limit
Every include:, plus what's behind it
Each include: counts as one toward the 10 for the included domain's own SPF record. But if that record
uses further include:, mx, a, ptr, or exists terms,
those count too, and they're usually hiding several levels deep.
Provider lookup counts drift. include:spf.protection.outlook.com is currently a flat ip4:/ip6: list
(1 lookup). SendGrid and Mailgun still nest. Re-check the live chain; do not memorize a blog table forever.
The mx mechanism (one term, not one per MX host)
mx counts as exactly one toward the 10-term limit, even if the domain publishes several MX hosts. The receiver
still has to look up those hosts' A/AAAA records (extra DNS work), but those address queries sit under a
separate RFC 7208 limit: at most 10 address records per mx evaluation. They do
not each use another slot of the main 10. That is a common myth that makes people miscount their budget.
The a mechanism
a counts as one term. One address lookup may return several A/AAAA records; multiple addresses in that response
do not multiply the counter. Same idea for ptr and exists: each evaluated term is one, with their own extra
processing rules in the RFC.
redirect (the same limit as include)
A redirect= modifier is evaluated against the same 10-term limit, not a fresh one. A redirect that itself uses includes
uses your lookup budget just like an include would.
What does NOT count
ip4: and ip6: mechanisms: always free
Direct IP mechanisms are never DNS lookups. This is the entire basis of SPF flattening: replace lookup-hungry include: chains
with flat ip4:/ip6: lists, and your record drops to 1 lookup total.
An include: whose domain has no SPF record
If the included domain publishes no SPF record, the include: still costs one of your 10: the lookup that
discovered the empty result. There is no free pass. Empty or NXDOMAIN answers also feed the separate void-lookup limit (implementations
SHOULD stop after two), so broken includes can make the SPF check fail even before you hit 10 terms.
exp: the explanation modifier
exp= references a macro string in a TXT record that's only looked up after a fail result. It does
not count toward the 10-term limit during evaluation. Same for all.
The hidden gotcha: nested includes
The nasty part is that providers nest. A "simple" record like this:
v=spf1 include:spf.protection.outlook.com include:sendgrid.net include:mailgun.org ~all
looks like 3 lookups, but currently uses about 8 once the chains are expanded: Outlook is flat
(1), SendGrid nests ab.sendgrid.net (2), and Mailgun fans out through US and EU SPF trees
(5). Add an mx or another provider include and you're on the edge without seeing it.
include: lines in your TXT record is almost never the number a
receiver will evaluate.
The practical way to check
You can't reliably count this by eye. The correct number is the evaluated DNS lookup count, which means you need a tool that walks the full chain.
Use the free lookup tool at spflat.cloud: enter your domain and it reports the real lookup count, whether you're at/over the 10-lookup limit, and whether a PermError occurs right now. No account needed.
When "under 10" isn't enough
Even at 8 or 9 lookups you're one provider change away from a PermError. Email providers routinely add or remove CIDR ranges; the moment a
nested include expands by one more hop, your record flips into permerror and receivers treat it as absent.
That's why counting correctly matters, and why automated flattening (which expands the whole chain to direct IPs and keeps it updated as provider ranges change) is the only way to stay well under the limit, long term, without constant manual checks.
Summary
- Count the evaluated lookups, not your
include:lines. mxandacost lookups too;ip4:/ip6:cost nothing.- Nested includes hide the real number.
- Check the actual count with a tool, not your arithmetic.
Check your real SPF lookup count
Free, no signup. See the evaluated DNS lookup count (including nested includes) and whether you're at or over the 10-lookup limit.
Check Your SPFOr start your free 14-day trial →
