← Email SecurityEmail Auth / Foundation

SPF — Sender Policy Framework

A DNS TXT record that authorizes which mail servers are permitted to send email on behalf of your domain — the first line of defense against email spoofing.

How It Works

Publishing a Sender Allowlist in DNS

SPF works by publishing a TXT record at your domain's apex (e.g., example.com IN TXT "v=spf1 ..."). When a receiving mail server gets a message claiming to be from @example.com, it looks up that TXT record and checks whether the sending server's IP is authorized.

SPF checks the envelope From address (also called MAIL FROM or return-path) — not the header From that users see in their email client. This distinction matters: SPF passes even if the visible From header is spoofed to a different domain, which is why DMARC alignment is required to close that gap.

The result is one of: pass, fail, softfail, neutral, none (no SPF record), temperror, or permerror. Receiving servers use this result — along with DKIM and DMARC — to decide whether to accept, quarantine, or reject the message.

SPF Checks Envelope From, Not Header FromThe envelope From (return-path) is set during the SMTP transaction and is invisible to end users. The header From is what appears in "From:" in email clients. Attackers can pass SPF by controlling a legitimate domain for the envelope while spoofing the visible From — DMARC alignment closes this.
; Full SPF record example ; Multiple authorized senders example.com. IN TXT "v=spf1 ip4:203.0.113.10 ; dedicated sending IP ip4:198.51.100.0/24 ; on-prem mail relay range include:spf.sendgrid.net ; SendGrid ESP include:_spf.google.com ; Google Workspace ~all" ; softfail everything else
Reference

SPF Mechanisms and Modifiers

Each mechanism in an SPF record specifies a way to authorize sending IPs

MechanismExampleDescription
ip4ip4:203.0.113.0/24Authorize an IPv4 address or CIDR range. Most explicit and recommended for known sending IPs.
ip6ip6:2001:db8::/32Authorize an IPv6 address or CIDR range.
aa:mail.example.comAuthorize the A/AAAA record(s) of the given hostname (or the domain itself if omitted). Counts as 1 DNS lookup.
mxmx:example.comAuthorize the IP(s) of the domain's MX records. Common but costs multiple lookups (1 for MX + 1 per MX host).
includeinclude:spf.mailgun.orgRecursively evaluate the SPF record of another domain. Used to authorize third-party senders (ESPs, CRMs). Each include costs at least 1 lookup.
redirectredirect=spf.example.comModifier: replace the entire SPF evaluation with another domain's record. Unlike include, the result replaces — not merges — the policy.
existsexists:%{i}.spf.example.comPerform a DNS lookup for a dynamically constructed domain (using macros). Used for complex per-IP authorization logic.
all-all / ~all / ?allCatch-all at the end. -all = hard fail, ~all = softfail, +all = pass everything (dangerous), ?all = neutral.
Critical Limitation

The 10 DNS Lookup Limit

RFC 7208 limits SPF evaluation to 10 DNS lookups. Each include:, a:, mx:, and exists: mechanism costs at least one lookup. Nested includes count recursively. Exceeding 10 lookups causes a permerror — the SPF record permanently fails, and many receiving servers treat this the same as SPF fail.

Organizations that use many ESPs (email service providers), CRMs, and marketing platforms commonly exceed this limit. Solutions include SPF flattening — replacing include: mechanisms with the resolved IP ranges — or using a managed SPF service that dynamically serves a flattened record.

  • ip4: and ip6: cost zero lookups — always prefer these for known static IPs
  • Each include: may nest further includes — count all recursively
  • SPF flattening must be re-run when the ESP changes their IP ranges
  • Tools: MXToolbox, dmarcian, PowerSPF for lookup counting and flattening
  • PTR mechanism is explicitly deprecated — do not use it
; Count lookups before publishing ; (each indented item = 1 lookup) v=spf1 include:spf.sendgrid.net ; 1 include:_spf.sendgrid.com ; 2 (nested) include:_spf.google.com ; 3 include:_netblocks.google.com ; 4 include:_netblocks2.google.com; 5 include:_netblocks3.google.com; 6 include:spf.protection.outlook.com ; 7 include:spf.mailgun.org ; 8 include:mailgun.org ; 9 (nested) mx:example.com ; 10 ← AT LIMIT ip4:203.0.113.10 ; 0 lookups ~all ; One more include = permerror
Known Limitation

SPF and Email Forwarding

Email forwarding breaks SPF. When a message is forwarded by an intermediate server (e.g., a university alias, a mailing list, or an old address redirect), the forwarding server's IP is not in the original sender's SPF record. The receiving server sees the forwarding server's IP — which fails SPF for the original sender's domain.

Sender Rewriting Scheme (SRS) addresses this by rewriting the envelope From address when forwarding. The rewritten address uses the forwarding domain, which passes SPF. DMARC sees the forwarded message as failing SPF alignment — but DKIM alignment (which survives forwarding) can carry DMARC on its own.

This is one reason DKIM + DMARC together are more robust than SPF alone: DKIM signatures survive forwarding (as long as the forwarder doesn't modify the message body), while SPF always breaks.

Forwarding Chain
  1. Sender: user@company.com
  2. Forwarded by: alias@university.edu
  3. Received by: inbox@gmail.com

Gmail checks SPF for company.com but sees university.edu's IP → SPF fail. DKIM from company.com still validates → DMARC passes via DKIM alignment.

Diagnostics

Inspecting and Troubleshooting SPF

Look up your SPF record

# Query SPF TXT record dig TXT example.com +short | grep spf # Or from specific resolver dig TXT example.com @8.8.8.8 +short # Multiple TXT records = invalid SPF # Only one v=spf1 record is allowed

Count DNS lookups

# Manual trace with dig dig TXT spf.sendgrid.net +short dig TXT _spf.google.com +short # Check if flattening is needed # Tools: # mxtoolbox.com/spf.aspx # dmarcian.com/spf-survey/

Simulate SPF check

# Check if an IP passes SPF # for a given domain # Using spfquery (postfix-utils) spfquery -ip 203.0.113.10 \ -sender user@example.com \ -helo mail.example.com # Or: pyspf / checkdmarc

Validate syntax

# RFC 7208 compliance check # (kitterman.com/spf/validate.html) # Common errors: # - More than 1 v=spf1 TXT record # - Exceeding 10 DNS lookups # - Missing -all or ~all # - Mechanisms after "all" (ignored)