A correctly-signed message can still be rejected because of SPF — not because SPF is broken, but because almost nobody knows what SPF actually authenticates. SPF does not verify the sender you see in your inbox. It verifies a sender most users never look at, and that gap is where deliverability quietly dies.
What SPF actually authenticates
Every SMTP transaction has two addresses. There is the visible From: header that your recipient reads, and there is the SMTP envelope sender — the MAIL FROM — used at the protocol level for bounce routing. SPF only cares about the envelope sender, not the header you see in Gmail.
When a receiving server accepts a connection, it extracts the domain from MAIL FROM, looks up that domain's v=spf1 TXT record, and walks the listed mechanisms to decide whether the connecting IP is authorised. The result is one of: pass, fail, softfail, neutral, none, temperror, or permerror.
How SPF works at the protocol level
The flow is straightforward, but the details matter:
Sender IP 203.0.113.5 ──HELO──▶ receiver.example
──MAIL FROM:<bounce@yourdomain.com>──▶
receiver.example then asks DNS:
TXT yourdomain.com → "v=spf1 include:_spf.google.com ~all"
It expands include: into another lookup, walks mechanisms
left-to-right, and returns the first match. If 203.0.113.5
matches an authorised mechanism → pass. Otherwise → fail/softfail.The mechanisms you'll see in real records:
ip4:/ip6:— literal addresses or CIDR ranges. Zero lookups.a/mx— resolve the domain's A or MX records and match. Cost: one lookup each.include:— splice another domain's SPF record into yours. Cost: one lookup, plus everything that record costs.exists:— pass if the named hostname resolves. Cost: one lookup.all— the catch-all at the end, prefixed with-(hardfail),~(softfail),?(neutral), or+(pass — never use this).
Why SPF fails in production
1. The 10-DNS-lookup limit
RFC 7208 caps SPF evaluation at ten DNS lookups. The mechanisms that consume lookups are include, a, mx, ptr, and exists. A common record like include:_spf.google.com include:mailgun.org include:sendgrid.net can blow through the limit on its own once you expand each provider's nested includes.
When you exceed ten lookups, receivers return permerror and SPF effectively does not exist for that message — which becomes a DMARC fail unless DKIM aligns. The fix is SPF flattening (replacing include: with the resolved IP ranges) or consolidating senders, not adding more include statements.
2. Forgotten third-party senders
The other failure pattern is structural: your billing platform, support desk, CRM, transactional ESP, and marketing ESP all send mail "from" your domain. If any one of them isn't in your SPF record, that traffic fails. DMARC aggregate reports are the canonical way to find these — see the DMARC lesson for how to read them.
3. Forwarding breaks SPF
When a recipient forwards your message — a mailing list, a .forward rule, an alias — the forwarding server becomes the new SMTP sender. Your SPF record no longer covers it. This is structural, not a misconfiguration, which is why DMARC requires either aligned SPF or aligned DKIM. DKIM signatures survive forwarding; the DKIM lesson covers why.
4. Alignment with DMARC
SPF passing is not the same as SPF aligning. DMARC requires the domain in the SMTP envelope (MAIL FROM) to match the domain in the visible From: header. Many ESPs send with their own bounce domain by default — SPF passes against the ESP's domain, but the alignment check against your header From fails. Configure a custom return-path / bounce domain on your sending domain to fix this.
5. The +all and ?all booby traps
+all means "anyone in the world is authorised to send as my domain." It appears in old copy-pasted records and turns SPF into a rubber stamp for spoofers. ?all (neutral) is functionally the same. If you see either, replace them with ~all or -all immediately.
How to check your SPF record
Before you change anything, look at what you have published. The SPF checker resolves your record, expands every include:, counts DNS lookups, and flags syntax issues. If you've already deployed DMARC, the DMARC checker will show whether your published policy is parseable.
Read the Authentication-Results header on a message you sent to yourself at Gmail. You want to see spf=pass with the envelope domain matching your header From. Anything else — softfail, neutral, none — is a finding, not a passing result.
When you're ready to deploy
Per-ESP SPF setup pages have the literal records to publish, including the correct include: token for each provider:
SPF is the easy half of email authentication. The hard half — proving the message wasn't tampered with in flight, and surviving forwarding — is what DKIM exists for. And neither matters to a receiver until you tie them together with DMARC.
Looking for the short definition? See SPF in the glossary.
