Every include, a, mx, and redirect in your SPF record costs a DNS lookup, and the budget is ten. Why records break at scale and how to fix them without flattening regret.
SPF records fail in two ways. The obvious way is a missing include when you add a vendor. The insidious way is the opposite: too many includes, quietly accumulated over years of adopting SaaS tools, until evaluation crosses ten DNS lookups and every receiver on the internet starts returning permerror. The record looks fine to the eye. It is broken for everyone, everywhere, at once.
Where the limit comes from
RFC 7208 caps SPF evaluation at 10 mechanisms that trigger DNS lookups: include, a, mx, ptr, exists, and redirect. The plain ip4 and ip6 mechanisms are free. The cap exists to stop SPF being used as a DNS amplification weapon, and it is enforced mid-evaluation: the receiver counts as it resolves, and lookup eleven aborts the whole check with a permanent error.
The counting is recursive, which is the part that surprises people. An include does not cost one lookup; it costs one plus every lookup inside the included record. A single vendor include can silently spend four or five of your ten. You do not control what vendors put in their records, and they change them without telling you, which means a record that passed last quarter can permerror today with no change on your side.
What permerror actually costs you
Permerror is not a soft degradation. For DMARC evaluation, an SPF permerror means SPF contributes nothing, so authentication rides entirely on DKIM. If DKIM is healthy and aligned, mail keeps flowing and the breakage hides in your aggregate reports as a persistent spf=permerror line. If DKIM is absent or broken on any stream, that stream now fails DMARC outright, and at p=quarantine or p=reject it stops arriving.
Fixing a bloated record
Remove before you optimize
Most over-limit records carry dead weight: the ESP you migrated away from last year, the survey tool nobody uses, the mx mechanism on a domain whose MX hosts never send outbound mail. Cross-reference every include against the sources actually appearing in your DMARC aggregate reports. Anything sending no mail comes out. This alone fixes the majority of cases.
Split by subdomain
The subdomain architecture we covered in the domain strategy article pays off again here. Each sending subdomain gets its own SPF record with only its own vendor's include: mail.example.com carries the ESP, billing.example.com carries the invoicing platform. Each record stays trivially under budget, and the envelope domains diverge in a way that also improves alignment clarity. The organizational domain's record shrinks to corporate mail plus -all.
Flattening, with eyes open
Flattening replaces includes with the literal ip4/ip6 blocks they currently resolve to, which costs zero lookups. It works until the vendor changes their IPs, and vendors change IPs without notice, at which point your flattened record silently authorizes the wrong ranges and your mail starts failing SPF. Manual flattening is therefore a time bomb. Automated flattening services re-resolve and republish continuously, which genuinely works, but understand what you bought: a dependency that edits your DNS forever, and a record that authorizes whatever the automation last saw.
The quieter alternative is to stop asking SPF to carry the load. DMARC needs one aligned mechanism, and DKIM alignment survives forwarding, vendor IP changes, and the lookup budget entirely. A sender whose every stream signs with an aligned DKIM key can treat SPF as a supporting check rather than a single point of failure, which is exactly the posture the DKIM-first era rewards.
Keeping it fixed
SPF budget hygiene
- 1
Count lookups in CI or cron
Run an SPF evaluation counter against your domains weekly and alert above 8 lookups, leaving headroom for vendor drift.
dig TXT example.com +short | grep spf1 - 2
Gate new vendor includes
Adding an include is a change request that states its recursive lookup cost, not a copy-paste from onboarding docs.
- 3
Review against DMARC data quarterly
Any include with no matching traffic in aggregate reports for a quarter is a removal candidate.
- 4
Prefer DKIM alignment per stream
Every new sending service gets aligned DKIM as the primary mechanism; its SPF include is a bonus, not a requirement.
The 10-lookup limit is not going away, and vendor sprawl is not slowing down. Audit the record this week, delete the archaeology, split what remains by subdomain, and let DKIM carry alignment. SPF works best as the simple mechanism it was designed to be, authorizing a short list of senders, not as an ever-growing registry of every tool the company ever trialed.
Frequently Asked Questions
Do ip4 and ip6 mechanisms count against the limit?
Why does my checker show a different count than my vendor's docs?
Is the void lookup limit separate?
Should I ever use the ptr mechanism?
Key Takeaways
- SPF evaluation aborts with permerror after 10 DNS lookups, counted recursively through every include
- Vendor includes cost what their nested records cost, and vendors change them without notice
- Permerror removes SPF from DMARC evaluation entirely, leaving DKIM as the only aligned mechanism
- Fix order: delete dead includes, split records by sending subdomain, then consider automated flattening as a managed dependency
- Weekly lookup counting plus quarterly review against DMARC report traffic keeps the budget honest
Related articles
2025 in Email: The Year Authentication Became Mandatory Everywhere
Microsoft joined the mandate, Gmail moved to hard rejection, and DMARCbis reached the finish line. What 2025 changed for senders and what it sets up for 2026.
Greylisting, Tarpitting, and Other Receiver Defenses Senders Should Understand
Receiving servers defend themselves with deliberate delays, temporary rejections, and traps for impatient senders. How each defense works and how a good MTA passes them.
Email Address Internationalization: SMTPUTF8 and EAI in Practice
Addresses like 佐藤@例え.jp are valid email. How EAI and SMTPUTF8 work, who supports them, and what sending systems break when a Unicode address shows up.