When you publish an SPF record, you list which mail servers can send email on behalf of your domain. Most of those listings come via include: statements referencing SPF records from cloud services — your CRM, support desk, transactional mail provider.
Each include: triggers a DNS lookup. RFC 7208 limits SPF evaluation to 10 DNS lookups total. Exceed that limit and the receiving server is allowed to treat your SPF as permanently erroring — returning permerror — which most providers treat the same as an SPF fail.
Why this accumulates
SPF records rarely start broken. They grow incrementally as services are added:
- Exchange Online:
include:spf.protection.outlook.com - Mailchimp:
include:servers.mcsv.net - Salesforce:
include:_spf.salesforce.com - Zendesk:
include:mail.zendesk.com - Xero:
include:_spf.xero.com
Each include: may contain nested include: statements, each costing another lookup. A record that looks like 5 includes can easily resolve to 12–15 actual lookups.
That pattern is exactly what we found at a Sydney professional services firm: seven cloud services added one at a time, resolving to 14 lookups, failing only at providers that enforce the limit strictly. We covered how we diagnosed and fixed it for that firm separately.
How to count your lookups
Trace the full include chain manually with dig:
dig TXT yourdomain.com
For each include: found, repeat for that domain. Count every include:, redirect=, a and mx mechanism — each is one lookup. ip4: and ip6: statements cost nothing.
Alternatively, use the Email Authentication Checker — it resolves your SPF record and reports the full include chain with a lookup count.
What counts as a lookup, and what doesn’t
This is where most manual counts go wrong. The limit is on DNS-querying mechanisms, not on the length of the record.
These cost one lookup each:
include:— and everything nested inside itaandmxptr(deprecated, see below)exists:- the
redirect=modifier
These cost nothing:
ip4:andip6:— the address is already in the record, so there is nothing to resolveall- the
exp=modifier, which is only evaluated when a message actually fails
So a record with twenty ip4: ranges and two include: statements is at two
lookups, not twenty-two. A record with five include: statements and nothing
else can be at fifteen. Length tells you nothing; structure is everything.
One extra trap: the mx mechanism resolves your MX records and then resolves
each MX hostname. RFC 7208 caps that expansion at ten hosts, and exceeding it is
its own permerror independent of the ten-lookup budget.
The void lookup limit nobody mentions
Separately from the ten-lookup budget, RFC 7208 limits void lookups — DNS
queries that come back NXDOMAIN or with no relevant records — to two per
SPF evaluation. Exceed that and you get permerror, even if your total lookup
count is comfortably under ten.
This is the failure mode that catches records nobody has audited in years. A
service is decommissioned, the provider eventually withdraws the SPF record the
include: pointed at, and that include: becomes a void lookup. Three retired
services in one record and SPF stops evaluating — with a lookup count that still
looks fine on a naive count.
It is also why “the record has not changed, so it cannot be SPF” is bad reasoning. Your record does not have to change for it to start failing. Somebody else’s does.
permerror is not the same as fail
These are different results and the difference matters for diagnosis.
failmeans SPF evaluated correctly and the sending IP is not authorised.permerrormeans SPF could not be evaluated at all — a syntax error, too many lookups, or too many void lookups.
RFC 7208 leaves the handling of permerror up to the receiver, and that is
precisely why an over-limit record produces symptoms that look random. Some
providers treat it as a fail and reject. Some treat it as neutral and deliver.
Some weight it as a spam signal without rejecting outright. Same message, same
record, different outcome per recipient — which is exactly the pattern that
sends people looking for a problem inside their own mail server.
For DMARC, however, there is no ambiguity: a permerror is not a pass, so it
cannot contribute to DMARC alignment. A domain at p=reject with an over-limit
SPF record is relying entirely on DKIM, whether it knows it or not.
Mechanisms to avoid entirely
ptr performs a reverse DNS lookup on the connecting IP and then forward-
resolves the result. It is slow, it is unreliable, and RFC 7208 explicitly
discourages publishing it. Some receivers skip it entirely. If you find ptr in
a record, it is almost always a copy-paste inheritance from a decade-old
template — remove it.
Be equally wary of +all, which authorises the entire internet to send as your
domain. It appears more often than it should, usually as a misguided fix for
delivery problems caused by something else.
How to fix it
Remove unused includes. Check each service listed — is it still actively sending as your domain? Many organisations keep includes for services retired years ago.
Flatten your SPF record. Instead of include:servers.mcsv.net, look up what IP ranges that resolves to and list them directly as ip4: statements. This eliminates the lookup cost for that service. The trade-off: if the provider changes their IP ranges you need to update your record.
Restructure nested includes. If multiple services include the same third-party SPF records, consolidate.
Use SPF macros. For high-volume senders with large IP ranges, macros allow per-message lookup evaluation without pre-publishing all IPs. This requires receiver support, which most providers have.
How to tell this is happening in production
You will not get a bounce that says “SPF permerror”. The signals are indirect:
- DMARC aggregate reports. If you have a
rua=address, the reports carry the SPF result per source IP. A record over the limit shows up as SPF failing from sources that should obviously pass — your own mail platform included. This is the earliest reliable signal. - Provider-specific failures. Delivery problems clustered at one or two
recipient domains, while the rest of your mail flows normally, is the
signature of a receiver-dependent
permerrorpolicy. - A scheduled check. The lookup count is a number you can assert on. Any monitoring that resolves your SPF chain on a schedule and alerts above a threshold will catch a provider’s nested change before your recipients do.
That third one matters more than it looks, because this problem arrives without
you doing anything. A provider adds one include: inside their own record and
your count goes up overnight. Auditing SPF once at setup is not enough; it is a
value that drifts on somebody else’s schedule.
The right target
Aim for 7 or fewer lookups. This leaves headroom for future services and handles providers that enforce stricter limits than the RFC requires.
After any SPF change, wait for DNS propagation (check your TTL) and re-verify with the Email Auth Checker.