← blog

authentication · 13 Sept 2026 · 5 min read

The SPF 10-lookup limit, and why your record silently fails when you cross it

SPF allows ten DNS lookups per evaluation. Every include, a, mx and redirect spends one, nested includes spend their own, and going over produces a permanent error that receivers count as a failure. How to count, what to cut, and why flattening is a maintenance debt rather than a fix.

SPF evaluation stops after ten DNS-querying mechanisms, and the result is permerror, which DMARC treats as a failure. Each include, a, mx, ptr, exists and redirect costs one lookup, and nested includes cost their own. Fix it by removing unused services, giving each sending service its own subdomain, and never using ptr. Flattening to IP addresses works until the provider changes an address.

Three signal beams converging; two meet the target, the third misses below it.

There is a number in the SPF specification that most people find out about the hard way: a receiver evaluating your record will perform at most ten DNS lookups, and if the record needs an eleventh, evaluation stops with permerror. A permanent error is not a soft failure or a warning. DMARC treats it as SPF having failed, and unless DKIM is carrying the message on its own, the message fails DMARC.

Nothing warns you when you cross the line. The record looks fine, it resolves, and it is syntactically valid. Mail just starts failing at receivers that count carefully, which is all the large ones.

What counts

Every mechanism that makes the receiver query DNS spends one lookup:

MechanismCostNotes
include:1, plus everything inside itThe usual culprit. Nested includes cost their own lookups
a1Even a with no argument, which looks up the domain itself
mx1Plus the A lookups for each MX host, capped separately
ptr1Deprecated, slow and unreliable. Never use it
exists:1Rare outside macro-heavy setups
redirect=1Plus everything in the target record
ip4: / ip6:0Literal addresses cost nothing
all0

The cost that surprises people is the nesting. A provider’s include:_spf.provider.example might itself contain three includes, each of which contains two more. You published one mechanism and spent nine lookups.

There is a second, smaller limit worth knowing: at most two lookups may return no answer, so-called void lookups, before the record also fails. Stale includes pointing at records that no longer exist trip this one.

Counting your own record

Resolve it by hand once, so you understand what the checkers are doing:

$ dig TXT yourapp.com +short
"v=spf1 include:_spf.google.com include:sendservice.example include:crm.example -all"

Three includes: three lookups so far. Now resolve each:

$ dig TXT _spf.google.com +short
"v=spf1 include:_netblocks.google.com include:_netblocks2.google.com include:_netblocks3.google.com ~all"

Three more, and each of those _netblocks records is pure ip4:/ip6:, so they stop there. Running total: six. Do the same for the other two includes. If the CRM’s record has two includes and the send service’s has one, you are at nine, and the next tool someone connects breaks the domain.

The count changes without you touching anything. A provider restructuring its own record can push you over from their side. That is why the count is something to re-run on a schedule, not a number you write down once.

What to cut

In order of preference:

Remove services that no longer send. Every organisation’s root SPF record carries at least one include for a tool nobody has used in two years. The DMARC aggregate reports tell you which includes are actually producing mail.

Give each sending service its own subdomain. This is the structural fix. mail.yourapp.com for the application, news.yourapp.com for the newsletter, crm.yourapp.com for sales tooling. Each subdomain has its own SPF record with its own ten-lookup budget, and each one holds exactly one include. The root domain’s record shrinks to the office mail provider and -all. As a side effect, each stream’s reputation is also tracked separately, which is a benefit you would want anyway.

Drop ptr and bare a/mx when they are redundant. If the hosts an mx mechanism would authorise are already covered by an include, the mx is pure cost.

Replace an include with literal addresses only as a last resort. This is flattening, and it deserves its own section.

Why flattening is a debt, not a fix

Flattening means resolving an include to the IP ranges it currently contains and publishing those as ip4: and ip6: mechanisms, which cost nothing. The record passes the count on the day you do it.

The problem is the word currently. A provider adds a sending range, and your flattened record does not include it. Mail from the new range fails SPF, and because SPF failures do not bounce, the first sign is a slow decline in delivery at receivers that weight SPF, weeks later, with no error to trace.

If you must flatten, treat it as an automated job: re-resolve the includes daily, re-publish when the result changes, and alert when it does so you know why the record moved. A flattened record with no automation behind it is a record with an unknown expiry date.

The permerror is not the only way to fail

Two more rules that produce a permanent error and are easy to hit while editing:

  • Two SPF records on one domain. Both start v=spf1, so the receiver cannot pick, and the result is permerror. Merge them into one.
  • A record over 255 characters in a single string needs to be split into multiple quoted strings within the same TXT record, not into two records. Most DNS hosts do this for you, but not all.

Check the result the same way you check everything else in email authentication: send one message and read Authentication-Results. spf=permerror is spelled out in the header, and it is far easier to find there than in a delivery-rate chart three weeks later.

Where Pharos fits

Pharos sends from a subdomain you dedicate to it, so the SPF record on that subdomain holds one include and nothing else, and the root domain’s lookup budget is untouched. That include is maintained on the Pharos side and re-verified daily along with the other records, which is the part of this problem you should not have to schedule yourself.

Questions this raises

Does ip4 or ip6 count as a lookup?

No. Literal address mechanisms are evaluated without DNS and cost nothing. Only include, a, mx, ptr, exists and the redirect modifier count toward the ten. The all mechanism is also free.

How do I count the lookups my record actually uses?

Resolve every include recursively and add up each include, a, mx, ptr, exists and redirect you encounter at every level. A provider's include frequently contains three or four more, so a record that reads as three includes can cost twelve. Several free online checkers do this count for you; what matters is that you re-run it whenever a service is added.

Is SPF flattening safe?

It works on the day you do it. Flattening replaces includes with the IP addresses they currently resolve to, so the record stops tracking the provider's changes. When the provider adds an address, mail from it fails SPF and nothing tells you. If you flatten, automate re-flattening and alert on drift.

Sources

Written by the Pharos team · 13 Sept 2026 rss

Check your own headers first.

If authentication is failing, Pharos fixes it as a side effect of onboarding a domain. Free plan, 3,000 emails a month.

start free