SPF Too Many DNS Lookups: Fixing the 10-Lookup Limit
One include can cost seven of SPF's ten DNS lookups. What counts toward the limit, what a permerror does to DMARC, and how to get back under it safely.
You add one more vendor to your SPF record. The DNS change goes through and nothing bounces. A week later the aggregate reports show SPF failing for mail from Google Workspace, a sender nobody has touched in years.
The record is not wrong. It is too expensive. SPF caps every evaluation at 10 DNS lookups, and the new include pushed yours to 11. Past the cap, receivers stop evaluating and return permerror for every message the domain sends, not only for mail from the new vendor.
What counts toward the limit
RFC 7208, section 4.6.4 names the terms that cost a lookup: the include, a, mx, ptr, and exists mechanisms, and the redirect modifier. Implementations "MUST limit the total number of those terms to 10", and past that they "MUST return permerror".
Three details catch people out:
- Nested terms count. An
include:costs one lookup, plus every lookup inside the record it points to, all the way down. ip4,ip6, andallcost nothing. They do not query DNS at evaluation time.- Void lookups have their own limit of two. A term that resolves to no record, such as an include for a vendor that retired its SPF hostname, counts against it. A third void lookup also returns
permerror.
What one include actually costs
Vendor setup guides tell you which include to add. They do not tell you what it costs. We resolved common vendor includes on October 14, 2026:
| Include | Lookups |
|---|---|
_spf.google.com (Google Workspace) |
1 |
spf.protection.outlook.com (Microsoft 365) |
1 |
mail.zendesk.com |
1 |
amazonses.com |
1 |
_spf.salesforce.com |
2 |
sendgrid.net |
2 |
mailgun.org |
5 |
email.freshdesk.com |
7 |
The largest providers publish flat lists of ip4: and ip6: ranges, so they cost one lookup each. Others nest. The include Freshdesk documents chains to SendGrid and to four regional records of its own: seven lookups for one line of your record. Add Google Workspace and Mailgun and this record needs 13:
v=spf1 include:_spf.google.com include:email.freshdesk.com include:mailgun.org ~all
These numbers change whenever a vendor restructures its record, and nobody tells you. Treat the table as a snapshot and measure your own record. The SPF checker walks the whole include tree and counts both limits.
What permerror does to DMARC
DMARC passes when SPF or DKIM passes and aligns with the domain in From: (RFC 9989). A permerror is not a pass, so once the record is over the limit, SPF contributes nothing for any message. Every sender now depends on DKIM alone.
Mail from services that sign with your domain keeps passing. Mail that relied on SPF fails DMARC: an on-premises relay, a scanner that sends as scans@, a vendor whose DKIM was never set up. At p=none nobody notices. At p=quarantine or p=reject it is lost mail.
The aggregate reports show it before anyone complains. Look for this in the auth_results block:
<spf>
<domain>example.com</domain>
<result>permerror</result>
</spf>
A permerror on your own domain across every source points at the record itself: the lookup limit, a syntax mistake, or two SPF records at the same name, which section 4.5 also treats as permerror. Reading DMARC aggregate reports covers the rest of the report.
Cut the record before you flatten it
Most records over the limit carry weight they do not need. Work through these first. Each one removes lookups without adding anything to maintain.
Remove includes for vendors you no longer use. Every record has one: the marketing platform from three years ago, the helpdesk that was replaced. Thirty days of aggregate reports tell you which sending sources are real. An include whose ranges never appear as a source is a candidate for removal.
Remove includes that are never evaluated. SPF checks the domain in the envelope sender (MAIL FROM), not the visible From: (RFC 7208, section 2.4). Many platforms send with a bounce address on their own domain. For that mail, receivers check the vendor's SPF record and never read your include. In the reports, the SPF domain for that source is the vendor's domain, not yours. Those messages pass DMARC through aligned DKIM or not at all, so the include costs lookups and buys nothing.
Replace a, mx, and ptr with addresses. For servers you run, ip4: and ip6: cost nothing. ptr should not be in the record at all: section 5.5 says it "SHOULD NOT be published". An mx term is often left over from a default record a DNS host generated. If Google or Microsoft hosts your inbound mail, their include already authorizes your outbound mail, and the mx term spends a lookup on servers that receive mail for you, not servers that send it.
Give a high-volume sender its own subdomain. Most sending platforms support a custom return-path, usually a CNAME such as bounce.example.com that points at the vendor. SPF for that mail is then evaluated on the subdomain, with its own budget of 10 lookups, and costs nothing on your apex. Under relaxed alignment, the DMARC default, bounce.example.com aligns with example.com in From:. This is the one fix that also improves alignment, which the third-party sender guide covers in detail.
Flattening, and why a static copy goes stale
Flattening replaces each include: with the ip4: and ip6: ranges it resolves to, and the lookups for those vendors drop to zero. It also brings four problems of its own.
The copy does not follow the vendor. When a provider adds a range, its own record changes the same day. Your flattened copy does not, and mail from the new range fails SPF with no error anywhere. A hand-flattened record works on the day it is published and decays from then on.
Macros cannot be flattened. Salesforce publishes v=spf1 exists:%{i}._spf.mta.salesforce.com -all. The %{i} macro expands to the connecting IP address, so authorization is decided per message, at query time. There is no list of ranges to copy. Keep that include as it is.
Size. RFC 7208 asks for SPF answers under 450 octets so they fit in a UDP response (section 3.4), and a single TXT string holds at most 255 characters (section 3.3). The Freshdesk tree alone resolves to dozens of ranges. A flattened record for a real domain ends up split across several chained records on subdomains you now maintain.
Two records. Pasting a flattened record next to the old one leaves two v=spf1 records at one name, which is a permerror of its own.
Flattening is safe when something re-resolves it on a schedule and republishes when a vendor changes. On the Professional plan, DDMARC does that for you: you list your sending sources, DDMARC resolves them, keeps the ranges current as providers change them, and serves the result from its own zone, so your apex record becomes a single include. Whatever does the flattening, check how it handles macro-based records like Salesforce's before you rely on it.
The order we work in
- Lower the TTL first. Drop the SPF record's TTL to 300 seconds at least one old TTL before the change, often a day ahead. Resolvers then pick up the new record quickly, and a mistake rolls back in minutes. Raise the TTL again once the reports are clean.
- Count. Run the record through the SPF checker and note both totals: lookups and void lookups.
- Inventory. Pull 30 days of aggregate reports and list every source that sends as your domain, with its SPF domain and result.
- Cut. Remove dead includes, includes that are never evaluated, and
a,mx, andptrterms you can replace with addresses. - Move. Put the highest-volume vendors on a custom return-path subdomain.
- Flatten what is left, with a tool that keeps it current.
- Measure every new include before it is published. Make the lookup count part of vendor onboarding. Most records go over the limit one vendor at a time.
If the inventory turns up senders nobody can account for, that is the work the PlatOps DMARC enforcement sprint does: every legitimate sender found and aligned before the policy moves.
Questions we get asked
The include, a, mx, ptr, and exists mechanisms and the redirect modifier count, including every one of them inside nested includes. The ip4, ip6, and all mechanisms do not. RFC 7208 also limits void lookups, terms that resolve to no record, to two.
A permerror means SPF does not pass for any message from the domain, so DMARC can only pass through DKIM that is signed with, and aligned to, your domain. Mail from senders that sign that way still passes. Mail that depended on SPF fails, and at p=quarantine or p=reject it is filtered or rejected.
Only when something keeps it current. A flattened record is a copy of each vendor's address ranges on the day it was made, and it goes stale when a vendor changes them. Macro-based records, such as Salesforce's, cannot be flattened at all. Remove and move includes first, then use automated flattening that re-resolves the ranges for whatever is left.
Field notes, twice a month.
When a mailbox provider changes the rules, you hear it here with the record you need to change. No digest, no roundup, no product news.
DMARC field notes, twice a month, from PlatOps Security, LLC (Gaithersburg, MD). We use your address only to send it. Unsubscribe with one click in any issue. Privacy policy.