Email infrastructure
SPF: too many DNS lookups, and the silent failure it causes.
SPF is capped at ten DNS lookups per check. Cross that limit and your record returns PermError, which fails authentication for every message you send — silently, with no bounce, visible only as missing mail and failing DMARC reports. The mechanisms that cost a lookup are include, a, mx, ptr, exists, and redirect; ip4, ip6, and all are free. The honest fix is rarely a flattening tool: prune the includes you do not use, drop the deprecated ptr, and delegate SPF to a subdomain so your root record stays at a single lookup.
In short
- The cap is 10 lookups, plus 2 void. Defined in RFC 7208 §4.6.4; cross either and you get PermError.
- PermError fails all your mail, silently. Not just the extra service — every message, with no bounce to warn you.
- include, a, mx, ptr, exists, redirect cost lookups. ip4, ip6, and all are free.
- One SPF record only. Two v=spf1 records cause PermError regardless of lookup count.
- Delegation beats flattening. A delegated _spf subdomain is static; flattening goes stale when vendor IPs change.
Why does SPF break the moment you add one more service?
Sender Policy Framework lets a domain owner list the servers allowed to send mail in its name, and a receiving server checks that list before deciding how to treat a message. To evaluate it, the receiver resolves the mechanisms in your record through DNS — and RFC 7208 §4.6.4 caps that work at 10 DNS-querying mechanism lookups, plus a separate cap of 2 void lookups, per check. The cap exists for a sound reason: without it, a malicious record could chain references to trigger hundreds of DNS queries and turn every receiver into a denial-of-service amplifier.
The trouble is that the limit creeps up on you. A record with three senders works perfectly. Over a couple of years a team adds a CRM, a help desk, a marketing platform — each an innocent-looking include — and one day the sixth one tips the count past ten. Google Workspace alone consumes 3–4 lookups; a few more services and you hit the cap. From that moment the record is invalid, and the failure mode is the cruel part: Exceeding either limit returns PermError, which fails SPF for every message from the domain — silently. There is no bounce on the sending side; the only evidence is missing mail and failing DMARC reports.
This is not a rare edge case. A 2026 scan of 5.5 million domains found that 4.8% of SPF-enabled domains exceed the 10-lookup limit — 148,655 of 3.1M scanned (DMARCguard SPF Supply Chain Study, 2026, 5.5M Tranco domains). Each of those domains is sending mail with broken authentication right now, most of them unaware, because nothing in their own systems tells them. The deliverability damage shows up first as a slow drift toward spam folders, weeks before anyone traces it back to the record.
What does an over-limit record look like — and the fix?
Here is a record that has grown past the limit the way most do: a pile of includes accumulated over
time, an mx, and a deprecated ptr that nobody remembers adding. It looks
reasonable, and it is silently returning PermError.
# Before — 11 lookups, silently failing with PermError
v=spf1 include:_spf.google.com # 3-4 lookups
include:sendgrid.net # 1
include:servers.mcsv.net # Mailchimp · 1
include:_spf.salesforce.com # 1
include:spf.protection.outlook.com # 2-3
a mx ptr # 3 more — ptr is deprecated
-all
The durable fix keeps the root record at a single lookup by delegating the real list to a subdomain,
while pruning what is unused, dropping ptr, and converting a static sender to a direct
ip4. The delegated record still uses ordinary includes, so your vendors keep the ability
to update their own IPs.
# After — root stays at ONE lookup via subdomain delegation
example.com. TXT "v=spf1 redirect=_spf.example.com"
# the delegated record holds the real complexity, maintained in one place
_spf.example.com. TXT "v=spf1 include:_spf.google.com include:sendgrid.net
ip4:198.51.100.10 -all"
# ptr removed · static sender swapped for ip4 · unused includes pruned Counting lookups
Which mechanisms cost a DNS lookup?
The whole problem comes down to which mechanisms query DNS and which do not. Know this table and you can count your own record by hand.
| Mechanism | What it does | Lookup? |
|---|---|---|
| include | Costs ≥1 lookup and resolves the target's own SPF record, whose nested includes count too. The most common mechanism — about a third of all SPF mechanisms in the wild. | Counts |
| a | Resolves the domain's A/AAAA records. | Counts |
| mx | One lookup, plus one per returned MX host — can be surprisingly expensive. | Counts |
| ptr | Reverse-DNS per connecting IP, slow and unreliable. RFC 7208 discourages it — remove it. | Counts (deprecated) |
| exists / redirect | Each resolves a domain's SPF record; one lookup apiece. | Counts |
| ip4 / ip6 / all | Literal matches — no DNS query at all. Use these to replace lookup-heavy mechanisms for static senders. | Exempt |
The single most useful habit is to count before you add. Before bolting a new include onto your record, check the current total with an SPF analyzer — because the nested includes inside a provider's own record can change without notice, a record that was at eight lookups last month can be at eleven today through no edit of your own. There is also a second, quieter cap: RFC 7208 §4.6.4 limits void lookups — queries that return nothing — to two, and a single decommissioned service whose include now resolves to an empty record can trip it on its own.
The fix
Fixing it, fastest to most sustainable
Work the list in order. The early steps are quick wins; the later ones are the structural fixes that keep the problem from coming back.
- 1
Confirm it is actually a PermError
Run your domain through an SPF checker such as MXToolbox; it counts nested includes and flags "too many DNS lookups". Cross-check by sending a test to Gmail, opening Show Original, and looking for spf=permerror, and by reading your DMARC reports, where the SPF result shows permerror for every sender rather than just one.
- 2
Prune includes for services you no longer use
This is the cleanest and safest fix. Review every include, a, and mx mechanism; if the service no longer sends for you, delete it. This also clears the void-lookup PermError caused by a decommissioned service whose include now resolves to nothing.
- 3
Replace static senders with direct ip4 / ip6
For any sender on a fixed IP — typically your own mail server — swap the include or a mechanism for the IP itself, which costs no lookup. The trade-off is that you take over responsibility for that IP, since the vendor can no longer update it for you.
- 4
Remove the deprecated ptr mechanism
If ptr appears anywhere in your record, remove it. It does a reverse-DNS lookup for every connecting IP, which is slow and unreliable, and the specification recommends against it. Replace it with direct IP references where you still need those hosts authorised.
- 5
Delegate SPF to a subdomain
For the long term, point your root record at a delegated _spf subdomain with redirect, so the root stays at a single lookup while the delegated record carries the real complexity in one maintained place. This is static once configured and is the most reliable fix for a domain with many senders.
- 6
Treat flattening as a last resort
Only if delegation is not feasible, resolve includes to raw IPs in the record. Because vendor IPs change, a flattened record goes stale and silently breaks unless you automate periodic re-flattening and monitoring — which is exactly the maintenance burden delegation avoids.
Subdomain delegation is more reliable than flattening for almost every case; flattening is a workaround when delegation is not feasible. Many teams paying for a flattening tool no longer need it once they prune and delegate.
PermError is not the same as an SPF fail
It is worth being precise about the error, because the two outcomes call for completely different responses. PermError is not the same as an SPF fail. Fail means the record was evaluated and the IP did not match; PermError means the record itself is broken (too many lookups, a syntax error, multiple records, or unresolvable/circular includes) and could not be evaluated at all. A fail is a normal, healthy result — it is SPF doing its job, telling the receiver that a particular IP was not on your list. A PermError is the record itself being unreadable, which means SPF is giving the receiver no usable answer for any of your mail.
The knock-on effect reaches DMARC. DMARC interprets an SPF PermError as a fail, so if SPF is your only alignment path, a PermError quietly weakens DMARC too. A team that has carefully moved DMARC toward enforcement can find that a single over-limit SPF record quietly pulls the floor out from under it, because the alignment they were counting on now resolves to PermError for every message. Fixing the lookup count is therefore not only an SPF repair; it is often the missing piece that lets a DMARC policy actually hold.
There is one more misconfiguration worth ruling out while you are in the record, because it produces
the same PermError by a different route. RFC 7208 requires exactly one SPF (v=spf1) TXT record per domain. Two records cause PermError for all mail, regardless of lookup count — a very common misconfiguration. If you are troubleshooting and cannot
tell whether lookups or a duplicate record are to blame, count the lookups first; if the count is
comfortably under ten and you are still seeing PermError, look for that second stray
v=spf1 TXT entry.
The stale include that fails a record on its own
Not every PermError comes from sheer volume. The second cap — two void lookups — catches a different and sneakier failure: an include that points at a domain which no longer has an SPF record, so the query returns nothing. A void lookup is a DNS query that comes back empty or with an NXDOMAIN, and most receivers will return PermError once a record trips past two of them. The usual culprit is a single decommissioned service whose include was simply never removed.
There is a well-documented example of exactly this. Trend Micro KB KA-0017579: after migrating off Hosted Email Security, customers who left include:spf.hes.trendmicro.com in their record hit a void-lookup PermError because the stale include resolved empty. Removing the one dead include fixed it. The lesson is that a record can be comfortably under ten counted lookups and still PermError, because a dead include is not just wasted budget — it is an active void lookup. When you migrate away from any mail service, removing its include is part of the migration, not an optional cleanup for later.
This is why the cleanest fix is also the first one on the list. Pruning is not glamorous and there is no tool to sell for it, but going through every include, a, and mx mechanism and deleting the ones that no longer send for you removes both kinds of failure at once: it reclaims lookup budget and it clears the void lookups that a stale include leaves behind. Most over-limit records have at least one forgotten service in them, and some are fixed by that step alone.
Why the lookup budget runs out as you grow
The structural reason behind almost every over-limit record is the same: SPF was designed when most organisations sent mail from one or two places, and the modern stack sends from a dozen. Each new SaaS tool that emails on your behalf — a CRM, a help desk, a marketing platform, a billing system, a survey tool — wants its own include, and the includes nest. Google Workspace alone consumes 3–4 lookups; a few more services and you hit the cap. The budget that felt generous at three senders is gone by the time you reach the seventh, and nothing in the process warns you as you cross the line.
The data bears out how mechanical this is. The single most common mechanism in SPF records is the include, and it is precisely the one that costs lookups and nests unpredictably, because the provider's own record can grow without telling you. AutoSPF (Apr 2026) attributed 41% of SPF failures across a sample of fast-growing domains to exceeding the 10-lookup limit. The pattern is consistent: the more third-party services an organisation adopts, the faster it burns through its lookup budget, and the teams that hit the limit are usually the ones growing fastest and adding tools most aggressively.
That framing points straight at the durable fix. If the problem is that includes accumulate and nest beyond your visibility, the answer is not to freeze your sending infrastructure or to hardcode a snapshot of IPs that will drift — it is to put all that complexity behind a single delegated record you control, so the root stays at one lookup no matter how many services sit behind it. Growth stops being a countdown to PermError and becomes an edit in one maintained place.
The honest fix
Flattening versus delegation, plainly
Both cut the lookup count. Only one of them stays fixed.
The vendor-dominated search results around this topic push flattening hard, often as a paid service,
because it is the fix that needs a tool. We will say the less profitable thing: most domains do not
need a flattening subscription. Prune the includes you stopped using, drop ptr, convert
your own static servers to ip4, and if you still have genuine complexity, delegate it to
a subdomain. Reach for flattening only when delegation truly is not an option, and if you do, wire up
the automation that keeps it from silently rotting.
Where we stand
We host sending infrastructure, so we see over-limit SPF records constantly — they are, in our experience and in the published data, the most common single SPF fault. The honest advice costs us a product we could otherwise sell: most teams arriving with a PermError do not need a flattening service, they need an afternoon of pruning and a subdomain delegation, after which the record stays fixed on its own. Trend Micro KB KA-0017579: after migrating off Hosted Email Security, customers who left include:spf.hes.trendmicro.com in their record hit a void-lookup PermError because the stale include resolved empty. Removing the one dead include fixed it.
If you run mail with us, keeping your SPF inside the limit is part of the setup, and we will design the
delegation so it stays static as you add and drop senders. If you self-host or send through an ESP, the
same sequence applies regardless of who runs the servers: confirm the PermError, prune, drop
ptr, convert static senders to direct IPs, and delegate the rest. Count before you add,
and the limit stops being a trap.
Questions
Answered plainly
The questions teams ask when SPF starts failing for no obvious reason.
What is the SPF 10-lookup limit, exactly?
SPF evaluation is capped at ten DNS-querying mechanism lookups per check, plus a separate cap of two void lookups, defined in RFC 7208 §4.6.4. Mechanisms that resolve a name — include, a, mx, ptr, exists, and redirect — each cost at least one lookup, and nested includes count against the same budget. Literal mechanisms (ip4, ip6, all) cost nothing. The limit exists for security and performance: without it, a malicious record could trigger hundreds of DNS queries and turn receivers into a denial-of-service vector.
What actually happens when I exceed it?
Your record returns PermError, and SPF fails for every message you send — not just the mail from the extra service, all of it. The worst part is that it is silent: there is no bounce on your side, so the only evidence is mail going missing and DMARC reports showing permerror. A record that worked fine with three senders breaks the day you add the sixth, and deliverability can degrade for weeks before anyone connects it to the SPF change.
Is PermError the same as an SPF fail?
No, and the distinction matters. An SPF fail means the record was evaluated correctly and the sending IP simply was not authorised. A PermError means the record itself could not be evaluated at all — too many lookups, a syntax error, more than one SPF record, or an unresolvable or circular include. Because DMARC treats a PermError as a fail, a broken record also quietly undermines DMARC alignment if SPF is the path you rely on.
Should I use an SPF flattening tool?
Usually not as your first move, and often not at all. Flattening replaces includes with their resolved IPs, which does cut the lookup count, but those IPs go stale the moment a vendor changes their sending infrastructure — and large providers change ranges regularly. A flattened record therefore needs automated re-flattening and monitoring or it silently breaks. Most teams who prune unused includes and delegate to a subdomain find they no longer need the paid flattening tool they were considering.
Why is subdomain delegation better than flattening?
Because it is static once set up, with no dependency on a third-party tool re-resolving IPs. You point your root record at a delegated _spf subdomain, the root stays at a single lookup, and the delegated record holds the real complexity — but it still uses ordinary includes, so vendors keep the ability to update their own IPs. Flattening fixes the symptom (the lookup count) while taking on the underlying fragility of hardcoded IPs; delegation fixes the structure and leaves the vendors in charge of their own ranges.
Can I just have two SPF records to fit everything?
No — that makes things worse. RFC 7208 requires exactly one v=spf1 TXT record per domain, and publishing two causes a PermError for all your mail regardless of how many lookups each contains. It is one of the most common SPF misconfigurations. Merge every authorised sender into a single record, and if that single record is too long for one DNS string, your DNS provider should split it into multiple strings within the same record — which is different from having multiple records.
SPF failing for no obvious reason?
Tell us your sending domain and we will count your lookups, find the PermError, and design a delegation that stays under the limit as you grow — no flattening subscription required.