An SPF record that needs more than ten DNS lookups is broken, but not in the way most guides describe it. Evaluation stops at the first mechanism that matches the connecting server, so a sender matched by the second term never reaches the twelfth. Going over the limit means the record can return a permanent error, and does for every sender not matched earlier in the record. Order decides who is affected.
That distinction matters because it changes the fix. If the limit broke every message you would notice immediately. Instead it breaks the senders sitting at the end of your record, which is usually the platform you added most recently and check least often.
include tells the receiver to go and check another record
The include mechanism does not copy another domain's record into yours. It tells the receiving server to evaluate that other domain's SPF record as a separate check and use the result: if the connecting IP passes there, the include matches and evaluation stops.
v=spf1 include:_spf.google.com include:sendgrid.net ~all
That record says: check Google's list, then check SendGrid's list, then apply ~all to anything that matched neither. Each include hands the receiver a fresh record to parse, which may itself contain more include terms, which is where the budget goes.
The word is misleading in one specific way. include returns only a pass or a no-match. A -all inside an included record does not reject your mail; it simply means that record did not match, and evaluation continues with your next term. Only the all in your own record decides the outcome.
Five mechanisms spend the budget, and three are free
RFC 7208 section 4.6.4 caps a single SPF evaluation at ten DNS-querying terms. Exceeding it is a MUST-level permanent error, not a soft warning.
| Term | Costs a lookup |
|---|---|
include | Yes, plus everything inside it |
a | Yes |
mx | Yes |
ptr | Yes, and it is deprecated |
exists | Yes |
ip4 | No |
ip6 | No |
all | No |
exp | No |
Two details in that table are commonly got wrong. An mx term costs one lookup for the term, not one per MX record returned, and a record listing a hundred IP addresses with ip4 spends nothing at all. That second point is the whole basis of SPF flattening.
The %{p} macro is also charged to the same budget of ten, because expanding it requires a reverse lookup.
The count is of terms evaluated, not of domains named
This is the rule that makes a record cost more than it looks like it costs. The limit counts every DNS-querying term a receiver evaluates during one check, and a domain reached down two different branches is evaluated twice.
If your record includes two vendors and both of them include the same shared infrastructure domain, that shared domain's entire subtree is paid for twice. Counting distinct domain names gives you a number lower than the one a receiver will reach, which is why a record that looks like six lookups on paper can permerror in practice.
Two related cases have the same flavor:
- A loop is a permanent error, not a branch to skip. If A includes B and B includes A, a receiver does not quietly stop; it fails the record.
- Terms after
allare never evaluated.allalways matches, so anything following it is dead code and costs nothing. Aredirectsitting beside anallis inert for the same reason.
Over the limit fails the senders at the end of your record
A receiver evaluates your terms in order and stops at the first match. That produces three different outcomes from one broken record, depending on who is sending.
- A sender matched before the budget runs out passes normally. Nothing is wrong for them, and nothing appears in your logs.
- A sender matched after the tenth lookup gets a permanent error. Most receivers treat a permerror as a failure rather than as a missing record, so DMARC sees no SPF pass.
- A sender not in your record at all hits your
allterm, exactly as designed, if evaluation survives that far.
So the practical symptom is one platform's mail suddenly failing authentication while everything else looks fine. If that platform was added to the end of the record, it is the first to fall off the edge.
This also means a fix can be free. Moving your highest-volume sender to the front of the record does not reduce the total, but it does mean that sender is matched before the budget is spent. It buys time rather than solving the problem.
How to find the real number
Counting by hand is unreliable because you cannot see inside a vendor's record without resolving it, and vendors change theirs without telling you. Run the domain through a free SPF checker, which walks the whole tree, resolves each include recursively and reports the total a receiver would actually reach rather than the number of terms you typed.
What to look for in the result:
- The total against the limit of ten. Anything over means the record can permerror.
- Which branch is expensive. One vendor is usually responsible for most of the cost, and vendors differ enormously.
- Duplicated subtrees. Shared infrastructure reached twice is the commonest hidden overrun.
- Void lookups. A term resolving to nothing has its own separate budget, and an
includepointing at a domain with no SPF record is a permanent error on the first occurrence. - Anything after
all. Dead terms are harmless but they are usually a sign the record has been edited by several people who did not read it.
Across the domains we test for the Unspam 2026 Email Deliverability Benchmark, 93% publish a valid SPF record, so the common failure in this area is not an absent record but a record that has quietly outgrown its budget.
Four ways to get back under ten
In rough order of how much you should prefer them.
Remove what you no longer use. Most overrun records carry a vendor nobody has sent through in two years. This is free, it is permanent, and it is almost always available.
Replace an include with ip4 ranges where the vendor publishes stable ones. An ip4 term costs nothing. The catch is that you now own the maintenance: when the vendor changes IPs, your mail fails and nobody tells you. Only do this where the vendor documents a stable range and commits to notifying changes.
Move sending to a subdomain. Your marketing platform can send from mail.example.com with its own SPF record and its own budget of ten. This is the structural fix, it scales, and it has the side benefit of separating reputations.
Flatten the record with a service. SPF flattening resolves every include and publishes the resulting IP list for you, refreshing it automatically. It works, and it makes a third party's uptime a dependency of your mail authentication. Treat it as the last option rather than the first.
If you are building a record rather than repairing one, an SPF record generator produces a correct starting record, and our guide to SPF records covers the syntax in full.
What to check once the record is under the limit
SPF passing is necessary but it is not enough on its own, because DMARC needs the SPF domain to align with the address in your From header, and SPF breaks on forwarding no matter how tidy the record is. Our guide to email authentication covers how the three records depend on each other and why DKIM, not SPF, is the one that survives a mailing list.
Re-check the total whenever you add a sending platform, and again a few months later even if you changed nothing, because the count includes vendors' records and those move under you. To see what a real message looks like at a receiver, including the SPF result next to everything else that decides placement, run a free spam test.