The SPF 10-lookup limit explained
An SPF check may run at most 10 terms that need a DNS query, and every include inside an included record counts too. Go past 10 and the check stops with a permanent error (permerror) instead of a pass, so SPF no longer vouches for your mail and some receivers reject it. The usual cause is one include per sending service, each pulling in more includes of its own. The fixes are to remove what you no longer use, move services to their own subdomains, list your own servers by IP address, and flatten only with care.
What counts toward the limit
RFC 7208, section 4.6.4 names the terms that make the receiver query DNS while it evaluates your record, and requires receivers to stop at 10 of them:
| Term | Counts toward 10? |
|---|---|
include: | Yes, plus every lookup inside the included record |
a, mx | Yes |
ptr | Yes (and it should not be used at all) |
exists: | Yes |
redirect= | Yes, plus the lookups in the record it points to |
ip4:, ip6: | No |
all | No |
exp= | No (its lookup happens later, only to build an error text) |
Two sub-limits sit on top of the main count. An mx term counts once, but the mail hosts it returns may lead to at most 10 address (A or AAAA) lookups, or the term itself returns permerror. A ptr term has the same cap of 10, though there the extra names are ignored instead of causing an error. Section 5.5 says ptr should not be published at all: it is slow, less reliable when DNS has errors, and it loads the reverse DNS servers.
The void lookup limit
The same section adds a smaller limit. A void lookup is a query that returns no records, or a reply that the name does not exist. Receivers should allow only two and return permerror on the third. A stale a: or mx: term that points at a retired host name is one way to collect them.
Other ways to get permerror
The lookup limits are not the only route to a permanent error. RFC 7208 also returns permerror when:
- the domain publishes more than one record starting with
v=spf1(section 4.5); - the record has a syntax error anywhere in it (section 4.6), such as an impossible network size like
ip4:192.0.2.0/33. A typo such asinclude=instead ofinclude:is worse in a quiet way: it reads as an unknown modifier, which section 6 says receivers must ignore, so the include is skipped without any error and that service's mail fails SPF; - an
include:points at a domain that has no SPF record (the table in section 5.2).
Section 8.7 calls permerror a condition that needs the DNS operator to step in. Retrying will not fix it.
How to count your lookups
Count every lookup term in your own record, then open each include and redirect target and count its terms too, all the way down. Take this record:
example.com. TXT "v=spf1 ip4:192.0.2.10 include:_spf.mail.example.net include:spf.crm.example.org mx -all"ip4:192.0.2.10costs nothing.include:_spf.mail.example.netcosts 1. If that record holds three more includes, add 3: 4 so far.include:spf.crm.example.orgcosts 1, and its record holds onlyip4:ranges: 5.mxcosts 1: 6 lookups.-allcosts nothing.
Microsoft's SPF setup guide gives the same arithmetic: an include whose record holds three more includes adds four lookups in total. It also explains why an over-limit record can fail only some of the time. Receivers read the record from left to right and stop at the first match, so mail from a server listed early can still pass, while mail from a server listed late fails, and the bounce may say the message required too many lookups. A few passing messages prove nothing.
The SPF checker follows every include for you and shows the total number of lookups.
Why vendor includes add up
Every service that sends as your domain asks for its own include:: the mailbox provider, the newsletter tool, the CRM, the help desk, the billing system. That is what include is for: pointing at servers run by independent organizations (RFC 7208, section 5.2). But each included record is maintained by its vendor and may be a chain of further includes, so a vendor that adds one to its own record can push you over the limit without any change on your side.
How to get back under 10
Remove includes you no longer use
Start with an inventory of what really sends as your domain. Microsoft recommends a DMARC reporting service for finding your mail sources (DMARC aggregate reports). Delete the includes of services you have stopped using, and a or mx terms for hosts that no longer send.
Also check which domain each service puts in the envelope sender (the Return-Path header of a message it sent). SPF checks that domain, not the From: address people see. A service that uses its own bounce domain is checked against its own SPF record, so your include for it never comes into play for that mail. Before you delete such an include, make sure the service signs with DKIM for your domain, so that DMARC can still pass.
Move services to subdomains
Give each service you do not run yourself a subdomain, such as news.example.com for the newsletter. Each subdomain has its own SPF record and its own budget of 10 lookups. Microsoft recommends this for bulk mail services, partly to keep their reputation separate from your staff's mail. The subdomain needs its own record, because the record of example.com does not cover it.
example.com. TXT "v=spf1 ip4:192.0.2.10 include:_spf.mail.example.net -all"
news.example.com. TXT "v=spf1 include:spf.newsletter.example.org -all"This keeps DMARC working. Under DMARC's default relaxed alignment, a bounce domain like news.example.com aligns with a From: address at example.com, because both share the organizational domain (RFC 9989, section 4.4).
List your own servers by IP address
ip4: and ip6: cost no lookups. For servers you control, RFC 7208 (section 10.1.1) calls a record that lists the addresses directly the best option and mx the expensive one. Section 10.1.2 names the trade-off: a and mx let you renumber hosts without touching SPF, at the cost of a lookup for every receiver. Unless your addresses change often, list them.
SPF flattening and its risks
Flattening replaces an include: with the IP addresses the included record lists today. The lookups are gone, but your record now holds a copy nobody updates for you: when the vendor changes its sending addresses, mail from the new ones fails SPF until you notice.
Microsoft's guide limits flattening to vendors with stable, documented address ranges, and says not to flatten services whose sending addresses change often, its own Microsoft 365 include among them. If you flatten anyway, it asks you to document what you replaced and when, watch the vendor's announcements, review the entries at least quarterly and test SPF after each change.
Flattened records also get long. RFC 7208 (section 3.4) advises keeping the record small enough that the DNS answer fits in 512 octets, roughly 450 octets for the name and records together, because larger answers can be silently ignored by some receivers. A single record can be split into several quoted strings of up to 255 octets each, which receivers join without spaces (section 3.3).
Let DKIM carry DMARC
DMARC passes when either SPF or DKIM passes for a domain aligned with the From: address (RFC 9989, section 4.4). Mail that is signed with DKIM for your domain still passes DMARC on a day when SPF breaks, so setting up DKIM for each service is the safer long-term fix. See DKIM selectors and key rotation.
One record per domain
A domain must not publish two SPF records (RFC 7208, section 3.2). When a new service tells you to "add this SPF record", merge its include: into the record you already have, before the final all. Two records starting with v=spf1 give every receiver a permerror, even if each record is fine on its own.
Every name that sends mail needs its own record, subdomains included. A domain that sends no mail at all should publish v=spf1 -all, which section 10.1.2 calls a well-established best practice. The SPF generator writes a single, merged record from the services you pick.
What the all qualifiers mean
The last term says what happens to a server the record does not list. Its prefix, the qualifier, decides the result (RFC 7208, sections 4.6.2 and 8):
| Ending | Result | What receivers do with it |
|---|---|---|
+all (or plain all) | pass | Any server on the internet passes. Never publish it. |
?all | neutral | Treated exactly like having no SPF record. |
~all | softfail | Probably not authorized; receivers should not reject on this alone. |
-all | fail | Not authorized; what happens next is the receiver's policy. |
A record without any all ends in neutral, as if ?all were there (section 4.7). Terms after all are ignored, and so is a redirect= in the same record (section 5.1). Microsoft recommends -all once DKIM and DMARC are in place, and keeps ?all for testing only. Gmail's SMTP error list uses 550 5.7.26 for mail that fails SPF under a -all record, and 550 5.7.27 for a bulk sender's SPF failure. Other servers report a failed check as 550 5.7.23 and an SPF error, such as an over-limit record, as 5.7.24 (RFC 7372, section 3.2).
Questions
Can I split SPF into two TXT records to get more lookups?
No. Two SPF records are a permerror on their own, and the limit counts lookups across the whole evaluation, not per record. What you can split is one long record into several quoted strings inside a single TXT record. To get a fresh budget, use a subdomain.
Should I end with ~all or -all?
What happens next is up to the receiver. RFC 7208 says a softfail alone should not lead to a rejection, while a fail may. -all is the clearer statement once you are sure every service is listed. If you are still finding senders, ~all with DMARC reports is a reasonable step. See moving DMARC from none to reject.
Try it
Run your domain through the SPF checker to see the lookup total, and test an address you mail with the free email checker.
Related
- SPF generator and DMARC checker.
- Bounce code 550 5.7.23: SPF check failed.
- Glossary: SPF, DKIM and DMARC.
Related guides
- DKIM selectors and key rotation
- Moving DMARC from p=none to p=reject
- How to read DMARC aggregate reports
- Gmail and Yahoo sender requirements
- Outlook.com requirements for high-volume senders
- All guides
Sources
- RFC Editor: RFC 7208, Sender Policy Framework (SPF): lookup and void lookup limits (4.6.4), record selection (3.2, 4.5), record size (3.3, 3.4), qualifiers and results (4.6.2, 4.7, 5.1, 8),
includeandptr(5.2, 5.5), record design (10.1). - Microsoft Learn: Set up SPF to identify valid email sources: counting nested includes, partial failures, subdomains, flattening advice,
-alland?all. - RFC Editor: RFC 9989, DMARC: alignment and its relaxed default (4.4, 4.7).
- Google: Gmail SMTP errors and codes: 550 5.7.26 and 550 5.7.27.
- RFC Editor: RFC 7372, Email Authentication Status Codes: the 5.7.24 SPF error code (3.2).
How we wrote this: drafted by the email-validation.org team with the help of AI writing tools, from our product documentation and the sources linked in the text.