SPF record syntax explained
An SPF record is one TXT record, on one line, that answers one question: which servers may send email using this domain in the envelope-from address? The syntax is small — a version tag, a handful of mechanisms, an optional qualifier on each — but it is unforgiving. A record with a typo does not warn you; it either fails open, fails closed, or is ignored entirely, and you find out from a DMARC report weeks later.
This page is the reference. If you want the concept first, read What is SPF?.
The shape of a record
v=spf1 ip4:198.51.100.7 include:_spf.google.com ~all
Three parts, always in this order. The middle part is where SPF mechanisms and SPF qualifiers live:
| Part | Example | Rule |
|---|---|---|
| Version | v=spf1 | Must be first, must be exactly this |
| Mechanisms | ip4:… include:… mx | Evaluated strictly left to right |
The all mechanism | ~all / -all | Must be last; everything after it is dead code |
Evaluation stops at the first mechanism that matches. That is the single most important sentence on this page: order is not cosmetic. A record that begins with +all authorises the entire internet and never reaches anything you wrote after it.
Qualifiers: the character in front
Every mechanism can carry a qualifier. Omit it and you get +.
| Qualifier | Name | Result when the mechanism matches |
|---|---|---|
+ | Pass | Authorised (the default) |
~ | SoftFail | Not authorised, but accept and mark |
- | Fail | Not authorised, reject |
? | Neutral | No statement — treated like no SPF at all |
In practice the qualifier only ever varies on all, because that is the catch-all for "none of my mechanisms matched". Which of ~all and -all you should publish is its own decision, covered in SPF softfail vs hardfail.
?all deserves a warning. It is not a cautious middle ground — it explicitly says "I make no claim", which is the same information a receiver has when you publish nothing at all. If you meant to be careful, ~all is the careful option.
The mechanisms
ip4 and ip6
The literal cases: a single address or a CIDR range.
ip4:198.51.100.7
ip4:198.51.100.0/24
ip6:2001:db8::/32
These cost no DNS lookups, which matters more than it sounds — see the ten-lookup limit below.
include
Hands evaluation to another domain's SPF record and asks: would that record pass this IP?
include:_spf.google.com
Two things surprise people. First, include is not a text merge — the other record is evaluated as its own question, and only a pass counts as a match. A SoftFail inside an included record does not softfail your message; it simply doesn't match, and evaluation continues. Second, its all mechanism is never applied to your record. Only the outcome of the mechanisms before it matters.
a and mx
Authorise the domain's own A/AAAA records, or the hosts named by its MX records.
a
mx
a:mail.example.com
mx:example.com
Bare a and mx mean "the current domain". Each counts as one term against the ten-term limit — but mx also triggers an address lookup per host it names, and RFC 7208 caps those at ten for a single mx.
exists
The SPF exists mechanism matches if a domain name resolves to anything at all. It is the escape hatch for macro-driven, per-sender logic and is rare outside large mail platforms. If you are hand-writing a record, you do not need it.
redirect — a modifier, not a mechanism
The SPF redirect modifier is the one entry that is not a mechanism at all.
v=spf1 redirect=_spf.example.com
Replaces your record with another domain's, wholesale — including its all. Unlike include, it is only consulted if no mechanism matched, and it is the last word: whatever the target says is your answer. Use it when several domains genuinely share one policy. Never combine it with an all mechanism, because all always matches and redirect will never be reached.
ptr — do not use it
The SPF ptr mechanism asks the receiver to do a reverse DNS lookup and check the result. RFC 7208 deprecates it in plain language: it is slow, unreliable, and imposes cost on the receiver. Some receivers ignore it outright. If you inherited a record containing ptr, replacing it is an improvement, not a risk.
The ten-lookup limit
SPF allows a maximum of ten DNS-querying mechanisms per evaluation. include, a, mx, ptr, exists and redirect all count; ip4, ip6 and all do not. Every include you add also drags in whatever that record looks up, recursively.
Exceed ten and the result is PermError — which most receivers treat as no SPF at all, so a record that looks generous fails everywhere at once. This is common enough to have its own page: Too many DNS lookups.
Syntax mistakes that break a record silently
| Mistake | What actually happens |
|---|---|
| Two SPF records on one domain | PermError — receivers must not pick one. See multiple SPF records |
v=spf1 not first | The record is not an SPF record and is ignored |
Anything after all | Unreachable — all always matches |
include:example.com/24 | include takes a domain, never a CIDR |
ip4:example.com | ip4 takes an address, never a name |
| Splitting a long record into two TXT records | Two records = PermError. Long records must be split into multiple strings inside one record |
A trailing +all "just in case" | Authorises every host on the internet |
One SPF record protects one thing: the envelope-from domain. It says nothing about the From address your recipient actually sees, which is why SPF alone cannot stop spoofing and why DMARC exists. That relationship is laid out in SPF, DKIM and DMARC: how they fit together.
A worked example
A company sends from Google Workspace, a marketing platform, and one legacy server on a fixed IP:
v=spf1 ip4:198.51.100.7 include:_spf.google.com include:sendgrid.net ~all
Read it aloud: version one; the legacy server passes; anything Google authorises passes; anything SendGrid authorises passes; everything else is a softfail. Lookup cost: zero for the ip4, plus whatever the two includes consume — comfortably inside the limit today, and worth re-checking each time a sender is added.
Once a record is in place, the reports tell you whether it matches reality. Sending sources, grouped by provider shows which senders are actually using your domain — including the ones nobody remembered to add.