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.

10
max DNS lookups allowed in one SPF record
512
bytes — SPF record max before truncation risk
permerror
exceeding 10 lookups returns this — equals SPF fail
~all
softfail — still used by 38% of senders; too permissive

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:

MechanismExampleCounts toward limit?Use case
ip4:ip4:203.0.113.0/24NoDirect IP ranges — preferred, no lookup
ip6:ip6:2001:db8::/32NoIPv6 ranges — also no DNS lookup
include:include:_spf.google.comYes +1 per includeThird-party senders; each costs 1 lookup
a:a:mail.company.comYes +1A record of a domain; use sparingly
mx:mx:company.comYes +1MX record of domain; common but costly
redirect=redirect=_spf.otherdomain.comYes — replaces policyDelegates SPF to another domain entirely
all-all / ~all / +allNoTerminal qualifier — always last
SPF pass — correct alignment
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.
SPF permerror — too many DNS lookups (common misconfiguration)
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 address
  • ip4:x.x.x.x/24 — authorises an IPv4 CIDR range
  • ip6:2001:db8::1 — authorises a specific IPv6 address
  • include: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 records
  • mx — authorises the IP addresses in the domain's MX records
  • exists:domain.com — passes if the specified domain resolves
  • redirect=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:

IncludeDirect lookupsNotes
include:_spf.google.com2–3Expands to several sub-includes
include:sendgrid.net2Moderate
include:spf.protection.outlook.com3–4Complex; expands deeply
include:mailgun.org2Moderate
include:amazonses.com3Expands 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 +all authorises 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 notation ip4:1.2.3.0/24. Don't use ip4:1.2.3.* — that's not valid SPF syntax.