The SPF 10-lookup limit (and how to beat it)
RFC 7208 caps SPF DNS lookups at 10. Most organizations hit this limit within months. Here's why, what happens, and how to solve it.
The problem: you can't add vendors without breaking SPF
Imagine you have an SPF record for your domain:
v=spf1 include:google.com include:sendgrid.net include:mailgun.org a mx -all
This is straightforward: Google Workspace, SendGrid, Mailgun, your A record, and your MX records are authorized. That's 5 mechanisms = 5 DNS lookups. You're fine.
Then you add Microsoft 365:
v=spf1 include:google.com include:sendgrid.net include:mailgun.org include:outlook.com a mx -all
Now it's 6 lookups. Still fine.
Six months later, you add Constant Contact (marketing emails), Twilio (transactional), Postmark (customer notifications), and a custom vendor. Suddenly:
v=spf1 include:google.com include:sendgrid.net include:mailgun.org include:outlook.com include:constantcontact.com include:twilio.com include:postmark.com include:custom-vendor.com a mx -all
That's 10 includes + a + mx = 12 lookups. You've exceeded the limit.
Warning
When you exceed 10 lookups, SPF evaluation returns permerror (permanent error). The receiver treats this as an SPF failure. Your legitimate mail might be rejected or flagged as suspicious—and you won't know why unless you manually test your SPF record.
What counts toward the limit
RFC 7208 §4.6.4 specifies which mechanisms trigger a lookup:
| Mechanism | Counts? | Why |
|---|---|---|
ip4:, ip6: |
No | Direct IP addresses; no lookup needed |
include: |
Yes (1 per include) | Requires resolving the included domain's SPF |
a: |
Yes | Requires resolving the A/AAAA record |
mx: |
Yes | Requires resolving MX records |
ptr: |
Yes (deprecated) | Requires reverse DNS lookup; not recommended |
exists: |
Yes (1 per exists) | Macro-based dynamic lookup |
redirect= |
Yes (when used) | Alternative to include; consumes remaining budget |
Additionally, if an SPF record itself includes other SPF records (nested includes), those nested lookups also count against the limit.
Why does the limit exist?
RFC 7208 §4.6.4 imposes the limit to prevent denial-of-service (DOS) attacks. A malicious SPF record with deeply nested includes could cause a receiver's DNS resolver to perform hundreds of lookups, consuming bandwidth and CPU. The 10-lookup cap is a safety valve.
The RFC was written in 2014, when cloud email services and third-party sending platforms were less common. Today, almost every organization uses multiple vendors, and 10 lookups is often too restrictive.
Traditional fixes (and why they don't scale)
Flattening: manual IP consolidation
The "classic" workaround is to flatten your SPF record: instead of using include: for each vendor, manually resolve their SPF records, extract all the IP addresses, and publish them directly in your record:
# Instead of include:sendgrid.net, extract SendGrid's IPs: v=spf1 ip4:66.111.4.0/30 ip4:69.46.85.192/27 ... -all
This works and eliminates the include, reducing lookup count. But flattening has serious drawbacks:
- Manual maintenance: You must monitor each vendor's SPF record and update your own whenever they change their IPs (which they do regularly).
- Brittle: If you miss an update, legitimate mail from a vendor's new IP will fail SPF.
- Scale limitations: Your TXT record has a 255-character limit (often split across multiple strings). Dozens of IP addresses can exceed this.
- Vendor transparency: Many vendors don't publicly list all their IPs. You're working with incomplete data.
- Outdated fast: Vendors add / rotate IPs constantly. Your flattened list stales within weeks.
Manual flattening is a maintenance nightmare. Most organizations that try it eventually give up and accept the lookup limit problem.
Removing vendors (not a real solution)
Some organizations drop ESPs or marketing platforms to stay under the limit. This limits your technology stack and puts email authentication above business needs—backwards.
Using redirect (slightly less bad)
SPF supports a redirect= modifier that points the entire record to another domain:
v=spf1 redirect=mail.example.com
This moves the include list to mail.example.com's SPF record. It reduces your main record's complexity but doesn't solve the underlying lookup budget—you've just moved the problem to a subdomain.
The modern solution: synthetic DNS with pre-flattening
The right way to solve the 10-lookup problem is to flatten at authority time, not at query time. That's what UglyDMARC does:
How it works:
-
You publish one synthetic include:
v=spf1 include:%{ir}.%{v}.%{d}.spf.uglydmarc.com -allThe macros expand to embed the sender's IP in the query domain. For example, sender IP192.0.2.1on your domain becomes:1.2.0.192.in-addr.example.com.spf.uglydmarc.com - In the background, we pre-flatten your entire include tree. We resolve all your vendors' SPF records recursively, extract every IP address and CIDR range, and build a flat set of authorized IPs for your domain. This happens asynchronously—never in the critical path.
-
At query time, we do an O(1) CIDR membership test. When a receiver queries our authoritative DNS, we check: is this sender's IP in the pre-flattened set? If yes, we return
v=spf1 ip4:{sender_ip} -all. If no, we returnv=spf1 -all. - The receiver sees a single-IP response—always within the limit. One include, one IP mechanism = 0 additional lookups. It doesn't matter if your include tree has 50 vendors; the receiver never sees that complexity.
The key insight: we move the flattening work from manual (your ops team) to automatic (our background service), and we move it out of the critical query path.
Why this is better than traditional flattening:
- Zero maintenance: You don't manage IPs. Our system monitors vendor SPF records for changes and refreshes the flattened set automatically.
- Always current: When a vendor adds a new IP, our system picks it up within hours. You're never serving stale data.
- Deterministic: Every query returns the same response (a single IP) within 2ms. No DNS lookup unpredictability.
- Scalable: Add 50 vendors, 100 vendors—it doesn't matter. Your query cost stays O(1).
- Observable: Full logs of which IPs matched, which queries failed, and why. DMARC alignment visibility at query time.
Example: moving to synthetic SPF
Before (hitting the limit):
v=spf1 include:google.com include:sendgrid.net include:mailgun.org include:outlook.com include:constantcontact.com include:twilio.com include:postmark.com include:custom-vendor.com a mx -all
12 lookups. SPF fails with permerror. Legitimate mail is at risk.
After (synthetic):
v=spf1 include:%{ir}.%{v}.%{d}.spf.uglydmarc.com -all
1 lookup, always. Query time is <2ms. Flattening happens in the background; you never worry about vendor IP changes.
When to use synthetic SPF
Use synthetic SPF (like UglyDMARC) if:
- You have more than 6–7 third-party senders (vendors, ESPs, APIs)
- You want to add vendors without manually managing SPF flattening
- Your SPF record is hitting or exceeding the 10-lookup limit
- You want deterministic, fast SPF responses
- You need visibility into which IPs are authorized and which aren't
If you have a simple setup (one mail provider, <5 includes), traditional SPF is fine. But if you're managing multiple vendors or planning to scale, synthetic SPF eliminates the problem entirely.
Going further
Solving the SPF lookup limit is just one piece of email authentication. Once you've beaten the limit with synthetic SPF, your next step is DMARC enforcement to eliminate impersonation. Read about DMARC to understand the full picture, then learn how to interpret DMARC reports as you move from monitoring to enforcement.