spf=permerror (SPF record includes too many DNS lookups)
That line in a Received-SPF header or an Authentication-Results block means your SPF record blew past the ceiling RFC 7208 sets: no more than ten DNS-querying mechanisms per evaluation. When a receiver hits the eleventh lookup it stops and returns permerror, and most treat permerror as an outright SPF failure. The frustrating part is that your record can look completely valid. The limit is not about how the record reads; it is about how many DNS queries it forces the receiver to make while expanding it.
What Actually Counts
The limit counts mechanisms that trigger a DNS lookup, evaluated recursively. Each include, a, mx, ptr, and exists costs one, and every include you chain into is expanded and its lookups counted too. The ones that do not query DNS are free.
| Mechanism | Costs a lookup? |
|---|---|
include: |
Yes, plus everything it expands to |
a / a: |
Yes |
mx |
Yes, plus one per MX host returned (up to 10) |
ptr |
Yes (and deprecated, drop it) |
exists: |
Yes |
ip4: / ip6: |
No |
all |
No |
redirect= |
Yes (counts as a lookup itself) |
The trap is the recursion. A single include:_spf.vendor.com looks like one lookup in your record, but if the vendor's record itself includes three more providers, you have just spent four of your ten on one line. You do not control what a third party nests inside their record, and they can change it without telling you.
Counting Your Own Record
Expand it and count. dig shows you your published record; a full check walks the includes for you:
dig +short txt example.com | grep v=spf1
"v=spf1 include:_spf.google.com include:sendgrid.net include:_spf.vendor.com include:servers.mcsv.net ip4:203.0.113.0/24 ~all"
Four includes, and each of those expands further. _spf.google.com alone typically resolves to three nested includes. It is easy to see how a record with a handful of vendors lands at twelve or thirteen actual lookups. The SPF checker at mrdns.com expands the whole tree and gives you the real count, which is the number that matters, not the count of lines you typed.
Fixing It: Flattening
Flattening means resolving the include chains yourself and replacing them with the raw ip4: and ip6: ranges they point to. Those cost zero lookups.
# before: 3 includes, ~9 lookups when expanded
v=spf1 include:_spf.provider.com ~all
# after: the same networks, 0 lookups
v=spf1 ip4:198.51.100.0/24 ip4:203.0.113.0/26 ip6:2001:db8::/32 ~all
Flattening trades a maintenance cost for a correctness win, and you have to be honest about the trade. When a provider changes the IP ranges behind their include, your flattened record does not follow. You either automate the flattening so it re-resolves and republishes on a schedule, or you accept that you now own a record that can silently drift out of date. Do not flatten a provider by hand once and forget it. That is how mail starts failing SPF six months later for no visible reason.
Fixing It: Subdomains and Pruning
Often the cleaner fix is to stop stuffing everything through one domain. Send different mail streams from different subdomains, each with its own lean SPF record:
example.com v=spf1 ip4:203.0.113.0/24 ~all # corporate mail
mktg.example.com v=spf1 include:sendgrid.net ~all # marketing
bounce.example.com v=spf1 include:servers.mcsv.net ~all # transactional
Each record gets its own budget of ten. A marketing platform that eats five lookups no longer competes with your corporate mail server for the same ceiling. This also happens to improve deliverability attribution, since a reputation problem on the marketing stream stays contained to mktg.example.com.
Before you touch anything, prune. Old vendors you no longer use, a ptr mechanism left over from 2010, a duplicate include: these are lookups spent on nothing. Remove them first and you may find you never needed to flatten at all.
One Record, One Domain
A subtle failure that looks like a lookup problem but is not: publishing two separate v=spf1 TXT records on the same domain is itself a permerror, regardless of how few lookups each contains. A domain gets exactly one SPF record. Merge them.
Keep Watching After You Fix It
SPF is not a set-and-forget record, especially once third-party includes are involved. A vendor deepening their own include chain can push you back over ten without a single change on your side. Recheck the expanded count whenever you add a sender, and fold SPF into the broader picture: it is one of three signals receivers weigh, alongside DKIM and DMARC, and a green SPF result still fails DMARC if it is not aligned. Our walkthrough of SPF, DKIM, and DMARC together covers how they interlock. On the infrastructure side, Generator Labs blacklist monitoring tracks whether the sending IPs behind those records are landing on reputation lists, which is the failure SPF cannot warn you about. Start monitoring.