emailmarketing.net

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

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 FROM identity if a HELO check was not performed or did not reach a definite result.
  • It is RECOMMENDED that verifiers also check the HELO identity separately. When MAIL FROM is 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=spf10 does not match and is discarded). RFC 7208 deprecated the separate SPF record type in DNS (type 99), so publish TXT only.
  • If no v=spf1 record 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 include chains 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 prefer ip4 or ip6 (which cost no lookups) to a or mx where practical.
  • Several v=spf1 records 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 -all or ~all.
  • An include of a domain with no SPF record gives permerror (a recursive none). Check vendor includes when you stop using a service.
  • The ptr mechanism 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= after all never takes effect, because all matches first.
  • DKIM, which authenticates the content instead
  • DMARC, on how SPF results and alignment feed into policy
  • ARC, on preserving authentication results across forwarding

Check your own record

The free check reads what your domain publishes in DNS.

In this topic

All 13 in Authentication →