SPF is the oldest and simplest email authentication standard, introduced in 2003. Despite its age — and its limitations — it’s a required foundation for email deliverability and spoofing protection. If your domain sends email and has no SPF record, fix that before everything else.
What SPF Does
SPF lets you publish a list of authorized mail servers in DNS. When a receiving mail server gets email claiming to be from @yourdomain.com, it:
- Extracts the envelope sender domain from the
MAIL FROMcommand - Looks up the SPF TXT record at that domain
- Checks whether the sending IP matches any entry in the record
- Returns
Pass,Fail,SoftFail,Neutral, orPermError
Email arrives from IP 74.125.130.27 claiming MAIL FROM: <anything@example.com>
↓
DNS lookup: example.com TXT → "v=spf1 include:_spf.google.com ~all"
↓
Expand include:_spf.google.com → contains 74.125.130.27
↓
Result: SPF Pass ✓
SPF Record Syntax
An SPF record is a TXT record published at your root domain. It always starts with v=spf1:
example.com. TXT "v=spf1 ip4:203.0.113.0/24 include:_spf.google.com include:sendgrid.net ~all"
Mechanisms
| Mechanism | Example | Matches |
|---|---|---|
ip4: | ip4:203.0.113.0/24 | IPv4 address or CIDR range |
ip6: | ip6:2001:db8::/32 | IPv6 address or CIDR range |
include: | include:_spf.google.com | All IPs listed in another domain’s SPF |
a | a or a:example.com | A record(s) of the domain |
mx | mx or mx:example.com | MX record hosts of the domain |
all | ~all or -all | Matches everything (catch-all, always last) |
Qualifiers
Every mechanism can be prefixed with a qualifier:
| Qualifier | Symbol | Meaning |
|---|---|---|
| Pass | + (default) | Authorized — accept |
| Fail | - | Not authorized — reject |
| SoftFail | ~ | Suspicious — accept but flag |
| Neutral | ? | No policy |
All Qualifier: What to Use
; Strict: reject emails from unlisted IPs
"v=spf1 include:_spf.google.com -all"
; Soft: flag but deliver (recommended while setting up)
"v=spf1 include:_spf.google.com ~all"
; No policy (useless for security)
"v=spf1 include:_spf.google.com ?all"
Start with ~all while you’re confirming all sending sources are included. Switch to -all once you’re confident. With DMARC p=reject, the distinction matters less.
Common Sending Sources to Include
| Email Service | Include Mechanism |
|---|---|
| Google Workspace | include:_spf.google.com |
| Microsoft 365 | include:spf.protection.outlook.com |
| Mailchimp | include:servers.mcsv.net |
| SendGrid | include:sendgrid.net |
| Mailgun | include:mailgun.org |
| HubSpot | include:_spf.hubspot.com |
| Postmark | include:spf.mtasv.net |
| Amazon SES | include:amazonses.com |
Only include the services that actually send email for your domain. Every include: costs one DNS lookup.
The 10-Lookup Limit
RFC 7208 limits SPF evaluation to 10 DNS lookups. Each include:, a:, mx:, or ptr: mechanism triggers a lookup (and the lookups they trigger count too).
; This record might look fine but could exceed 10 lookups:
"v=spf1 include:_spf.google.com include:spf.protection.outlook.com
include:sendgrid.net include:mailgun.org include:_spf.hubspot.com
include:servers.mcsv.net include:amazonses.com ~all"
If you hit the limit, receiving servers return PermError — treated like a misconfiguration.
Fix: Replace include: with explicit ip4:/ip6: ranges. Look up what IPs each service’s SPF include expands to and add them directly. This is called SPF flattening. Some tools do this automatically and keep the flattened record updated.
Writing Your First SPF Record
Step 1: Identify all services that send email using your domain:
- Your main email provider (Google Workspace, M365)
- Your marketing platform (Mailchimp, HubSpot)
- Your transactional email provider (SendGrid, Postmark)
- Any custom applications or servers
Step 2: Build the record:
; Simple: only Google Workspace
example.com. TXT "v=spf1 include:_spf.google.com ~all"
; Multiple services + dedicated server IP
example.com. TXT "v=spf1 ip4:203.0.113.42 include:_spf.google.com include:sendgrid.net ~all"
Step 3: Publish as a TXT record at your root domain (@ or example.com). TTL of 300-3600 seconds.
Step 4: Verify:
dig TXT example.com | grep spf
# or
nslookup -type=TXT example.com
Verifying SPF Pass in Email Headers
When you send a test email to Gmail and view the original headers, look for:
Received-SPF: pass (google.com: domain of sender@example.com designates
74.125.130.27 as permitted sender) client-ip=74.125.130.27;
Authentication-Results: mx.google.com;
spf=pass (google.com: domain of sender@example.com designates
74.125.130.27 as permitted sender) smtp.mailfrom=sender@example.com;
spf=pass confirms your record is working.
SPF for Subdomains
SPF does not apply recursively to subdomains. If you send email from marketing.example.com, that subdomain needs its own SPF record:
marketing.example.com. TXT "v=spf1 include:servers.mcsv.net ~all"
Domains that never send email should have a restrictive SPF record to prevent misuse:
noreply.example.com. TXT "v=spf1 -all"
SPF Limitations: Why It’s Not Enough Alone
SPF has two fundamental limitations:
-
It checks the envelope sender, not the From: header. The address users actually see (
From:) can be anything. SPF passes on the invisible envelope address. -
Email forwarding breaks SPF. When a server forwards email, it changes the envelope sender. The new sending IP isn’t in the original domain’s SPF, so SPF fails — even for legitimate mail.
Both limitations are why DKIM and DMARC exist. DKIM signs the message cryptographically (survives forwarding). DMARC ties both mechanisms to the From: header and specifies what to do when they fail.
Use all three: SPF + DKIM + DMARC.
Quick Reference
; Full example
example.com. TXT "v=spf1 ip4:203.0.113.0/24 include:_spf.google.com include:sendgrid.net -all"
; Subdomain that sends no email
static.example.com. TXT "v=spf1 -all"
; Check current record
dig TXT example.com +short
; Test SPF evaluation for a specific IP
python3 -c "import spf; print(spf.check2('203.0.113.42', 'user@example.com', 'example.com'))"
SPF is the 10-minute fix that blocks a significant class of spoofing. Set it up, verify it, then layer DKIM and DMARC on top.
Common Questions
What happens if my domain has no SPF record?
Without an SPF record, receiving mail servers have no way to verify whether your server is authorized to send email. Many servers treat messages from domains without SPF as higher-risk and route them to spam or reject them. It also means anyone can forge your From address and the SPF check will be neutral rather than failing — giving no protection against spoofing.
Can I have two SPF records?
No. RFC 7208 specifies that a domain must not have more than one SPF TXT record. Having two records causes an SPF PermError, which many receiving servers treat as a hard fail. If you need to add more sources, add them to your single existing record using the include: mechanism.
What is the SPF 10-lookup limit?
SPF evaluation can trigger at most 10 DNS lookups (include:, a:, mx:, ptr:, exists: each count). Exceeding 10 lookups causes a PermError. Modern email service providers often chain several includes together, making it easy to hit this limit. Use SPF flattening tools or ip4:/ip6: mechanisms instead of includes where possible.
What is the difference between ~all and -all?
~all (SoftFail) means: IPs not in the SPF list should be treated as suspicious but not outright rejected — receiving servers typically deliver to spam. -all (Fail) means: IPs not in the SPF list should be rejected. For domains with DMARC p=reject, the difference matters less since DMARC handles the enforcement. For domains without DMARC, -all provides stronger protection.
Does SPF protect the From: address users see?
SPF checks the envelope sender (Return-Path / MAIL FROM), not the From: header that users see. Attackers can pass SPF on the envelope address while forging the From: header. DMARC alignment is what ties SPF to the From: header — requiring the envelope sender domain to match the From: domain.