SPF is the simplest of the three big authentication standards — and also the one we see misconfigured most often. The record itself is one TXT line in DNS. The concepts behind it are a weekend's worth of edge cases. This guide covers both: how the standard actually works, what a correct record looks like for every common setup, and the mistakes that quietly drop you into Spam.
Publish one TXT record at your domain apex: v=spf1 followed by include: entries for every service that sends on your behalf, ending with ~all during rollout or -all once you trust it. Stay under 10 DNS lookups. Don't publish two SPF records — that alone fails the whole check.
What SPF actually does
SPF — Sender Policy Framework — lets a domain owner publish a list of IP addresses authorised to send mail for that domain. When a receiving server gets a message, it reads the envelope sender (the MAIL FROM domain, sometimes called Return-Path), looks up that domain's SPF record, and checks whether the connecting IP is on the authorised list.
That's the whole mechanism. It does not sign messages, it does not protect the body or headers from tampering, and it cares only about the envelope sender — not the visible From: address a user sees. That limitation is why DKIM and DMARC exist on top of it. SPF alone is a necessary foundation, not a complete authentication story.
Anatomy of an SPF record
Every SPF record lives in a TXT record at the domain root and starts with the version tag v=spf1. After that comes a space-separated list of mechanisms, each prefixed with a qualifier, ending with an all terminator.
The mechanisms you'll use in practice:
ip4:203.0.113.10— authorise a single IPv4 address.ip4:203.0.113.0/24— authorise a whole CIDR block.ip6:2001:db8::/32— same, IPv6.aora:mail.example.com— the A record of the domain (or the named host) is authorised.mx— every IP in your MX records is authorised. Useful for self-hosted mail, dangerous otherwise.include:_spf.google.com— import another domain's SPF record. This is how ESPs hand you a pre-maintained list.
Qualifiers sit in front of a mechanism and decide what happens on a match:
+(default) — pass.-— fail. Reject or quarantine.~— softfail. Accept but mark.?— neutral. Effectively no policy.
The record ends with all, preceded by a qualifier. The terminator decides what happens to everything the record didn't match. -all says "anything else fails." ~all says "anything else is suspect, accept with a warning."
Typical records for real setups
Google Workspace only — the most common starter record:
v=spf1 include:_spf.google.com ~allMicrosoft 365 only:
v=spf1 include:spf.protection.outlook.com -allGoogle Workspace plus SendGrid (for transactional mail):
v=spf1 include:_spf.google.com include:sendgrid.net ~allA richer hybrid — Google Workspace for humans, Amazon SES for transactional, Mailgun for marketing, and a single static sending IP for your billing server:
v=spf1 ip4:203.0.113.10 include:_spf.google.com include:amazonses.com include:mailgun.org ~allThe order doesn't matter for correctness, but readability does — keep the most frequently used senders first, since evaluation stops at the first matching mechanism.
The 10-lookup limit
SPF evaluation is capped at 10 DNS lookups total. Exceed that and the receiver returns permerror, which most filters treat as no SPF at all. Every include:, a, mx, exists: and redirect= counts as one lookup — and nested includes count too.
The typical failure: someone starts with Google Workspace (1 lookup), adds SendGrid (1 lookup), then Mailchimp (1), then Klaviyo (1), then Salesforce Marketing Cloud (5 nested lookups on its own) and a new helpdesk platform. They hit 11 and the whole record quietly breaks.
Audit tools like dig +short TXT plus a manual walk of each include: will tell you the count. Online checkers such as MXToolbox, Dmarcian and Easydmarc report it directly.
Flattening vs subdomain strategy
Two ways to stay under the limit when you have many vendors.
Flattening means resolving every include: down to a list of raw ip4: and ip6: addresses and publishing those directly. Reduces lookups to zero but creates a maintenance problem: ESPs rotate their IP blocks regularly, and a stale flattened record silently drops real senders. Services like Dmarcian SPF Surveyor and EasyDMARC offer managed flattening with dynamic updates, which is the only safe way to do it.
Subdomain separation is cleaner: send transactional mail from tx.example.com, marketing from mkt.example.com, support from help.example.com, each with its own SPF record listing only that vendor. Downsides: you need to configure DKIM and return-path per subdomain, and the visible From address typically changes. Upside: every subdomain stays well under the 10-lookup ceiling and one vendor's reputation can't drag another's down.
Common mistakes
The mistakes we see every week running placement tests:
- Two SPF records. RFC 7208 is explicit: exactly one
v=spf1record per domain. Two records = permerror, which most filters treat as no SPF. Happens when people add a second record instead of editing the first. - Missing a major ESP. The team signs up for Postmark for transactional, no one updates DNS. All transactional mail softfails.
- Wrong terminator.
+allis catastrophic — it authorises the entire internet. Don't ever ship that, even temporarily. - Typos in
ip4:.ip4:203.0.113(missing the last octet) oripv4:(wrong mechanism name) fail silently. - Forgetting DMARC alignment. SPF can pass with a Return-Path of
bounces.sendgrid.netwhile your From header isyou@brand.com. DMARC will still fail unless you configure a custom Return-Path that aligns.
Testing SPF
Start with dig, straight from the terminal:
dig +short TXT example.com | grep spf1If you see your record and nothing else, good. If you see two SPF records, fix that today. Next, send a test message to a Gmail address you control and look at "Show original" — the Authentication-Results header spells out spf=pass, spf=softfail or spf=fail directly.
For continuous monitoring, DMARC aggregate reports (rua=) give you per-source SPF pass/fail counts from real receivers, which beats any static checker.
Softfail vs hardfail
~all (softfail) tells receivers "unauthorised IPs are suspect, but still accept the mail." -all (hardfail) says "reject outright." The received behaviour varies — Gmail generally treats softfail as a mild signal and hardfail as grounds for the Spam folder, not a reject. But some stricter corporate filters do reject on -all.
Rollout order: start with ~all while you audit your senders through DMARC reports. Once two weeks of reports show only known, expected sources passing, switch to -all. This catches accidental omissions before they start bouncing legitimate mail.
- One TXT record, not two, at the domain apex.
- Every sending vendor either included or listed by IP.
- Lookup count audited and under 10.
- Terminator is
~allor-all, never+all. - Return-Path aligned with From domain for DMARC.
- DMARC reports confirming expected pass/fail sources for two weeks.
- Moved from
~allto-allonce confident.