Skip to content
QKQuickKit日本語

SPF DNS lookup limit (10) and how to trace a permerror

SPF turned into permerror after adding an include, or fails only for some senders: how RFC 7208 counts DNS-lookup terms, how to count them by hand, and how to reduce them.

Sources and commands checked:

You added one include: to your SPF record and receivers now report spf=permerror, or SPF fails only for mail from some senders. The first thing to check is the limit RFC 7208 puts on DNS lookups.

The rule (RFC 7208 §4.6.4)

  • The terms that query DNS during evaluation are the include, a, mx, ptr, and exists mechanisms and the redirect modifier. SPF implementations MUST limit the total of those terms to 10 and return permerror when the limit is exceeded.
  • all, ip4, ip6, and exp do not query DNS during evaluation and are not counted. exp can still cause a TXT query for its domain later, to fetch the explanation after the result is fail (RFC 7208 §4.6.4, §6.2); that query does not count toward the limit of 10.
  • The count covers the whole evaluation: terms in the records reached through include: and redirect= add to the same total.
  • In addition, evaluating one mx must not query more than 10 address records (A or AAAA); beyond that, that mx produces permerror.
  • A lookup that returns an empty answer (NOERROR with no records) or a nonexistent name (NXDOMAIN) is a "void lookup", and implementations SHOULD limit them to two. A leftover include: for a service you no longer use can trigger this.

Example: counting

Consider this setup. The domains and addresses are reserved for documentation (domains from RFC 2606, IPv4 from RFC 5737, IPv6 from RFC 3849). The TXT content on each line can be pasted into SPF Record Parser as it is.

example.com.            TXT "v=spf1 mx include:_spf.mail.example.net include:spf.crm.example.org a:relay.example.com -all"
_spf.mail.example.net.  TXT "v=spf1 include:_nb1.mail.example.net include:_nb2.mail.example.net include:_nb3.mail.example.net ~all"
_nb1.mail.example.net.  TXT "v=spf1 ip4:192.0.2.0/26 ip6:2001:db8:1::/48 ~all"
_nb2.mail.example.net.  TXT "v=spf1 ip4:192.0.2.64/26 ip6:2001:db8:2::/48 ~all"
_nb3.mail.example.net.  TXT "v=spf1 ip4:192.0.2.128/26 ip6:2001:db8:3::/48 ~all"
spf.crm.example.org.    TXT "v=spf1 include:spf1.crm.example.org include:spf2.crm.example.org ip4:198.51.100.0/24 ~all"
spf1.crm.example.org.   TXT "v=spf1 ip4:203.0.113.0/25 ~all"
spf2.crm.example.org.   TXT "v=spf1 ip4:203.0.113.128/25 ~all"
RecordTerms that query DNSCount
example.commx, include ×2, a4
_spf.mail.example.netinclude ×33
spf.crm.example.orginclude ×22
_nb1–_nb3.mail.example.net, spf1 / spf2.crm.example.orgnone (only ip4 / ip6)0
Total for a sender that matches nothing, so that evaluation reaches the final -all9

The 9 is the number of limited terms reached in evaluation order. Evaluation stops as soon as the sender's address matches, so a sender that matches earlier counts fewer terms (mail sent from the _nb1 range that does not match mx, for example, passes after three: mx, include:_spf.mail.example.net, and include:_nb1.mail.example.net). It is also not the number of DNS queries itself (mx is followed by address queries for each MX).

Adding one more include: for a service whose record has two include: terms of its own makes the total 9 + 1 + 2 = 12, over the limit.

Why only some senders fail

Mechanisms are evaluated left to right, and evaluation stops at the first match (RFC 7208 §4.6.2). A sender that matches the early mx or the first include: passes before the count reaches 10; only senders matched by a later include:, or not matched at all, hit the limit and get permerror. That can explain "the record works, but mail sent through one service fails".

Counting by hand

  1. Fetch your domain's TXT records and find the one that starts with v=spf1. Two or more v=spf1 records are a permerror by themselves (RFC 7208 §4.5).
    dig +short TXT example.com
  2. Paste the record into SPF Record Parser to see how many DNS-querying terms that record contains. A TXT record split into several strings can be pasted as it is.
  3. Repeat steps 1 and 2 for each include: and redirect= target and add the counts. A domain reached twice is counted twice.
  4. Count empty or NXDOMAIN answers separately as void lookups.

QuickKit does not query DNS, so SPF Record Parser counts only the one record you paste. The total for an evaluation is the sum over every record it reaches.

Reducing the count

  • Remove include: terms for services you no longer use; they can also cause void lookups.
  • ptr SHOULD NOT be published (RFC 7208 §5.5); consider removing it.
  • For addresses you control that do not change, replacing a or mx with ip4: / ip6: lowers the count.
  • Expanding a service's include: into its IP addresses ("flattening") breaks silently when the service changes its addresses, and legitimate mail starts failing. If you do it, pair it with something that tracks those changes.
  • SPF checks mainly the domain of the SMTP MAIL FROM (envelope sender) (RFC 7208 §2.4). If each service uses its own subdomain in MAIL FROM, that service's include: can move to that subdomain's record. If you use DMARC, check that alignment still holds afterwards.

Limits and caveats

  • Whether the limit is hit depends on the sender's address and the evaluation order. Keeping the worst case (a sender that matches nothing) at 10 or fewer is the safe target.
  • Slow or failing DNS answers can produce temperror, which is a different problem from the lookup count.
  • This guide explains the RFC 7208 rules. Check the receiving system for its actual verdict and how it reports it.

Sources (primary references)