SPF record syntax: a complete cheat sheet (with examples)
Every SPF mechanism, qualifier, and modifier — what they mean, when to use them, and the four mistakes that break 90% of records.
Sender Policy Framework records are deceptively simple — one TXT record at your domain root listing who's allowed to send. In practice, the syntax has enough quirks that the majority of records we see in WillItInbox reports are subtly broken. This is the cheat sheet we wish every email admin had taped to their monitor.
Anatomy of an SPF record
Every SPF record follows the same skeleton: a version tag, one or more mechanisms with optional qualifiers, and an all mechanism at the end. Here's a complete record with annotations.
v=spf1 ip4:192.0.2.0/24 include:_spf.google.com include:sendgrid.net ~all- `v=spf1` — the version tag. Always required, always exactly this string.
- `ip4:192.0.2.0/24` — authorize a specific IPv4 range.
- `include:_spf.google.com` — defer to Google's SPF record. Counts as one DNS lookup.
- `include:sendgrid.net` — same, for SendGrid.
- `~all` — softfail anything that doesn't match (allow, but mark suspicious).
Every mechanism, in plain English
| Mechanism | Matches | DNS lookups |
|---|---|---|
all | Always matches (use last with a qualifier) | 0 |
ip4:<addr> | An IPv4 address or CIDR range | 0 |
ip6:<addr> | An IPv6 address or CIDR range | 0 |
a / a:<domain> | The A record(s) of the domain | 1 |
mx / mx:<domain> | The MX hosts of the domain | 1 (plus 1 per MX) |
include:<domain> | The included domain's SPF record | 1 (plus its lookups) |
exists:<domain> | True if the domain resolves at all | 1 |
ptr / ptr:<domain> | Reverse DNS of the connecting IP. Deprecated. | Many |
Qualifiers — the symbol before each mechanism
- `+` (Pass) — implicit default.
include:foois the same as+include:foo. - `~` (SoftFail) — receivers should accept but mark as suspicious. Common on
~all. - `-` (Fail) — receivers should reject. Use on
-allonce you're confident. - `?` (Neutral) — no opinion. Useless in practice.
The 10-DNS-lookup limit
RFC 7208 §4.6.4 caps the total number of DNS lookups during SPF evaluation at 10. Exceed this and the result is permerror — which most receivers treat as a hard fail. Lookup-counting mechanisms include include, a, mx, exists, ptr, and redirect=. Nested includes count too: if include:_spf.salesforce.com itself contains five includes, you've used six lookups before doing anything else.
Three reliable ways to stay under the cap:
- Remove unused providers. That ESP from three years ago doesn't need to be in the record. Audit annually.
- Replace `include:` with `ip4:`. If a provider publishes a stable IP list, paste the IPs directly. You trade one
includelookup for zero lookups, at the cost of having to update when the provider changes IPs (rare for well-run ESPs). - Use SPF flattening. Tools like spf-flatten or scl-spf rewrite your record at publish time, expanding all
includes to literal IPs. Schedule a monthly re-flatten so provider changes are picked up.
The `redirect=` modifier
If you operate many domains that share one SPF policy, redirect= lets the others delegate. The redirect target's SPF replaces yours entirely — your all is ignored.
v=spf1 redirect=_spf.example.comUseful for keeping a single source of truth across, say, example.com and example.co.uk. Not to be combined with mechanisms in the same record — the redirect overrides them.
The `exp=` modifier
An optional explanation string returned to senders when SPF fails. Almost no one uses it because almost no one reads bounce messages. You can safely ignore it.
Real-world example records
Small business — only Google Workspace
v=spf1 include:_spf.google.com -allMarketing + transactional split
v=spf1 include:_spf.google.com include:mailgun.org include:sendgrid.net ~allSelf-hosted with a single fixed IP
v=spf1 ip4:203.0.113.42 a:mail.example.com -allThe four mistakes that break 90% of records
- Multiple SPF records. A second
v=spf1record at the same domain causespermerror. Merge into one. - Exceeding 10 lookups. Same
permerrorresult. Audit and flatten. - Trailing `+all` or `?all`. Marketing tools that auto-generate records sometimes do this. Always check the final mechanism.
- Stale providers. A 2-year-old provider still in your record is an unmaintained attack surface. Audit annually.
Publishing changes safely
- Lower the existing record's TTL to 300 (5 minutes) at least 24 hours before changes.
- Make the change. Verify with
dig txt yourdomain.comfrom a few resolvers. - Send a test through WillItInbox. Confirm the change is reflected.
- Wait 48 hours. Watch DMARC aggregate reports for SPF failures.
- Restore the TTL to 3600 once stable.
Frequently asked questions
Last updated April 26, 2026.
Keep reading