Pangram verdict · v3.3
We believe this text is mainly AI, with some human-written content.
AI likelihood · overall
AIArticle text · 1,528 words · 1 segments analyzed
22 min read SPF Record Syntax: Mechanisms, Qualifiers, Modifiers, and Macros SPF record syntax follows one shape: a single DNS TXT record that starts with v=spf1, followed by space-separated terms — mechanisms with optional qualifiers, then modifiers — evaluated left to right until the first match. Here is a complete record: v=spf1 ip4:192.0.2.0/24 include:_spf.example.com -all That one line authorizes a /24 network and a third party’s servers, then fails everything else. Every rule governing it lives in RFC 7208, the SPF standard published in April 2014. This page is the full reference: every mechanism, every qualifier, both modifiers, the complete macro table, evaluation order, DNS lookup limits, and record placement rules — each with the RFC 7208 section that defines it. If you want protocol fundamentals first — why SPF exists and how it fits with DKIM and DMARC — start with our SPF guide. If you build and maintain records, bookmark this. SPF Record Syntax at a Glance An SPF record is one string of text in the RDATA of a single TXT record, and its grammar has exactly three kinds of parts: a version tag, mechanisms (each with an optional qualifier), and modifiers. The version tag must be exactly v=spf1 — a record starting v=spf10 is discarded, not partially matched (§4.5). Every SPF record is the same three kinds of parts: a version tag, mechanisms with optional qualifiers, and modifiers. The grammar in brief, from §3 and §4.6.1: PartFormRoleVersionv=spf1, always first, exactSelects the record (§4.5)Mechanism[qualifier]name[:arg][/cidr]Tested against the client IP; can match or not match (§4.6.2)Qualifier+ - ~ ? before mechanismThe result returned when its mechanism matches (§4.6.2)Modifiername=value, at most once eachExtra information; never matched (§6) The three kinds of parts in SPF record syntax Terms are separated by spaces. Mechanism names are case-insensitive, and terms containing none of =, :, or / are mechanisms (§4.6.1). One syntax error anywhere invalidates the whole record: check_host() — the receiver’s evaluation routine, as the RFC names it — validates SPF syntax first and returns PermError immediately if anything is malformed, without evaluating a single term (§4.6). That is why one stray character can fail authentication for every message a domain sends. SPF Mechanisms Eight mechanisms exist, and each either matches the connecting IP or does not. RFC 7208 §5 splits them into basic framework mechanisms (all, include) and designated-sender mechanisms (a, mx, ptr, ip4, ip6, exists). Before the full table: most real-world records use only include, ip4, ip6, and all — the rest of this reference exists so you can read other people’s records, not because your own needs them. MechanismSyntaxMatches when…Counts toward 10-lookup limit?RFC §allallAlwaysNo§5.1includeinclude:domainThe referenced record returns PassYes§5.2aa[:domain][/cidr]Client IP is among the domain’s A/AAAA addressesYes§5.3mxmx[:domain][/cidr]Client IP is an address of one of the domain’s MX hostsYes (plus per-MX address caps)§5.4ptrptr[:domain]Reverse DNS validates into the target domainYes§5.5ip4ip4:network[/cidr]Client IP is inside the IPv4 networkNo§5.6ip6ip6:network[/cidr]Client IP is inside the IPv6 networkNo§5.6existsexists:domainThe constructed domain has any A recordYes§5.7 All eight SPF mechanisms with syntax, match condition, and DNS lookup cost You can parse any published record term-by-term — with each mechanism’s cost and RFC cite attached — using the SPF syntax inspector. all all always matches, which is why it belongs at the end as the explicit default (§5.1). Everything after it is dead text: “Mechanisms listed after all MUST be ignored,” and any redirect= modifier is ignored whenever all appears anywhere in the record (§5.1). A record without a trailing all or redirect= silently defaults to Neutral (§4.7). include include:domain recursively evaluates the referenced domain’s SPF record and matches only when that evaluation returns Pass (§5.2). It is not a splice. The RFC itself concedes the name was “poorly chosen”: a -all inside an included record does not fail your outer record — a Fail, Softfail, or Neutral inside simply means “no match here, keep going” (§5.2). Better mental model: if-match. One consequence worth knowing: if the included domain publishes no SPF record at all, include returns PermError (§5.2). a and mx a matches when the client IP is one of the target domain’s A or AAAA addresses; mx matches when it is an address of one of the domain’s MX hosts (§5.3, §5.4). Both default to the current domain when no argument is given, and both accept dual CIDR suffixes — a/24 compares only the top 24 bits, and a:example.com/24//64 sets IPv4 and IPv6 prefixes separately (§5.3). Note the cost asymmetry: mx is one term against the 10-lookup limit, but evaluating it triggers one MX query plus an address query per MX host, capped at 10 address records before PermError (§4.6.4). ip4 and ip6 ip4: and ip6: test whether the client IP falls inside a literal network, using a colon — ip4:192.0.2.0/24, never ip4= (§5.6). Omitted CIDR lengths default to /32 and /128, i.e., exact-address match, and truncated addresses like 192.0.2 are not permitted (§5.6). These are the only designated-sender mechanisms with zero DNS cost, which makes them the cheapest terms in any record. exists exists:domain constructs a domain name, queries it for an A record, and matches if any A record comes back — regardless of its value, and always an A query even on IPv6 connections (§5.7). One lookup, arbitrary logic. Combined with macros (§7), it powers dynamic authorization: publish per-IP hostnames in a zone you control, and the record authorizes exactly those IPs without ever listing them. This is the pattern behind Salesforce’s current record, shown in the examples below. ptr (do not use) RFC 7208’s own heading for §5.5 is literally "ptr" (do not use): the mechanism “SHOULD NOT be published” because it is slow, unreliable under DNS errors, and burdens the .arpa name servers. Receivers must still support it, but you should never publish it. Key finding 41,728 domains — 1.4% of SPF-enabled domains — still publish the deprecated ptr mechanism Source: DMARCguard SPF Supply Chain study — our scan of 5,499,028 Tranco domains, March 15, 2026 Next step: paste your own record into the SPF syntax inspector and confirm every mechanism in it earns its DNS cost. SPF Qualifiers: +, -, ~, ? A qualifier is a single prefix character that sets the result returned when its mechanism matches — and when you omit it, the default is + (§4.6.2). mx and +mx are the same term. QualifierResultMeaning (§2.6)+PassThe client is authorized (default when omitted)-FailThe client is explicitly not authorized~SoftfailProbably not authorized; the domain isn’t willing to state a hard Fail?NeutralThe domain asserts nothing about the client The four SPF qualifiers and their check_host() results In practice the choice comes down to the final term: -all versus ~all. Receivers treat them differently, and mail flows through forwarders complicate the picture — we cover the trade-off in depth in softfail vs hardfail. If you also enforce DKIM and DMARC, publish -all; if you are still discovering senders, start with ~all and tighten once softfail vs hardfail settles the trade-off for your domain. SPF Modifiers: redirect and exp Modifiers are name=value pairs — always an equals sign, never a colon — that provide information rather than being matched (§6). RFC 7208 defines two, each allowed at most once; a duplicated redirect= or exp= is a PermError, while unrecognized modifiers are ignored no matter how often they appear (§6). redirect= redirect=domain hands the entire evaluation to another domain’s record — but only after every mechanism has failed to match (§6.1). The other record’s result becomes your result, with one sharpening: where a missing record would normally give None, a redirect= to a domain with no SPF record gives PermError (§6.1). Two rules people miss: redirect= counts as one DNS lookup (§4.6.4), and it “MUST be ignored” whenever an all mechanism appears anywhere in the record (§6.1). How it differs from include: Behaviorinclude: (§5.2)redirect= (§6.1)Matches onPass only; otherwise evaluation continuesNot a mechanism; applies after all missesEffect of referenced resultOnly Pass propagates as a matchResult replaces yours entirelyWith all in the recordUnaffectedIgnoredReferenced record missingPermErrorPermErrorIntended useCrossing administrative boundariesSharing one policy across your own domains include: versus redirect= behavior side by side exp= exp=domain names a TXT record whose macro-expanded string is returned as the explanation when a message Fails (§6.2). It is the only term in the language that never counts toward the lookup limit — §4.6.4 exempts exp explicitly, because its lookup happens after evaluation, and only on Fail. SPF Macros Reference Macros are %{...} sequences expanded at evaluation time from properties of the message — and they are the least-covered corner of SPF record syntax despite being fully specified in RFC 7208 §7. The ABNF defines exactly eleven macro letters: s, l, o, d, i, p, h, c, r, t, v (§7.1). MacroExpands to (§7.2)Notes%{s}The full sender, e.g. [email protected]%{l}Local-part of the sender (user)Limits caching (§7.3)%{o}Domain of the sender%{d}The domain being evaluated%{i}The connecting client IPDotted-quad IPv4 / dot-separated nibbles IPv6 (§7.3)%{p}Validated reverse-DNS name of the IP”do not use” (§7.2, §5.5)%{v}in-addr for IPv4, ip6 for IPv6%{h}HELO/EHLO domain%{c}Client IP, human-readableexp text only (§7.2)%{r}Domain of the host performing the checkexp text only (§7.2)%{t}Current timestamp (seconds since epoch)exp text only (§7.2) All eleven SPF macro letters and their expansions (RFC 7208 §7.2) Three literal escapes complete the set: %% is a percent sign, %_ a space, %- a URL-encoded space (§7.1).