An exhaustive technical reference for the SPF record format as defined in RFC 7208 — every mechanism, every qualifier, every modifier, every edge case, with worked examples you can paste into a UK domain's DNS. Use this page when you need to understand or audit an existing SPF record token by token.
v=spf1A valid SPF record is a DNS TXT record whose content matches the grammar in RFC 7208 Section 12. Simplified:
record = version *( 1*SP ( directive / modifier ) ) *SP
version = "v=spf1"
directive = [ qualifier ] mechanism
qualifier = "+" / "-" / "~" / "?"
mechanism = "all" / "include" / "a" / "mx" / "ptr" / "ip4" / "ip6" / "exists"
modifier = "redirect" / "exp" / unknown-modifierIn plain English: the record starts with v=spf1, followed by space-separated tokens, each of which is either a directive (a qualifier plus a mechanism) or a modifier. Directives are evaluated left-to-right and the first match wins; modifiers do not match but change evaluation behaviour.
v=spf1Every SPF record must begin with exactly v=spf1. There is no v=spf2, no v=spf3 — the version tag has never been incremented since the original RFC 4408. Records without this prefix are not SPF records and must be ignored by evaluators.
The historical v=spf2.0/mfrom,pra tag belongs to the failed Sender ID experiment and should be removed from any modern zone. RFC 7208 Section 3.1 explicitly deprecates its publication.
The universal-match mechanism. Always matches, regardless of the connecting IP. Used with a qualifier to specify the default behaviour. Must be the last mechanism in the record.
Syntax: all (typically written as -all, ~all, ?all or +all with a qualifier).
Example: -all — any IP that did not match a preceding mechanism hard-fails.
Matches if the connecting IPv4 address is inside the specified range. Takes a literal IPv4 address or a CIDR prefix.
Syntax: ip4:address or ip4:address/prefix-length.
Examples:
ip4:203.0.113.25 — matches only the single IP 203.0.113.25.ip4:203.0.113.0/24 — matches any IP from 203.0.113.0 to 203.0.113.255.ip4:217.149.241.18 — the live epost.plus example: exactly one authorised sending IP.DNS lookup cost: 0. This is why flattening to ip4: ranges is the standard remediation for the 10-lookup ceiling.
The IPv6 equivalent of ip4:. Takes an IPv6 literal or CIDR range.
Syntax: ip6:address or ip6:address/prefix-length.
Examples:
ip6:2001:db8::25 — matches exactly that address.ip6:2001:db8::/32 — matches any IP inside the /32.Publish IPv6 ranges for any sender that may connect over IPv6 — increasingly common as UK broadband and datacentre networks deploy dual-stack.
Matches if the connecting IP is one of the A or AAAA records of the specified domain. If no domain is specified, the currently evaluated domain is used.
Syntax: a or a:hostname or a:hostname/cidr or a/cidr.
Examples:
a — matches IPs equal to the current domain's A record.a:mail.firm.co.uk — matches IPs equal to the A record of mail.firm.co.uk.a:mail.firm.co.uk/24 — matches any IP in the /24 surrounding that A record.DNS lookup cost: 1 (plus implicit A and AAAA queries).
Matches if the connecting IP is one of the A or AAAA records associated with any of the specified domain's MX records.
Syntax: mx or mx:hostname or mx:hostname/cidr.
Examples:
mx — matches IPs backing the current domain's MX hosts.mx:mail.firm.co.uk — matches IPs backing that specific host's MX entries.DNS lookup cost: 1 for the MX query plus additional lookups per returned MX host's A/AAAA records. Can quietly consume multiple lookups in the 10-lookup budget.
Chains evaluation into another domain's SPF record. If that record produces a Pass, the include matches; any other result falls through to the next mechanism in the current record.
Syntax: include:domain.com.
Examples:
include:_spf.google.com — delegates evaluation to Google's SPF.include:servers.mcsv.net — delegates evaluation to Mailchimp's SPF.include:spf.protection.outlook.com — delegates evaluation to Microsoft 365's SPF.DNS lookup cost: 1 for the included record's TXT lookup, plus whatever that record consumes internally. This is the classic lookup-budget hog.
Matches if an A record exists for the specified domain (actually: if a DNS query returns any RR, including none of type A). Used with macros for dynamic SPF policies.
Syntax: exists:domain-expression.
Example: exists:%{l}.%{o}.spf.firm.co.uk — matches if a specialised DNS subtree exists for the local-part and origin of the sender. Rare in UK business use but occasionally appears in large-sender deployments.
DNS lookup cost: 1.
Matches if the PTR record of the connecting IP resolves back to the specified domain. RFC 7208 Section 5.5 recommends against using this mechanism — it is slow, relies on PTR records managed by the IP owner (usually the ISP or datacentre, not the mail administrator), and can be unreliable.
Syntax: ptr or ptr:domain.
Do not publish ptr in new SPF records. Remove it from any existing record you audit.
Every directive has an optional qualifier preceding the mechanism. Without an explicit qualifier, + (Pass) is assumed. The four qualifiers map to the four result codes:
| Qualifier | Result on match | Typical use |
|---|---|---|
+ | Pass | Default; rarely written explicitly |
- | Fail (hard) | On the terminating all at steady state |
~ | SoftFail | On the terminating all during rollout |
? | Neutral | Almost never used; equivalent to having no SPF |
Qualifiers other than + are typically attached only to the all mechanism. Attaching a qualifier like -ip4:... to earlier mechanisms — to explicitly disallow a specific range — is legal but unusual.
Replaces the entire evaluation with that of another domain's SPF record. Must not coexist with a terminating all in the same record.
Syntax: redirect=domain.com.
Example: a parent company publishes v=spf1 redirect=_spf.parent-group.co.uk on all its subsidiary domains. DNS lookup cost: 1. Use cases are rare in UK SME deployments; more common in large group structures.
Specifies a domain to query for an explanation string when SPF fails. The returned TXT record at that domain is included in the SMTP rejection error, helping senders diagnose the failure.
Syntax: exp=domain.com.
Example: exp=spf-explain.firm.co.uk where that domain's TXT record contains a human-readable message such as "Mail from firm.co.uk must originate from authorised servers — see https://firm.co.uk/smtp-help". Optional and rarely published.
RFC 7208 allows unknown modifiers (name=value) to be published and ignored by evaluators. The ecosystem did not settle on any widely used extension modifiers — if you see one in the wild it is usually a vendor-specific test beacon and safe to ignore.
SPF supports a limited templating system inside mechanism arguments, defined in RFC 7208 Section 7. The main macros:
| Macro | Meaning |
|---|---|
%{s} | The full envelope sender (e.g. [email protected]) |
%{l} | The local-part of the envelope sender (user) |
%{o} | The domain of the envelope sender (firm.co.uk) |
%{d} | The currently evaluated domain |
%{i} | The connecting IP address |
%{p} | The PTR hostname (deprecated alongside ptr) |
%{v} | "in-addr" for IPv4, "ip6" for IPv6 |
%{h} | HELO/EHLO hostname of the sender |
Macros are almost exclusively used with the exists mechanism to build dynamic per-sender lookup schemes. They are rarely needed for UK SME SPF deployment — most modern patterns use explicit ip4:, include: and mx mechanisms.
After SPF evaluation, one of the following result codes is produced:
| Result | Cause | Receiver action (typical) |
|---|---|---|
| None | No SPF record published for the envelope sender's domain | No judgment; DMARC then dictates behaviour |
| Neutral | An explicit ? qualifier matched, or ?all terminated evaluation | Treated similarly to None |
| Pass | A matching directive with + qualifier was encountered | Accept (subject to DMARC) |
| SoftFail | A matching directive with ~ qualifier was encountered | Accept but flag as suspicious |
| Fail | A matching directive with - qualifier was encountered | Reject |
| TempError | Transient DNS failure during evaluation | Defer and retry |
| PermError | Record is malformed, exceeds 10 lookups, or has other permanent issues | Treat as if None (per RFC) or reject (some receivers) |
The Authentication-Results header written by the receiver uses these terms directly — looking for spf=permerror in a bounce header is the quickest way to identify a 10-lookup-limit violation in the field.
SPF records are published as DNS TXT records. RFC 4408's RR type 99 (SPF) was deprecated in RFC 7208. Do not publish type 99 records.
A domain must not have more than one SPF record. Two TXT records that both begin with v=spf1 cause a PermError. If you need to combine policies from multiple sources, merge them into a single record with multiple include: mechanisms.
A single TXT string is limited to 255 characters. TXT records longer than that must be split into multiple strings within the same TXT record. Most DNS providers handle this automatically; if you publish a long SPF manually, remember to split it.
A single TXT record can contain up to 512 bytes in the DNS wire format comfortably; beyond that, TCP fallback is needed and some older resolvers may truncate. Keep SPF under 450 bytes including quotes and separators to be safe.
Use a moderate TTL — between 1 hour (3600) and 1 day (86400). Very short TTLs add unnecessary DNS load; very long TTLs delay updates. One hour is a reasonable default during active development; raise to six hours at steady state.
sole-trader.co.uk. IN TXT "v=spf1 include:_spf.smartxhosting.uk -all"Lookups: 1. Covers mail sent through the smartxhosting.uk email platform. Hard-fail on everything else.
firm.co.uk. IN TXT "v=spf1 include:spf.protection.outlook.com -all"Lookups: 2-3 (Microsoft's include expands internally). Simple, effective.
retailer.co.uk. IN TXT "v=spf1 include:spf.protection.outlook.com include:servers.mcsv.net include:_spf.salesforce.com include:sendgrid.net ~all"Lookups: up to 9 depending on how the vendor macros expand. Audit with a counter; flatten if over 10.
parked.co.uk. IN TXT "v=spf1 -all"Lookups: 0. No IP ever authorised. Combine with DMARC p=reject and a null DKIM at *._domainkey.parked.co.uk for full non-sending protection.
epost.plus. IN TXT "v=spf1 ip4:217.149.241.18 ~all"Lookups: 0. Single authorised IP; softfail during operations, paired with DMARC strict alignment enforcing reject.
When you take over a UK domain with an unknown SPF history, run through this checklist:
dig TXT domain | grep 'v=spf1' should return exactly one line.v=spf1? Yes, right at the beginning, no whitespace.all? -all or ~all. Never +all. Never missing.ptr mechanism? Grep the record for ptr. Remove if found.include:s? Cross-reference every include with the live sender inventory. Remove any whose service is no longer used.spf2.0. Delete if present.For a UK sole trader sending through a single hosting provider: v=spf1 include:_spf.provider.uk -all. One include, one hard-fail terminator. Nothing more.
A growing UK SME typically has five to ten SPF includes — marketing platform, transactional email provider, CRM, survey tool, e-signature service, invoicing, HR portal, monitoring alerts. At this point flattening or a hosted SPF service becomes essential to stay under the 10-lookup ceiling.
Old includes persist in SPF records for years after the underlying service was retired. Every unused include: still consumes a DNS lookup and slightly widens the attack surface. Audit at least annually.
ip4: only" patternFor domains with stable sending infrastructure where every legitimate sender's IP is known and static, hard-coding ip4: ranges eliminates the lookup-ceiling problem entirely. Suits public-sector deployments and large UK enterprises with a single outbound relay farm.
Some documentation suggests adding include:s for corporate forwarders, universities, ISPs — essentially any upstream that might forward your mail. Do not do this. It opens your SPF to anyone who can send through those networks. ARC is the correct solution to forwarding, not SPF permissions.
Very occasionally an administrator publishes two SPF records thinking the receiver will choose the more permissive. The receiver does not — the result is PermError and all your mail loses its SPF verdict. There must be exactly one record.
SPF does not live alone in the zone. Several interactions are worth noting:
The mx mechanism in SPF uses your current MX record set. Changing your MX records (for example during migration from one provider to another) can quietly change what SPF authorises. Review SPF whenever you touch MX.
SPF and DKIM records coexist in the same zone but at different names — SPF at the domain root, DKIM at selector._domainkey. They are independent; changing one does not affect the other. Both contribute independently to DMARC.
DMARC looks at both SPF and DKIM results but only counts each towards alignment. SPF pass with a non-aligning envelope sender does not help DMARC. Always verify DMARC is passing end-to-end, not just SPF.
SPF records published in a DNSSEC-signed zone are signed automatically by the zone's DNSSEC setup. Receivers that validate DNSSEC can be certain the SPF record has not been tampered with in transit.
If you delegate a subdomain (for example marketing platforms that ask you to CNAME a subdomain to their infrastructure), SPF on the parent does not apply. Ensure the delegated subdomain either inherits through redirect= or publishes its own SPF.
Q: Can I comment inside an SPF record?
A: No. TXT records have no comment syntax. Everything between the opening v=spf1 and the terminating all is evaluated. Keep a separate change-control document for why each mechanism is there.
Q: What is the difference between -all and ~all in terms of what receivers do?
A: -all asks receivers to reject unauthorised senders; receivers may do so at SMTP level. ~all asks receivers to flag as suspicious but deliver anyway. In practice, modern receivers rely more on the DMARC policy than on the SPF qualifier — but keeping SPF strict reinforces the intent and is required by several UK compliance frameworks.
Q: What happens if an include: points to a domain with no SPF record?
A: The included lookup produces None, the include mechanism therefore does not match, and evaluation continues with the next mechanism. This is different from TempError (which defers) or PermError (which fails the whole record).
Q: Can I use CIDR /32 notation to specify a single IP in ip4:?
A: Yes. ip4:203.0.113.25 and ip4:203.0.113.25/32 are equivalent. The bare address form is more readable.
Q: Must I include IPv6 even if my mail server is IPv4-only?
A: No, but be aware that if you ever add an AAAA record to your mail server without updating SPF, IPv6-originated mail will start failing. Most UK hosting platforms are dual-stack by default — add IPv6 ranges proactively.
Q: How do macros interact with the 10-lookup limit?
A: Macro expansion itself costs zero lookups. The mechanisms that consume lookups (exists, a, mx, include) still count. Exists combined with macros is a common way to build dynamic SPF that stays under the lookup budget.
Q: What is the exp= modifier for in practice?
A: It publishes a human-readable explanation string that receivers can include in bounce messages. Rarely used — most UK deployments do not bother. If you publish one, make sure the explanation URL actually exists and is kept current.
Q: Does capitalisation matter in SPF?
A: Mechanism names and keywords are case-insensitive. Domain names in arguments are lower-cased for DNS queries. As a convention, keep everything lowercase for readability.
Q: What is the expected behaviour when an SPF record exceeds 10 lookups?
A: The evaluation produces PermError. Under strict interpretation, this is treated as equivalent to None — SPF provides no verdict. In practice, some large receivers treat PermError as a soft-signal against the sender. Always keep within the budget.
Q: Is there any reason to publish v=spf1 +all?
A: No. That record declares that any IP in the world can send for your domain, which defeats the purpose of SPF entirely. If the record appears on a domain you manage, remove it immediately.
Q: Can I publish SPF at a wildcard record (*.firm.co.uk)?
A: Wildcard SPF is technically valid but usually a sign of a mistake. Subdomains that genuinely send mail should publish their own specific SPF. Wildcards mask typos and surprise subdomain delegations that should be visible and reviewed. Avoid wildcards unless there is a specific reason.
Q: Does the order of mechanisms affect the result?
A: Only the first matching mechanism counts. If you write v=spf1 ip4:1.2.3.4 -ip4:1.2.3.4 -all, the first match (+ip4:1.2.3.4 implicit qualifier, Pass) wins. Put broad includes and ranges early, specific overrides last if you must — though clean SPF almost never needs negative mechanisms.
Q: What is the practical maximum length of an SPF record in bytes?
A: Keep the complete TXT record (including quotes and separators) under 450 bytes to avoid DNS UDP truncation on older resolvers. The 512-byte UDP response limit with EDNS0 and TCP fallback means you can go further, but every UK business we audit that has pushed SPF near 512 bytes is a candidate for flattening anyway.