An SPF record almost always starts out simple: one line with the company mail server and little else. Then come the CRM, the newsletter platform, the ticketing system, the accounting software that sends invoices, and each one asks you to add its own include:. A few years later the record only works on paper. The reason is a precise rule in the SPF specification, often ignored until messages start landing in spam.
The rule: at most 10 mechanisms that query DNS
The SPF specification (RFC 7208) states that evaluating a record must not require more than 10 additional DNS queries. The limit exists so that a badly written, or deliberately crafted, record cannot turn every incoming email into a burst of queries against DNS servers.
These mechanisms and modifiers count toward the limit:
include:aandmxptr(discouraged in any case)exists:redirect=
ip4:, ip6: and all do not count, because they require no query: the address is already written in the record.
The detail that surprises many people is that the count is recursive. If you include a provider's record and that record contains three more include: entries, you have used four lookups, not one. A single cloud mail service can take up three or four on its own.
There is also a second, lesser-known limit: so-called void lookups, meaning queries that return an empty answer or a non-existent domain. The specification recommends tolerating no more than two. An include: pointing to the domain of a discontinued provider is therefore doubly harmful.
What happens when you exceed the limit
When the receiver counts the eleventh lookup, it stops evaluating and returns permerror. It is not an explicit «fail», but for many filters the outcome is equivalent: SPF did not pass. If the domain publishes a DMARC policy and the message is not signed with aligned DKIM, the email may be rejected or quarantined.
The problem is insidious because it is not stable. Providers change their records over time: a record that needed nine lookups yesterday may need eleven today without you touching anything.
Counting lookups by hand
Take an example record:
example.com. TXT "v=spf1 mx include:_spf.mail.example.net include:spf.crm.example.org include:mailing.example.net a:smtp.example.com ~all"
The count goes like this:
mx: 1 lookup (plus resolving the MX hosts, which the specification counts together).include:_spf.mail.example.net: 1, plus those inside the included record.include:spf.crm.example.org: 1, plus any nested ones.include:mailing.example.net: 1, plus nested ones.a:smtp.example.com: 1.
That is already five before opening the providers' records. If each contains two, the total reaches eleven. To check, query each included record with dig TXT name or nslookup -type=TXT name and note the depth, or use one of the many SPF validators that display the full tree.
How to get back under the limit
Remove what you don't need
The first step is always an inventory. In my work I regularly find include: entries for services abandoned years ago, mx mechanisms for domains that don't send from their MX hosts, or an a pointing at the website, which sends no mail. Every entry must be justified: who actually sends with this domain in the Return-Path?
Replace a and mx with explicit addresses
If the internal mail server has stable addresses, writing them as ip4: or ip6: removes the corresponding lookups. The cost is maintenance: if the address changes, SPF must be updated too.
Move services to subdomains
This is the cleanest solution. Many sending platforms let you use a custom bounce domain (for example bounce.example.com) as the Return-Path. SPF is evaluated on the Return-Path domain, not on the one visible in From. The platform's record is then published on the subdomain and uses none of the main domain's lookups. DMARC is still satisfied through relaxed alignment, because the subdomain belongs to the same organizational domain.
Flatten with care
Flattening replaces include: entries with the list of addresses they resolve to. It cuts lookups to zero, but freezes a snapshot: when the provider adds new addresses, your messages from those addresses fail SPF. It only makes sense with a tool that regenerates the record automatically at regular intervals, and never for services that change infrastructure often.
Rely on DKIM
SPF is not the only way to pass DMARC. If every service signs with DKIM aligned to your domain, an imperfect SPF becomes far less critical. That is no excuse for leaving the record broken, but it is why DKIM should be configured everywhere, not only on the main server.
Common mistakes to avoid
| Mistake | Consequence |
|---|---|
Two TXT records starting with v=spf1 |
immediate permerror |
+all at the end |
anyone is authorized to send |
ptr in the record |
slow, discouraged by the specification |
| Includes pointing to non-existent domains | void lookups and possible permerror |
| Record over 255 characters not split | some DNS panels truncate it |
On the last point: a single TXT string cannot exceed 255 characters, but a record can contain several quoted strings, which are concatenated. Many DNS panels split them automatically, others don't.
In short
- SPF allows at most 10 mechanisms that query DNS, counted inside nested
include:entries too. - Exceeding the limit produces permerror, which many filters treat as a failure.
- First remove unnecessary entries, then move third-party services to bounce subdomains.
- Flattening only works if it is automated and refreshed regularly.
- Aligned DKIM on every service makes mail far less fragile to SPF problems.