SPF Record Syntax: Complete Reference Guide

A complete technical reference for SPF record syntax, covering every mechanism, qualifier, and modifier defined in RFC 7208 — with real examples, a comparison table, and practical validation tips.


SPF records follow a precise syntax defined in RFC 7208. A single TXT record at your domain root contains a version tag, one or more mechanisms, optional modifiers, and a catch-all. Understanding each element lets you write accurate records, stay within DNS lookup limits, and avoid the common mistakes that cause SPF failures.

SPF Record Structure

Every SPF record starts with the version tag and is followed by terms separated by spaces:

text
v=spf1 [mechanisms] [modifiers] [all]

v=spf1 is mandatory and must appear first. There is only one SPF version, so this tag never changes. Everything after it is evaluated left to right. The all mechanism (with its qualifier) should always appear last as a catch-all fallback.

A Complete Real-World Example

Command
v=spf1 include:_spf.google.com include:mailgun.org ~all

This record authorises Google Workspace and Mailgun as sending sources, then softfails all other senders. It is a common baseline for businesses using a combination of hosted email and a transactional email service.

All SPF Mechanisms

ip4 — IPv4 Address or Range

Matches a single IPv4 address or a CIDR block. No DNS lookup is required, making it the most efficient mechanism.

Command
v=spf1 ip4:203.0.113.5 ~all v=spf1 ip4:203.0.113.0/24 ~all

ip6 — IPv6 Address or Prefix

The IPv6 equivalent of ip4. Accepts full IPv6 addresses or prefix-length notation. Also requires no DNS lookup.

Command
v=spf1 ip6:2001:db8::1 ip6:2001:db8::/32 ~all

a — Domain A/AAAA Records

Matches if the connecting IP is one of the A or AAAA records for the domain. If no domain is specified, the current domain is used. Consumes one DNS lookup.

Command
v=spf1 a ~all v=spf1 a:mail.example.com ~all

mx — Domain MX Records

Matches if the connecting IP is one of the mail servers listed in the domain's MX records. Useful if your inbound and outbound mail server are the same host. Consumes one DNS lookup per MX record resolved (up to a maximum of 10 per term).

Command
v=spf1 mx ~all

include — Delegate to Another Domain's SPF

The most commonly used mechanism. It fetches and evaluates the SPF record of the specified domain. If that domain's record returns Pass for the connecting IP, the include matches. Each include consumes at least one DNS lookup, plus any additional lookups made inside the referenced record.

Command
v=spf1 include:_spf.google.com include:mailgun.org ~all
include: Is Not Recursive InheritanceDespite the name, include: does not inherit all the rules of the other domain. It only passes if the other domain's SPF record would return Pass for that IP. If the included domain returns Neutral or None, the include does not match — evaluation continues to the next mechanism.

exists — Conditional A Record Check

Matches if the specified domain resolves to any A record. The domain can use macros (e.g., %{i} for the sending IP) to build dynamic lookup targets. This is an advanced mechanism used primarily by large ESP infrastructure.

Command
v=spf1 exists:%{i}.whitelist.example.com ~all

ptr — PTR Record Check (Deprecated)

Matches if the PTR record for the connecting IP resolves back to the specified domain. This mechanism is deprecated in RFC 7208 due to its unreliability and high DNS overhead. Do not use it in new records.

all — Catch-All

Always matches every IP. Because it matches everything, it must be the last mechanism in the record. Its qualifier determines what happens to senders not matched by any earlier mechanism.

Command
v=spf1 include:_spf.google.com -all ; hardfail: reject all others v=spf1 include:_spf.google.com ~all ; softfail: accept but mark suspicious v=spf1 include:_spf.google.com ?all ; neutral: no assertion v=spf1 include:_spf.google.com +all ; pass all (never use this)
Never Use +allUsing +all means every server in the world is authorised to send email for your domain. This completely defeats the purpose of SPF. Always end your record with ~all or-all.

SPF Qualifiers Reference

QualifierNameResult on MatchRecommended Use
+PassSender is authorised.Default for mechanisms; authorised senders.
-FailSender is not authorised; reject.Final -all when fully confident in all sources.
~SoftfailSender probably not authorised; accept with suspicion.Default catch-all during monitoring phase.
?NeutralNo assertion made.Rarely used; only when you genuinely cannot say.

SPF Modifiers

redirect — Replace the Entire Record

redirect=domain instructs the evaluator to use the SPF record of the target domain instead of continuing. Unlike include:, if redirect= is present, no all mechanism should be specified, as the target domain's catch-all applies.

Command
v=spf1 redirect=_spf.example.com

exp — Explanation String

exp=domain points to a TXT record containing a human-readable explanation shown to senders when their message fails SPF. This is optional and rarely used in practice.

The 10 DNS Lookup Limit

RFC 7208 allows a maximum of 10 DNS lookups during SPF evaluation. Mechanisms that count toward this limit are: include:, a, mx, ptr, and exists:. The ip4:, ip6:, and all mechanisms do not require DNS lookups and do not count.

Lookups triggered inside an include: — for example, if the included domain's SPF record itself contains include: statements — also count toward the total of 10. Exceeding the limit returns a PermError, which is treated as an authentication failure.

Prefer ip4 and ip6 Where PossibleIf you know the specific IP addresses used by a sending service, use ip4: orip6: instead of include:. This reduces your lookup count and makes your record more efficient. Check sending IP ranges in your email service provider's documentation.

Common SPF Record Examples

ScenarioSPF Record
Google Workspace onlyv=spf1 include:_spf.google.com ~all
Google Workspace + Mailgunv=spf1 include:_spf.google.com include:mailgun.org ~all
Single outbound IP onlyv=spf1 ip4:203.0.113.10 -all
No email sent from domainv=spf1 -all
Delegate to another domainv=spf1 redirect=_spf.provider.com

Validation Tips

  • Publish only one SPF TXT record per domain. Multiple records cause PermError.
  • Always end with ~all or -all. Never use +all.
  • Count your DNS lookups before publishing. Use the TXT Lookup tool to retrieve and review your record.
  • Test with a lower TTL (300 seconds) before deploying, then raise it after confirming correctness.
  • Do not publish SPF via a type=SPF DNS record — use TXT only. The SPF record type was deprecated in RFC 7208.

Frequently Asked Questions

What is the difference between include: and redirect= in SPF?

include: checks the target domain's SPF record and, if it returns Pass for the connecting IP, the include mechanism itself matches. Evaluation then continues if needed. By contrast,redirect= completely replaces the current domain's SPF evaluation with the target domain's record — it is as though the current domain has no SPF record of its own.

Can I use both redirect= and all in the same record?

No. If redirect= is present and there is no all mechanism in the current record, evaluation is handed to the target domain. If you include both, the redirect=modifier is ignored when an all mechanism is present.

How do I add a new sending service without exceeding the lookup limit?

First, check how many lookups your current record uses, including nested lookups within eachinclude:. If you are close to 10, consider replacing some include:directives with explicit ip4: ranges (available from your sending service's documentation) or use SPF flattening to collapse nested lookups into direct IP entries.

Is the order of mechanisms in an SPF record important?

Yes. SPF evaluates mechanisms from left to right and stops at the first match. For performance, put your most common sending sources first. The all mechanism must always be last because it matches everything, and anything after it would never be evaluated.

What does a PermError mean in practice?

A PermError means the SPF record is structurally invalid or the lookup limit was exceeded. Receiving servers typically treat PermError the same as a Fail, so your legitimate email may be rejected or flagged. You need to fix the record — either correcting syntax errors or reducing the number of DNS lookups — before delivery will recover.

Related Articles