SPF (Sender Policy Framework)
RFC 7208 reference — record syntax (mechanisms, qualifiers, modifiers, macros), the check_host() evaluation algorithm, DNS lookup limits, result codes, and common pitfalls.
Reference6 min read
Who it is for ESP operators, Senders
Applies to senders on any platform
ContentsOn this page — 10 sections
SPF lets you publish, in DNS, which hosts may send mail using your domain. Receivers compare the IP address of the connecting client with that list. SPF checks the RFC5321.MailFrom identity (the "Envelope From", or Return-Path) and, separately, the identity given in HELO or EHLO.
SPF is defined in RFC 7208. It authenticates the path the message took, through a DNS TXT record published by the domain. For how SPF feeds into DMARC alignment, see DMARC.
Identities checked
- Verifiers MUST check the
MAIL FROMidentity if a HELO check was not performed or did not reach a definite result. - It is RECOMMENDED that verifiers also check the
HELOidentity separately. WhenMAIL FROMis empty (bounces,MAIL FROM:<>), the HELO domain is used as the MAIL FROM domain (postmaster@<HELO domain>).
The record
- The record is published as a DNS TXT record at the domain being checked. It must begin exactly with
v=spf1(e.g.,v=spf10does not match and is discarded). RFC 7208 deprecated the separate SPF record type in DNS (type 99), so publish TXT only. - If no
v=spf1record exists, the result is none. If more than one exists, the result is permerror. - Terms are evaluated from left to right, and the first mechanism that matches decides the result through its qualifier. If nothing matches and there is no
redirect=, the default result is neutral (an implicit?all).
Qualifiers
| Qualifier | Result when the mechanism matches |
|---|---|
+ (default if omitted) |
pass |
- |
fail |
~ |
softfail |
? |
neutral |
Mechanisms
| Mechanism | Syntax | Meaning | DNS lookup? |
|---|---|---|---|
all |
all |
Always matches. Placed last as the explicit default (-all, ~all, …). Anything after all is ignored. |
No |
include |
include:<domain> |
Evaluates the SPF record of <domain> recursively. If the recursive result is pass, the mechanism matches. If it is fail, softfail or neutral, the mechanism does not match and evaluation continues. temperror and permerror are passed up. A recursive none gives permerror. |
Yes |
a |
a[:<domain>][/<cidr4>][//<cidr6>] |
Matches if the client IP address equals an A (IPv4) or AAAA (IPv6) record of the target domain (by default, the current domain), optionally within a CIDR prefix. | Yes |
mx |
mx[:<domain>][/<cidr4>][//<cidr6>] |
Looks up the target's MX records, then the address records of each MX name, and matches if the client IP address is among them. | Yes |
ptr |
ptr[:<domain>] |
A validated reverse DNS check. "This mechanism SHOULD NOT be published": it is slow, unreliable and deprecated in practice. | Yes |
ip4 |
ip4:<network>[/<cidr>] |
The client IP address is within the IPv4 network. The default prefix is /32. |
No |
ip6 |
ip6:<network>[/<cidr>] |
The client IP address is within the IPv6 network. The default prefix is /128. |
No |
exists |
exists:<domain-spec> |
Expands macros in the domain and does an A lookup (even for IPv6 connections). Matches if any A record is returned. This allows a policy for each IP address through macros. | Yes |
CIDR prefix lengths: /0–/32 for IPv4 (default /32), and /0–/128 for IPv6 (default /128). Only the given number of high-order bits is compared.
Modifiers
Modifiers are pairs of a name and a value (name=value). Each may appear at most once, anywhere in the record.
| Modifier | Syntax | Meaning |
|---|---|---|
redirect |
redirect=<domain> |
If no mechanism matched, evaluation continues with the SPF record of <domain>, and its result is used unchanged. If the redirect target has no SPF record, the result is permerror (not none). It counts toward the 10-lookup limit. It is ignored when the record also contains all (since all always matches first). |
exp |
exp=<domain> |
On a fail result, a TXT record is looked up at the domain, its macros are expanded, and it is returned as the explanation for people to read. It does not count toward the 10-lookup limit. |
Macros
Macros (%{x}) can be expanded in domain-spec fields and in exp text:
| Macro | Expands to |
|---|---|
%{s} |
Sender (the full MAIL FROM address) |
%{l} |
Local part of the sender |
%{o} |
Domain of the sender |
%{d} |
Current domain being checked |
%{i} |
Client IP address (dotted quad for IPv4; nibbles separated by dots for IPv6) |
%{p} |
Validated reverse DNS domain of the client IP address (SHOULD NOT be used, for the same reasons as ptr) |
%{v} |
in-addr for IPv4, ip6 for IPv6 |
%{h} |
The domain given in HELO or EHLO |
%% / %_ / %- |
Literal % / space / URL-encoded space (%20) |
Transformers: a digit limits how many labels are kept, counting from the right (%{d2} keeps the last two labels), r reverses the order of labels, and other delimiter characters may follow.
Result codes
| Result | Meaning (RFC 7208 §2.6) | Recommended handling by the receiver (§8) |
|---|---|---|
| none | No valid domain was extracted, or no SPF record was found | No information; inconclusive |
| neutral | The domain explicitly says nothing about the IP address (?) |
MUST be treated exactly like none |
| pass | The client is authorized to send mail for the domain | The domain is accountable; proceed |
| fail | The client is explicitly not authorized (-) |
Local policy. If rejecting, use SMTP 550 with enhanced status 5.7.1 |
| softfail | The host is probably not authorized (~), and the domain is in transition |
SHOULD NOT reject on this alone; MAY look more closely |
| temperror | A temporary error (usually DNS) during evaluation | Accept or defer. If deferring, use SMTP 451 with status 4.4.3 |
| permerror | The published record could not be interpreted correctly (the DNS operator must fix it) | If rejecting, use SMTP 550 with status 5.5.2 |
A note for DMARC: fail, softfail, neutral, none and both errors all count as "not pass". Only pass (with alignment) can satisfy the SPF side of DMARC.
DNS lookup limits (§4.6.4)
| Limit | Value | When exceeded |
|---|---|---|
Terms that query DNS in one evaluation (include, a, mx, ptr, exists, redirect) |
10 total, counted across all recursion through include and redirect |
permerror |
| "Void lookups" (NXDOMAIN or an empty answer) | SHOULD be limited to 2 | permerror |
Address lookups for each mx mechanism |
10 MX names | permerror |
Address lookups for each ptr evaluation |
10 PTR names | Records beyond the first 10 are ignored |
all, ip4, ip6 and exp do not count toward the 10-term limit.
Common pitfalls
- More than 10 lookups. Nested
includechains from ESPs, CRMs and ticketing tools add up fast. The result is permerror, which DMARC treats as having no SPF at all. Flatten includes or remove vendors you no longer use, and preferip4orip6(which cost no lookups) toaormxwhere practical. - Several
v=spf1records at one name give permerror. Merge them into a single record. +all(or a missing or permissive default) authorizes the entire internet, which is worse than having no record. End records with-allor~all.- An
includeof a domain with no SPF record gives permerror (a recursive none). Check vendor includes when you stop using a service. - The
ptrmechanism and the%{p}macro are deprecated, slow and unreliable. Do not publish them. - Forwarding breaks SPF. The forwarder's IP address is not in the original domain's record, so SPF fails after any hop that keeps the original MAIL FROM. This is built into authentication by path. It is why DKIM, which authenticates the content, is preferred for passing DMARC after forwarding, and why ARC exists.
- An SPF pass is not a DMARC pass. Many ESPs use their own bounce domain in MAIL FROM, so SPF passes but is not aligned with the From: domain. For alignment, use a custom Return-Path (bounce) subdomain of the From: domain. See DMARC.
- TXT strings longer than 255 characters must be split into several quoted strings within the single record (they are joined together). Large records may also force DNS over TCP.
- A
redirect=afterallnever takes effect, becauseallmatches first.
Related articles
Check your own record
The free check reads what your domain publishes in DNS.
In this topic
All 13 in Authentication →