SPF (Sender Policy Framework) is the email authentication mechanism that specifies which IP addresses and mail servers are authorised to send email on behalf of a domain. It works through a DNS TXT record that receiving mail servers query when they receive a message claiming to be from your domain. Despite being one of the oldest email authentication standards (RFC 7208, standardised in 2014 and widely deployed since the early 2000s), SPF configuration mistakes remain among the most common causes of DMARC failure — primarily because of one constraint that catches nearly every growing organisation: the 10 DNS lookup limit.
What SPF Does (and Doesn't Do)
SPF authenticates the envelope sender — the MAIL FROM address used in the SMTP transaction, also called the Return-Path or bounce address. This is often different from the From: header address that recipients see in their email client. SPF does not authenticate the visible From: header — that's DKIM's job.
When a receiving server gets a message claiming to be from yourdomain.com, it looks up the SPF record for yourdomain.com and checks whether the connecting IP address appears in that record. If yes: SPF pass. If the IP is explicitly excluded: SPF fail. If the IP isn't mentioned: SPF softfail (if ~all) or neutral.
SPF alone does not prevent phishing when the attacker uses a From: header spoofing your domain but a different Return-Path. DMARC closes this gap by requiring SPF alignment — the Return-Path domain must match the From: header domain for SPF to count toward DMARC compliance.
SPF Record Syntax Explained
# Basic SPF record structure
v=spf1 [mechanisms] [qualifier]all
# Example: Allow SendGrid, a specific IP, and Google Workspace
v=spf1 include:sendgrid.net ip4:203.0.113.10 include:_spf.google.com -all
Breaking down each component:
| Mechanism | Example | Counts toward limit? | Use case |
|---|---|---|---|
| ip4: | ip4:203.0.113.0/24 | No | Direct IP ranges — preferred, no lookup |
| ip6: | ip6:2001:db8::/32 | No | IPv6 ranges — also no DNS lookup |
| include: | include:_spf.google.com | Yes +1 per include | Third-party senders; each costs 1 lookup |
| a: | a:mail.company.com | Yes +1 | A record of a domain; use sparingly |
| mx: | mx:company.com | Yes +1 | MX record of domain; common but costly |
| redirect= | redirect=_spf.otherdomain.com | Yes — replaces policy | Delegates SPF to another domain entirely |
| all | -all / ~all / +all | No | Terminal qualifier — always last |
Received-SPF: pass (google.com: domain of noreply@yourcompany.com
designates 52.218.x.x as permitted sender)
client-ip=52.218.x.x;
Authentication-Results: mx.google.com;
spf=pass (google.com: domain of noreply@yourcompany.com
designates 52.218.x.x as permitted sender)
smtp.mailfrom=noreply@yourcompany.com;
# The envelope From (smtp.mailfrom) matches the domain in your SPF record.
# 52.218.x.x falls inside "include:amazonses.com" in the SPF record.
Received-SPF: permerror (google.com: permanent error in processing
during lookup of "yourcompany.com": DNS error)
Authentication-Results: mx.google.com;
spf=permerror (google.com: permanent error in processing)
smtp.mailfrom=yourcompany.com;
# permerror = exceeded 10 DNS lookups. Treated as SPF FAIL by most ISPs.
# Common cause: include:_spf.google.com + include:amazonses.com +
# include:salesforce.com + include:sendgrid.net = 12+ lookups.
# Fix: use SPF flattening tool or replace includes with ip4: mechanims.
v=spf1 — version identifier; always the first element. Required.
Mechanisms:
ip4:x.x.x.x— authorises a specific IPv4 addressip4:x.x.x.x/24— authorises an IPv4 CIDR rangeip6:2001:db8::1— authorises a specific IPv6 addressinclude:domain.com— includes the SPF record of another domain (performs a DNS lookup — counts against the 10-lookup limit)a— authorises the IP addresses in the domain's A recordsmx— authorises the IP addresses in the domain's MX recordsexists:domain.com— passes if the specified domain resolvesredirect=domain.com— redirects the entire SPF evaluation to another domain's SPF record
Qualifiers (before the mechanism):
+(default, pass) — this mechanism passing = SPF pass-(fail) — explicitly not authorised: results in SPF fail~(softfail) — not authorised but not hard fail: results in SPF softfail (treated as pass by many ISPs)?(neutral) — no assertion: neither pass nor fail
The all mechanism: Always the last element. Controls what happens for IPs not matched by any other mechanism. -all = hard fail (strongly recommended). ~all = softfail (pass for most ISPs). +all = allow everything (never use this — it negates SPF entirely).
# Common SPF record examples
# Gmail/Google Workspace only
v=spf1 include:_spf.google.com -all
# Microsoft 365 only
v=spf1 include:spf.protection.outlook.com -all
# SendGrid only
v=spf1 include:sendgrid.net -all
# Multiple services
v=spf1 include:_spf.google.com include:sendgrid.net include:spf.mailgun.org ip4:203.0.113.10 -all
# For a domain that sends NO email (defensive SPF)
v=spf1 -all
The 10 DNS Lookup Limit: The Most Common SPF Problem
RFC 7208 limits SPF evaluation to a maximum of 10 DNS lookups per SPF check. Each include:, a, mx, exists:, and redirect= mechanism counts as a lookup. If evaluation requires more than 10 lookups, the result is permerror — which many ISPs treat as a fail, breaking DMARC compliance.
The problem compounds: when you include include:sendgrid.net, that performs one lookup to get SendGrid's SPF record — but SendGrid's record may itself contain additional include: statements that perform their own lookups. All of those count toward your 10-lookup limit.
Check your current lookup count:
# Manual check
dig TXT yourdomain.com | grep "v=spf1"
# Then trace each include recursively
# Automated checkers (recommended)
# - MXToolbox SPF checker: mxtoolbox.com/SuperTool.aspx (select SPF)
# - Dmarcian SPF Inspector: dmarcian.com/spf-survey/
# - Kitterman SPF validator: kitterman.com/spf/validate.html
# These tools count lookups automatically and flag permerror
Common lookup-intensive SPF includes and their approximate lookup cost:
| Include | Direct lookups | Notes |
|---|---|---|
| include:_spf.google.com | 2–3 | Expands to several sub-includes |
| include:sendgrid.net | 2 | Moderate |
| include:spf.protection.outlook.com | 3–4 | Complex; expands deeply |
| include:mailgun.org | 2 | Moderate |
| include:amazonses.com | 3 | Expands to regional subsets |
If you use more than 3 major email services, you'll almost certainly hit the 10-lookup limit.
How to Flatten an SPF Record
SPF flattening resolves all the include chains to their final IP addresses and replaces them with explicit ip4: and ip6: directives. This eliminates the DNS lookups for those includes, dramatically reducing your lookup count.
# Before flattening (exceeds 10 lookups)
v=spf1 include:_spf.google.com include:sendgrid.net include:spf.protection.outlook.com
include:spf.mailgun.org include:amazonses.com ip4:203.0.113.10 -all
# After flattening (manually resolved IPs — lookup count: 0 + 1 for ip4 = 1 total)
v=spf1 ip4:209.85.128.0/19 ip4:209.85.192.0/19 ip4:66.102.0.0/20
ip4:167.89.0.0/16 ip4:198.21.0.0/21 ip4:159.122.0.0/17
ip4:13.110.95.0/26 ip4:13.110.122.0/26
ip4:40.107.0.0/16 ip4:52.58.0.0/16
ip4:203.0.113.10 -all
The problem with manual flattening: When your email service providers add new IP ranges (which they do regularly), your flattened record becomes outdated and valid emails start failing SPF. Manual flattening requires ongoing maintenance to stay current.
Automated SPF flattening services: AutoSPF, dmarcian's SPF Macro, and similar tools flatten the record automatically and update it when providers change their IP ranges. They typically replace the include chain with a single include pointing to a hosted, automatically-updated record. This eliminates the lookup problem while maintaining accuracy.
SPF Alignment and DMARC
For SPF to satisfy DMARC, the domain in the Return-Path (MAIL FROM) must align with the From: header domain. Under relaxed alignment (default), the organisational domain must match. Under strict alignment (aspf=s), they must match exactly.
# Aligned: SPF pass + DMARC SPF alignment pass
# From: newsletter@yourdomain.com
# Return-Path: bounce@yourdomain.com ← same org domain: yourdomain.com ✅
# Misaligned: SPF pass but DMARC SPF alignment FAIL
# From: newsletter@yourdomain.com
# Return-Path: bounce@sendingplatform.com ← different org domain ❌
When your ESP sends email on your behalf using their infrastructure, the Return-Path is typically at the ESP's domain (e.g., bounces.sendgrid.net). SPF passes for the ESP's domain, but DMARC fails because it doesn't align with your From: header domain.
Fix: Configure a custom bounce domain (Return-Path domain) on your ESP — typically a CNAME like bounce.yourdomain.com pointing to the ESP's bounce handling. This makes the Return-Path domain match your From: domain organisationally, achieving DMARC alignment.
Diagnosing SPF Failures from Email Headers
# Authentication-Results header shows SPF result
Authentication-Results: mx.google.com;
spf=pass (google.com: domain of bounce@yourdomain.com designates
203.0.113.10 as permitted sender)
smtp.mailfrom=bounce@yourdomain.com;
# Decoding the key fields:
# spf=pass/fail/softfail/neutral/none/permerror/temperror
# smtp.mailfrom= = the Return-Path domain that was checked (not the From: header)
# Received-SPF header (older format, still common)
Received-SPF: pass (google.com: domain of bounce@yourdomain.com
designates 203.0.113.10 as permitted sender)
receiver=google.com; client-ip=203.0.113.10;
helo=mail.yourdomain.com;
envelope-from=bounce@yourdomain.com
Key diagnostic signals:
spf=none— no SPF record found for the envelope sender domain. Publish an SPF record.spf=fail— sending IP is explicitly excluded (-all). Add the IP to your record or fix the Return-Path.spf=softfail— not authorised but with ~all. Usually treated as pass by ISPs; upgrade to -all after verifying all legitimate senders are included.spf=permerror— configuration error (most commonly: exceeded 10 DNS lookups, or syntax error). Fix the record.spf=temperror— transient DNS error. Usually self-resolving; if persistent, check your DNS provider.
Common SPF Configuration Mistakes
- Multiple SPF records: A domain can have only one SPF TXT record. If you have two records starting with v=spf1, evaluation fails with permerror. Merge them into a single record.
- Using +all:
v=spf1 +allauthorises any IP to send email for your domain. This negates SPF entirely and will cause DMARC tools to flag your domain. - Not including all sending services: Every service that sends email with a Return-Path at your domain must be in your SPF record. Missed includes = SPF fail for that service's mail.
- Confusing the From: domain with the Return-Path domain: SPF checks the Return-Path domain, not the From: header domain. If your ESP uses their domain as Return-Path, SPF passes for the ESP's domain regardless of what your SPF record says.
- IP4 ranges without CIDR notation: A single IP must be
ip4:1.2.3.4; a range must use CIDR notationip4:1.2.3.0/24. Don't useip4:1.2.3.*— that's not valid SPF syntax.

