SPF Syntax: Mechanisms, Qualifiers, and the Ten-Lookup Limit

A precise reference for SPF syntax: defining mechanisms and qualifiers, the ten-DNS-lookup limit, common syntax errors, and how to test a record with dig.

6 min Updated 29 Oct 2026

Your SPF record probably works, and that's the worst-case scenario. As long as no receiver returns permerror, nobody looks at it; and when one does, transactional emails silently sit in the queue or land in spam. If you're here, it means you want the exact value of every term, not a general explanation. So let's go straight to the syntax.

The structure of an SPF record and the mandatory order of terms

An SPF record is a TXT record on the domain name that starts with v=spf1. After that comes a list of terms separated by spaces, and their order matters: mechanisms are evaluated left to right, and the first match wins. If no match is found, the default qualifier, -all, applies. Many people don't know about this "default" behavior and think that the absence of all means neutral.

example.com. 3600 IN TXT "v=spf1 mx a:mail.example.com include:_spf.google.com ip4:185.10.20.30 -all"

We have three categories of terms. Mechanisms, which specify which hosts are authorized; qualifiers, which determine the result of each mechanism; and modifiers, which come with = and, unlike mechanisms, their order doesn't matter.

Mechanisms: exactly what each one matches

MechanismMatchesLookup cost
allAll senders; always comes lastZero
aA or AAAA record of the domain (or the specified domain)1
mxIP addresses of all the domain's MX hosts1 + number of MX
ip4 / ip6A specific address or CIDRZero
includeAnother domain's SPF record and its result1 + that domain's lookups
existsThe existence of a domain name (any record type)1
ptrReverse DNS of the sender's address1 per IP

Two points I see a lot in practice. First, ptr is deprecated and RFC 7208 explicitly advises against using it; if you see it somewhere, it's usually left over from an old panel. Second, include borrows its result from the target domain: if the domain you've included ends with -all and your address isn't in that list, the result is fail, not neutral. That single difference is the source of most tickets asking "why is transactional email from our server being rejected?"

Qualifiers: four values and one default

Each mechanism can come with one of four qualifiers: + means pass, - means fail, ~ means softfail, and ? means neutral. The absence of a qualifier is equivalent to +. That is, mx and +mx are the same.

  • -all: strict. Any sender not on the list is rejected. For a domain that only sends email from one specific path, it's the correct choice.
  • ~all: soft. The receiver can accept it and put it in spam. It makes sense for a transition period and when you're not sure you've covered all senders.
  • ?all: inert. It effectively neutralizes SPF and only makes sense for domains with no email.

My choice: -all. If you know your sender list, softfail only lets the receiver make a bad decision. Keep ~all only for the first two weeks of a migration, then tighten it.

The ten-DNS-lookup limit and the permerror

This is where healthy records break down. RFC 7208 sets a limit of 10 DNS lookups per SPF evaluation. The include, a, mx, ptr, and exists mechanisms each consume one lookup, and include also charges the lookups of the domain inside it to your account. ip4, ip6, and all are zero.

Exceeding the limit results in a permerror, and receivers treat it like fail or worse. The sign is exactly this: the record is syntactically correct, dig responds cleanly, but Gmail rejects the email. Tools like spfquery or online services show the final number:

dig +short TXT example.com | grep spf1
spfquery -i 185.10.20.30 -s mail.example.com -h example.com

A single include:_spf.google.com isn't just one lookup; it consumes several on its own. Three SaaS services, each with an include, easily get you to 12. The correct solution is to replace duplicate includes with direct ip4, since that costs zero lookups. The trade-off is that if that service changes its IPs, you have to update the record manually. I'll accept that cost; permerror is worse.

This is where they get it wrong

The most common real mistake is putting all in the middle of the record. Someone edits the record and -all ends up before include. From that moment on, every evaluation stops at all and the remaining terms are never read. The sign is confusing: the record looks complete, the include is in place, but emails from the new service are rejected and old emails go through. Because all matched before the include.

The second mistake is two separate TXT records with v=spf1. The RFC says that in this case the result is permerror, not picking one of them. If your DNS panel doesn't allow editing the existing record and you create a second one, that's exactly what happens. You must merge both into a single TXT.

Modifiers and redirect

You have two standard modifiers. redirect= delegates the entire evaluation to another domain and only applies when no mechanism has matched. exp= specifies a domain that returns the error text; almost no receiver displays it and its practical value is close to zero.

"v=spf1 include:_spf.example.net redirect=_spf.example.org -all"

A point that's rarely mentioned: redirect itself consumes a lookup, and if the target domain also exceeds the limit, the permerror is attributed to you. For primary domains, use an explicit include and keep redirect for parked domains.

If you're on shared hosting and don't have full DNS control, before touching the record, make sure you know where the sending path is determined. The guide Transactional email; why not send from hosting and what's the right alternative? clarifies this distinction and prevents a lot of wrong records.

How to test a record before publishing

  1. Create the record on a test domain, not the primary domain.
  2. Use dig +short TXT test.example.com to see the actual published value, not what you typed in the panel.
  3. Use spfquery or an online tool to check the lookup count and the result for a few sample IPs.
  4. Send a real email to Gmail and Outlook and read the Received-SPF header.
  5. If the result was softfail or neutral, find the cause before switching to -all.

To check MX records and make sure the receiving hosts are also configured correctly, MX record lookup is a quick starting point. If emails still go to spam after configuring SPF, the problem usually isn't SPF; Why do our emails go to spam? A complete troubleshooting guide covers the DKIM, DMARC, and reputation path.

A practical warning: SPF only verifies the sending path, not the content. A domain with flawless SPF and DKIM turned off will still have problems in Gmail. If you're working on Linux hosting and have access to DNS settings and the DKIM key, configure both together; SPF alone is half the job.

Frequently asked questions

What's the maximum number of includes in an SPF record?

There's no numerical limit on the number of includes, but the ten-DNS-lookup limit effectively restricts it to two or three includes depending on the cloud services. Each include consumes at least one lookup and adds its own internal lookups. If you exceed this limit, the result is permerror and the email is rejected.

What's the difference between -all and ~all in SPF?

-all tells the receiver to definitely reject senders outside the list, whereas ~all says you can accept but act with caution. In practice, ~all causes the email to land in spam instead of being rejected. For a domain where you know all its senders, -all is the right choice.

Why does my SPF record give permerror when it looks correct?

Three common causes: exceeding the ten-DNS-lookup limit, having two separate TXT records that both start with v=spf1, and placing all before other mechanisms. For the second case, check with dig +short TXT example.com; if two lines come back, they must be merged into one record.

Does ptr still work in SPF?

It works, but RFC 7208 prohibits its use and some receivers ignore it. On top of that, each IP consumes one lookup and quickly gets you to the ten limit. Use ip4 or include instead.

Before making any other change, run dig +short TXT on your domain right now and count the number of lookup-consuming terms. If you're close to ten, your next problem won't be SPF; the problem has already started.

Was this page helpful?