SPF evaluation is capped at ten DNS lookups. Go over and a conforming receiver returns permerror, which fails SPF — not just for whoever is spoofing you, but for the mail you actually sent. The limit exists because SPF is evaluated at SMTP time, and RFC 7208 §4.6.4 is blunt about why: without a cap, a malicious record could point a receiving server at an unbounded chain of lookups and use it as an amplifier.
The limit is on lookups performed during evaluation, not on the length of your record or the number of includes you wrote. That distinction is where most of the confusion lives.
What counts as a lookup
| Term | Costs a lookup? | Notes |
|---|---|---|
include: | Yes — one, plus everything inside it | Recursive. The include’s own includes count against your total, not theirs. |
a | Yes | With or without a domain. |
mx | Yes | One lookup, and the MX targets it resolves are capped at 10 separately. |
ptr | Yes | Deprecated by RFC 7208 §5.5. Some receivers ignore it entirely. |
exists: | Yes | The mechanism behind macro-based dynamic SPF. |
redirect= | Yes | A modifier, not a mechanism, but it still costs one. |
ip4: | No | Free. This is why flattening reduces the count. |
ip6: | No | Free. |
all | No | Free. |
exp= | No | Only evaluated on failure, and excluded from the limit. |
The count is yours, not theirs
If you include: a provider whose record contains four more includes, those four come out of your ten. A provider that keeps its record tidy is doing you a favour you cannot see from your own record.
The other limit: void lookups
The same section of the RFC sets a second, separate limit that far fewer tools check: at most two void lookups. A void lookup is one that returns nothing — either NXDOMAIN, or an empty answer with RCODE 0.
Exceeding it is a permerror in its own right. That means a record with six total lookups, comfortably inside the famous limit, can still fail outright because three of them point at nothing. And because the record still looks short, this is genuinely difficult to spot by reading it.
In practice void lookups almost always mean the same thing: an include: for a service you stopped using. The provider decommissioned the record, your SPF still references it, and nothing complained until you added the third one.
A worked example
GitHub's record is a good one to read because it sits exactly on the line — ten of ten, with no headroom at all:
"v=spf1 ip4:192.30.252.0/22 include:spf.protection.outlook.com include:_netblocks.google.com include:_netblocks2.google.com include:mail.zendesk.com include:_spf.salesforce.com include:servers.mcsv.net include:mktomail.com include:sendgrid.net ip4:62.253.227.114 ip4:166.78.69.169 ip4:166.78.69.170 ip4:166.78.71.131 ~all"
Eight includes, so eight lookups before anything recurses. Two of those providers have includes of their own, which brings the total to ten. The four ip4: ranges are free — someone has already done the arithmetic here and used literal ranges for the senders that allowed it.
| Include | Lookups it costs |
|---|---|
| _spf.salesforce.com | 2 |
| sendgrid.net | 2 |
| spf.protection.outlook.com | 1 |
| _netblocks.google.com | 1 |
| _netblocks2.google.com | 1 |
| mail.zendesk.com | 1 |
| servers.mcsv.net | 1 |
| mktomail.com | 1 |
The practical reading: adding one more sending service to this domain breaks SPF for all of it. Not degrades — breaks. That is the position a lot of domains are in without knowing, because nothing warns you at nine.
You can run the same expansion against your own domain with the SPF Inspector, which shows the per-include attribution rather than just the total.
What it looks like when you go over
- DMARC aggregate reports show SPF results of
permerrorrather thanfail. That distinction matters —failmeans “this sender is not authorised”,permerrormeans “your record could not be evaluated”. - Mail that has always been delivered starts landing in spam, usually at some receivers and not others, because not every receiver enforces the limit the same way.
- DMARC starts depending entirely on DKIM, because the SPF half can no longer pass. If DKIM is also missing or misaligned, mail at an enforcing policy gets rejected.
- Nothing in your own logs changes. The failure happens at the receiving end, which is why aggregate reports are the only place it surfaces.
Getting back under
- Remove what you no longer use. Genuinely the first step, and often the only one needed. Every void lookup is a candidate, and so is every include for a service someone set up years ago. Check your aggregate reports for whether anything is still sending through it before you pull it.
- Use literal ranges where the provider publishes stable ones. Some providers document a small, rarely-changing set of IP ranges.
ip4:costs nothing, so moving those out of an include is free headroom — but only where the provider treats those ranges as stable, which most do not. - Consolidate senders. Two marketing tools, two ticketing systems and three transactional providers is four lookups you may be able to spend elsewhere. This is an organisational fix, not a DNS one, and it is usually the most durable.
- Move some senders to a subdomain. The limit is per evaluated domain, so
mail.example.comgets its own ten. Bulk and transactional mail on a subdomain is good practice anyway — it keeps their reputation separate from your corporate mail. - Flatten, if you are going to automate it. Replacing includes with the IP ranges behind them removes the lookups entirely. It also takes a failure mode that was loud and makes it silent — see the trade-offs before you commit to it.
Do not split into a second SPF record
Publishing two v=spf1 records does not give you twenty lookups. It gives you a permerror: RFC 7208 §4.5 allows exactly one, and receivers do not pick the better one. This comes up because two providers each tell you to “add this record” — combine their mechanisms into one record instead.