You add one include to your SPF record for a new e-signature tool. The record still parses. Your DNS host accepts it without complaint. Three weeks later, invoices from your billing platform start bouncing at one customer's mail server, while the same platform delivers fine everywhere else. Nobody touched the billing platform. The thing that changed was the number eleven.
SPF has a processing limit that most people administering it have never heard of, and the failure it produces looks nothing like a record that is too long. RFC 7208 states the rule directly: SPF implementations must limit the total number of terms that cause DNS queries to 10 during evaluation, to avoid unreasonable load on the DNS, and if that limit is exceeded the implementation must return permerror. That is a MUST. A conforming receiver is not permitted to be generous about it.
Which terms count, and which do not
The limit is not a limit on includes, on characters, or on senders. It is a limit on terms that cause DNS queries during evaluation. RFC 7208 names them exactly: the include, a, mx, ptr, and exists mechanisms, and the redirect modifier.
The RFC is equally specific about what is free. The other terms — the all, ip4, and ip6 mechanisms, and the exp modifier — do not cause DNS queries at the time of SPF evaluation, and their use is not subject to this limit. So a record can carry two hundred ip4 ranges and spend nothing from the budget. A record with eleven includes and nothing else is already broken.
Two sub-limits sit on top of the ten. For mx, the evaluation of each MX record must not result in querying more than 10 address records, and if that limit is exceeded the mx mechanism must produce a permerror. For ptr, the same ten-address ceiling applies, but the consequence differs: all records other than the first 10 must be ignored. The RFC explains the asymmetry plainly, noting that the contents of MX records are under the control of the domain publishing them, while the contents of PTR records are under the control of whoever owns the IP address actually making the connection.
The count is not visible in your record
This is the part that catches experienced administrators. An include does not paste another record's contents into yours. RFC 7208 says the include mechanism triggers a recursive evaluation. Each vendor's record is evaluated as its own record, and every DNS-querying term inside it draws from the same budget of ten. A single include line in your record may cost one lookup or six, depending on what that vendor publishes today.
You do not control that number. Your vendors do, and they change it without telling you. Your record can sit untouched for a year and drift from nine lookups to twelve because a provider reorganized their own includes.
What a receiver does with permerror
RFC 7208 defines the result as an operator problem: a permerror result means the domain's published records could not be correctly interpreted, and signals an error condition that definitely requires DNS operator intervention to be resolved.
Microsoft documents what this looks like in practice. Its guidance states that if the number of DNS lookups is greater than 10, the message fails SPF with a permanent error, and the destination email system rejects the message in a non-delivery report with one of two errors: that the message exceeded the hop count, or that the message required too many lookups. Neither string contains the word SPF. An administrator reading that bounce has no obvious reason to open DNS.
Where the message actually ends up varies by receiver. RFC 7208 says that if the message is rejected during the SMTP transaction for this reason, the software should use an SMTP reply code of 550 and, if supported, the 5.5.2 enhanced status code. But the same document is candid that this is a judgment call. It lays out rejection and delivery-with-annotation as competing options, observes that a defective SPF implementation returning spurious permerrors would notify only the sender, and concludes that the choice is often a matter of local policy rather than a condition with a universal solution. Some receivers bounce. Some deliver and log. That inconsistency is the first source of intermittency.
How permerror behaves under DMARC enforcement
On its own, an SPF permerror is survivable at many receivers. Under a DMARC policy of quarantine or reject, it stops being survivable.
DMARC was updated in May 2026 by RFC 9989, which obsoletes RFC 7489. The evaluation model is explicit. For SPF, the preserved results must include pass or fail. Alignment is then checked only against results that passed, since the specification refers to the SPF-authenticated identifier only where there is an SPF pass result for the message being evaluated. A permerror is not a pass. It produces no authenticated identifier at all.
What follows is mechanical. RFC 9989 states that if no authenticated identifiers exist for the domain, or none of them align with the author domain, the message is considered to fail the DMARC mechanism check. Your SPF result has been removed from the equation. DMARC now rests entirely on DKIM. If the message carries an aligned DKIM signature that verifies, it still passes and you never notice. If it does not — a forwarded message, a mailing list that rewrote the body, a sender nobody got around to signing — DMARC fails, and the policy you published is what handles it.
This is why a lookup-limit problem so often surfaces as a complaint that the marketing mail went to junk, rather than as a DNS ticket. SPF quietly stopped contributing months ago, and DKIM was carrying every message until one stream came along that DKIM could not carry.
The void-lookup limit and the size limits
Three other limits sit alongside the ten, and they fail differently.
The first is the void-lookup limit. RFC 7208 defines void lookups as terms for which DNS queries return either a positive answer with an answer count of zero, or a name error answer. The rule reads that SPF implementations should limit void lookups to two, that an implementation may choose to make such a limit configurable, that a default of two is recommended, and that exceeding the limit produces a permerror result. Note the should and the may. This limit is genuinely optional and genuinely configurable, which means two receivers can evaluate the same record and legitimately disagree about it. In practice, void lookups accumulate from includes pointing at services that were canceled, where the vendor has since removed the record.
The second is the character-string limit. A DNS TXT record is built from character-strings capped at 255 octets each. RFC 7208 notes that TXT records containing multiple strings are useful in constructing records that would exceed that maximum, and that when a record contains multiple strings the record must be treated as if those strings are concatenated together without adding spaces. Concatenated without spaces is the detail that bites: a split made in the wrong place silently fuses two mechanisms into one unparseable token. Google's documentation states the limit for administrators as 255 characters, with the TXT record no larger than 512 bytes.
The third is total answer size. RFC 7208 says the record should remain small enough that the results of a query for it will fit within 512 octets, and offers 450 octets of DNS message as the working guideline. It warns that records too long to fit in a single UDP packet could be silently ignored by SPF verifiers due to firewall and other issues that interfere with the operation of DNS over TCP. Silently ignored is the operative phrase.
One more failure mode belongs in this group. Publishing two SPF records is not additive. Per RFC 7208, if the resultant record set includes more than one record, evaluation produces the permerror result. Microsoft states the same rule for its tenants: only one SPF record is allowed per domain or subdomain, and multiple records cause SPF to return permerror because the receiving system cannot determine which record to evaluate.
Why the breakage is intermittent, and why checkers miss it
Here is the trap. SPF is evaluated left to right, and it stops early. RFC 7208 says each mechanism is considered in turn from left to right, and that if it matches, processing ends and the qualifier value is returned as the result of that record.
An over-budget record therefore does not fail for everyone. It fails for whoever sits past the tenth lookup. Mail from a provider listed second in your record matches at lookup two, returns pass, and never touches the terms that would have blown the limit. Mail from the provider you appended last requires the evaluator to walk the whole chain, hit eleven, and return permerror. The same record, the same day, the same receiver: one sender authenticates and another does not, purely because of position in the record.
Layer on the other variables. Void-lookup enforcement is a should, so some receivers count it and some do not. Vendor records expand to different totals on different days as those vendors add or remove their own includes. Local policy on permerror handling differs by receiver. The result is a failure that moves around, by sender, by recipient domain, and by week.
And the record itself looks fine. It is short. It is syntactically valid. It contains no typos. Many online checkers report it as valid, because they resolve the chain at one instant and report what they found at that instant. A check run on Tuesday can come back clean and the same check on Friday can come back over the limit with nobody having edited anything. There is no lint error to find, because nothing about the text of your record is wrong.
The remedies, and what each one costs
Remove what is no longer sending
This is unglamorous and it is usually the highest-yield step. Most over-budget records contain includes for a webinar tool from years ago, a ticketing system that was replaced, and a CRM that was migrated two acquisitions ago. Each one may be costing one to four lookups, and the dead ones are also the likeliest source of void lookups, which can trip a separate permerror on their own. The cost of this remedy is the audit work of confirming that a sender is genuinely retired before its authorization is removed.
Give each sender its own subdomain
This is the only remedy that raises the ceiling instead of squeezing under it. Because the budget is scoped to the domain being evaluated, moving a sender to its own subdomain gives it a fresh allowance. Microsoft's guidance states it plainly: each subdomain has its own ten-lookup budget. Independent syntax references describe the same approach, noting that each discrete subdomain is afforded its own ten-lookup maximum.
Microsoft offers a second reason to do this that has nothing to do with lookups. For email services outside your direct control, such as bulk email platforms, it recommends a subdomain rather than the main domain, so that problems with mail sent from those services do not affect the reputation of mail sent by employees on the primary domain.
The cost is real. The sending platform has to support a custom envelope domain, and the DKIM and DMARC configuration has to follow the mail to that subdomain. Alignment is where this goes wrong: a subdomain that sends with a header From address on the parent domain buys nothing under strict alignment, and the SPF budget you freed up does not help if the identifier no longer lines up.
Flattening, and why it is a standing liability
Flattening means resolving your includes yourself and publishing the resulting ip4 and ip6 ranges directly. It works, because ip4 and ip6 cost nothing against the limit. Microsoft lists it as an option with a condition attached: replace a vendor's include with ip4 or ip6 values if that vendor has a stable, documented set of sending addresses, and understand that the approach requires you to monitor the vendor for IP address changes.
That condition is the whole problem. Flattening converts a pointer that updates itself into a copy that does not. When the vendor adds a sending range, their include reflects it the moment they publish it, and your flattened record does not. Nothing alerts you. Published guidance on the practice puts it directly: a flattened SPF record quickly becomes outdated unless you manually track every change, and every time a provider updates their infrastructure you must revise your record. It also names the diagnostic cost. Once includes have become bare IP addresses, you lose visibility into which addresses belong to which service, which makes the next investigation harder than it needed to be.
Flattening also trades one limit for another. The expanded ranges have to fit inside the 255-octet strings and the 450-to-512-octet answer size, so a heavily flattened record starts pressing against the size guidance, and a string split in the wrong place fuses mechanisms together. The honest framing is that flattening is not a fix. It is a recurring maintenance obligation with a silent failure mode, and taking it on means committing to monitoring that actually compares your published ranges against the vendor's current ones on a schedule.
Macros and delegation
RFC 7208 supports macro expansion, and the exists mechanism is built for it. The RFC's own example uses an exists term whose macro string expands the reversed client IP and the local part into a queryable name. The RFC notes this makes fine-grained decisions possible at the level of the user and client IP address, and a single exists term costs one lookup regardless of how many addresses it ultimately covers.
The catch is that you then have to operate a DNS zone that answers those queries correctly for every legitimate sender, forever. RFC 7208 also warns that a permerror can arise from macro use when the checked identities have an unexpected format, so the failure mode moves rather than disappearing. Macro-based records are additionally opaque to whoever inherits them. Delegated approaches, where a third party hosts and maintains the record your domain points at, shift the maintenance burden to an organization whose job it is, at the price of handing a vendor ongoing authority over which servers may send as your domain.
Whichever path gets chosen, the durable change is to stop treating the lookup count as a setup-time detail. It is a running number that several organizations outside your control can move, and nothing in DNS raises a flag when they move it. The record that sat at nine lookups the day it was built is the same record that will sit at eleven after a vendor reorganizes their infrastructure, and the first evidence of it will be a bounce message about hop counts, arriving weeks late, from one customer, about one sender.
