SPF (Sender Policy Framework)

Understanding SPF and how it protects your domain from email spoofing.


What is SPF?

SPF (Sender Policy Framework) is an email authentication protocol that allows domain owners to specify which mail servers are authorized to send email on behalf of their domain. It's published as a DNS TXT record and helps prevent spammers from sending emails with forged "From" addresses from your domain.

The Problem SPF Solves

Without SPF, anyone can send an email claiming to be from your domain:

From: ceo@yourcompany.com  (Attacker's server)
To: employee@yourcompany.com
Subject: Urgent: Wire Transfer Needed

Please transfer $50,000 to this account immediately...

SPF allows you to publish a list of authorized mail servers, so receiving servers can verify that emails actually came from your infrastructure.


How SPF Works

sequenceDiagram
    participant Sender as Mail Server
    participant DNS as DNS Server
    participant Receiver as Receiving Server

    Sender->>Receiver: Email from user@example.com
    Note over Sender,Receiver: SMTP connection from IP 192.0.2.1

    Receiver->>DNS: Query SPF record for example.com
    DNS->>Receiver: v=spf1 ip4:192.0.2.0/24 -all

    Receiver->>Receiver: Check if 192.0.2.1 is authorized
    Note over Receiver: IP matches! SPF Pass

    Receiver->>Receiver: Accept email

The SPF Check Process

  1. Email arrives at receiving mail server
  2. Receiver extracts the domain from the MAIL FROM (envelope sender)
  3. DNS lookup is performed for the domain's SPF record
  4. IP address check - Receiver compares sending IP against authorized IPs/ranges
  5. Result returned - Pass, Fail, SoftFail, Neutral, or PermError

SPF Record Anatomy

Basic Structure

v=spf1 mechanism1 mechanism2 ... modifier ~all

Example SPF Record

example.com. IN TXT "v=spf1 ip4:192.0.2.0/24 include:_spf.google.com -all"

Let's break this down:

Component Description
v=spf1 Version identifier - Must always be first
ip4:192.0.2.0/24 IP mechanism - Authorizes this IP range
include:_spf.google.com Include mechanism - Includes Google's SPF record
-all All mechanism - Reject all other senders (hard fail)

SPF Mechanisms

Mechanisms specify which IP addresses are authorized to send mail.

1. IP Mechanisms

IPv4 Addresses

# Single IP
v=spf1 ip4:192.0.2.1 -all

# IP range (CIDR notation)
v=spf1 ip4:192.0.2.0/24 -all

# Multiple IPs
v=spf1 ip4:192.0.2.1 ip4:198.51.100.1 -all

IPv6 Addresses

# Single IPv6
v=spf1 ip6:2001:db8::1 -all

# IPv6 range
v=spf1 ip6:2001:db8::/32 -all

2. Include Mechanism

Include another domain's SPF record:

v=spf1 include:_spf.google.com -all

Common Provider Includes:

Google Workspace:

v=spf1 include:_spf.google.com -all

Microsoft 365:

v=spf1 include:spf.protection.outlook.com -all

Mailgun:

v=spf1 include:mailgun.org -all

SendGrid:

v=spf1 include:sendgrid.net -all

Amazon SES:

v=spf1 include:amazonses.com -all

3. A and MX Mechanisms

A Mechanism - Authorize the domain's A record IP:

v=spf1 a -all

MX Mechanism - Authorize IPs from MX records:

v=spf1 mx -all

4. Exists Mechanism

Advanced mechanism for complex lookups:

v=spf1 exists:%{i}._spf.example.com -all


SPF Qualifiers

Qualifiers determine what action to take when a mechanism matches:

Qualifier Symbol Action Example
Pass + (default) Accept the email +ip4:192.0.2.1 or ip4:192.0.2.1
Fail - Reject the email -all
SoftFail ~ Accept but mark ~all
Neutral ? No policy statement ?all

The "All" Mechanism

The all mechanism is the catch-all that matches everything not matched by previous mechanisms:

# Hard fail - Reject unauthorized senders (RECOMMENDED)
v=spf1 include:_spf.google.com -all

# Soft fail - Accept but mark as suspicious
v=spf1 include:_spf.google.com ~all

# Neutral - No opinion (provides minimal protection)
v=spf1 include:_spf.google.com ?all

# Pass all - DANGEROUS! Never use this
v=spf1 +all

Best Practice: Always use -all for maximum protection.


Common SPF Configurations

Scenario 1: Google Workspace Only

example.com. IN TXT "v=spf1 include:_spf.google.com -all"

Scenario 2: Microsoft 365 Only

example.com. IN TXT "v=spf1 include:spf.protection.outlook.com -all"

Scenario 3: Multiple Email Providers

example.com. IN TXT "v=spf1 include:_spf.google.com include:mailgun.org -all"

Scenario 4: Email Provider + Custom Servers

example.com. IN TXT "v=spf1 ip4:192.0.2.0/24 include:_spf.google.com -all"

Scenario 5: No Email Sent from Domain

example.com. IN TXT "v=spf1 -all"

This explicitly states that NO servers are authorized to send email from this domain.


SPF Macros (Advanced)

SPF supports macros for dynamic record construction:

Macro Expands to
%{s} Sender (MAIL FROM)
%{l} Local part of sender
%{o} Domain of sender
%{d} Current domain
%{i} Sending IP address
%{h} HELO/EHLO domain

Example:

v=spf1 exists:%{i}._spf.%{d} -all

This checks if a DNS record exists for <sending-ip>._spf.example.com.


The 10 DNS Lookup Limit

Critical Limitation: SPF records are limited to 10 DNS lookups to prevent abuse.

What Counts as a Lookup?

  • include: - 1 lookup
  • a - 1 lookup
  • mx - 1 lookup
  • exists: - 1 lookup
  • redirect= - 1 lookup

Does NOT count: - ip4: and ip6: - No lookup required - all - No lookup

Example: Counting Lookups

v=spf1 include:_spf.google.com include:mailgun.org include:sendgrid.net a mx -all

Lookup count: 1. include:_spf.google.com - 1 lookup (plus any in Google's record) 2. include:mailgun.org - 1 lookup (plus any in Mailgun's record) 3. include:sendgrid.net - 1 lookup (plus any in SendGrid's record) 4. a - 1 lookup 5. mx - 1 lookup

If Google's SPF includes 3 more lookups, Mailgun's includes 2, and SendGrid's includes 2, total = 5 + 3 + 2 + 2 = 12 lookups - This exceeds the limit!

Fixing the 10 Lookup Limit

Option 1: Use IP Addresses Instead of Includes

# Before (uses lookups)
v=spf1 include:provider1.com include:provider2.com -all

# After (no lookups)
v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.0/24 -all

Option 2: SPF Flattening

Use a service that "flattens" includes into IP ranges:

Option 3: Use Subdomains

Split sending across subdomains:

# Main domain
example.com. IN TXT "v=spf1 include:_spf.google.com -all"

# Marketing emails
marketing.example.com. IN TXT "v=spf1 include:sendgrid.net -all"

# Transactional emails
app.example.com. IN TXT "v=spf1 include:mailgun.org -all"


Common SPF Issues

Issue 1: Multiple SPF Records

Problem:

example.com. IN TXT "v=spf1 include:_spf.google.com -all"
example.com. IN TXT "v=spf1 include:mailgun.org -all"

Why it fails: RFC 7208 requires exactly ONE SPF record. Multiple records cause a PermError.

Fix: Combine into one record:

example.com. IN TXT "v=spf1 include:_spf.google.com include:mailgun.org -all"

Issue 2: SPF +all (Pass All)

Problem:

example.com. IN TXT "v=spf1 +all"

Why it's bad: This authorizes ANYONE to send email from your domain.

Fix:

example.com. IN TXT "v=spf1 include:_spf.google.com -all"

Issue 3: SPF ~all (SoftFail)

Problem:

example.com. IN TXT "v=spf1 include:_spf.google.com ~all"

Why it's weak: SoftFail (~all) suggests but doesn't enforce rejection. Many receivers ignore SoftFail.

Fix:

example.com. IN TXT "v=spf1 include:_spf.google.com -all"

Issue 4: Exceeding 10 DNS Lookups

Problem: SPF record triggers more than 10 DNS lookups

Symptoms: - Emails fail SPF with "PermError" - ReputeAPI reports SPF_TOO_MANY_LOOKUPS

Fix: See The 10 DNS Lookup Limit section above

Issue 5: Syntax Errors

Common mistakes:

# Missing v=spf1
"include:_spf.google.com -all"

# Typo in mechanism
"v=spf1 inclde:_spf.google.com -all"

# Wrong version
"v=spf2 include:_spf.google.com -all"

# Extra spaces
"v=spf1  include:_spf.google.com  -all"

Always validate with tools: - MXToolbox SPF Check - Google Admin Toolbox - ReputeAPI /api/v1/check endpoint


SPF Results

When a receiving server checks SPF, it returns one of these results:

Result Description Action
Pass Sender IP is authorized Accept email
Fail Sender IP is NOT authorized (hit -all) Reject or mark as spam
SoftFail Sender IP probably not authorized (~all) Accept but flag
Neutral No policy statement (?all) Accept
None No SPF record found Accept (no SPF protection)
TempError Temporary DNS error Retry later
PermError Permanent error (syntax, too many lookups) Treat as Fail

SPF Alignment (for DMARC)

For DMARC to pass, SPF must be aligned with the From header domain.

Relaxed Alignment (Default)

The organizational domain must match:

MAIL FROM: bounce@mail.example.com
From: user@example.com
✓ SPF Aligned (example.com matches)

Strict Alignment

The exact domain must match:

MAIL FROM: bounce@mail.example.com
From: user@example.com
✗ NOT aligned (mail.example.com ≠ example.com)

DMARC Alignment Setting:

# Relaxed (default)
v=DMARC1; p=reject; aspf=r

# Strict
v=DMARC1; p=reject; aspf=s


Testing Your SPF Record

Using ReputeAPI

curl -X GET "https://api.reputeapi.com/api/v1/check?domain=example.com" \
  -H "X-API-Key: your-api-key"

Response includes SPF validation:

{
  "spf": {
    "present": true,
    "valid": true,
    "record": "v=spf1 include:_spf.google.com -all",
    "mechanisms": ["include:_spf.google.com", "-all"],
    "warnings": []
  },
  "issues": []
}

Using Command Line

Check SPF record:

dig example.com TXT +short | grep "v=spf1"

Check from specific IP:

# Using Python's pyspf
python3 -m pyspf.check -i 192.0.2.1 -s user@example.com -h mail.example.com

# Using spfquery
spfquery -ip 192.0.2.1 -sender user@example.com -helo mail.example.com

Online Tools


SPF Best Practices

1. Start with Hard Fail (-all)

v=spf1 include:_spf.google.com -all

Use -all not ~all. SoftFail provides minimal protection.

2. Keep It Simple

Only include servers that actually send mail:

# Good - Only necessary includes
v=spf1 include:_spf.google.com -all

# Bad - Too many includes
v=spf1 include:provider1 include:provider2 include:provider3 include:provider4 -all

3. Monitor DNS Lookup Count

Stay under 10 lookups. Use ReputeAPI to check:

curl "https://api.reputeapi.com/api/v1/check?domain=example.com" \
  -H "X-API-Key: your-api-key" | jq '.spf'

4. Use IP Ranges When Possible

# Instead of multiple includes
v=spf1 ip4:192.0.2.0/24 ip4:198.51.100.0/24 -all

5. Document Your SPF Record

Keep a comment with your DNS records:

; SPF for example.com
; Authorizes Google Workspace and internal mail server
example.com. IN TXT "v=spf1 ip4:192.0.2.100 include:_spf.google.com -all"

6. Test Before Deploying

  1. Create with ~all first (SoftFail for testing)
  2. Monitor for 1-2 weeks to catch legitimate servers
  3. Switch to -all once confident

7. Regular Audits

Review SPF records quarterly: - Remove unused includes - Update IP ranges - Check for lookup count - Verify all authorized senders


SPF for Subdomains

Explicit Subdomain Records

# Main domain
example.com. IN TXT "v=spf1 include:_spf.google.com -all"

# Subdomain with different SPF
mail.example.com. IN TXT "v=spf1 ip4:192.0.2.100 -all"

Subdomain Inheritance

Without explicit SPF record, subdomains do NOT inherit the parent's SPF. Each subdomain needs its own record.

Protecting unused subdomains:

# For domains that don't send email
*.example.com. IN TXT "v=spf1 -all"


SPF and DMARC

SPF alone doesn't provide full protection. It must work with DMARC:

# SPF Record
example.com. IN TXT "v=spf1 include:_spf.google.com -all"

# DMARC Record (enforces SPF)
_dmarc.example.com. IN TXT "v=DMARC1; p=reject; aspf=r; rua=mailto:dmarc@example.com"

Why both are needed: - SPF validates the envelope sender (MAIL FROM) - DMARC validates the header From address and enforces policy - Together they prevent email spoofing

Learn more: Email Authentication Overview


Migrating Email Providers

When changing email providers, update SPF carefully:

Step 1: Add New Provider

# Add new provider alongside old
v=spf1 include:_spf.google.com include:_spf.newprovider.com -all

Step 2: Test New Provider

Send test emails and verify SPF passes:

curl "https://api.reputeapi.com/api/v1/check?domain=example.com&refresh=true" \
  -H "X-API-Key: your-api-key"

Step 3: Remove Old Provider

# After migration complete
v=spf1 include:_spf.newprovider.com -all

Wait at least 48 hours between changes to allow DNS propagation.


SPF Validation with ReputeAPI

Check Domain's SPF

import requests

response = requests.get(
    "https://api.reputeapi.com/api/v1/check",
    params={"domain": "example.com"},
    headers={"X-API-Key": "your-api-key"}
)

result = response.json()

if not result['spf']['present']:
    print("❌ No SPF record found")
elif result['spf']['valid']:
    print(f"✅ SPF valid: {result['spf']['record']}")
else:
    print(f"⚠️ SPF has issues")

# Check for SPF-related issues
spf_issues = [i for i in result['issues'] if i['category'] == 'spf']
for issue in spf_issues:
    print(f"  [{issue['severity']}] {issue['message']}")
    print(f"  Fix: {issue['remediation']}")

Get SPF Recommendations

response = requests.post(
    "https://api.reputeapi.com/api/v1/recommendations",
    json={"domain": "example.com"},
    headers={"X-API-Key": "your-api-key"}
)

recommendations = response.json()
for rec in recommendations['recommendations']:
    if rec['category'] == 'spf':
        print(f"Priority: {rec['priority']}")
        print(f"Action: {rec['action']}")
        print(f"DNS Snippet: {rec['dns_snippet']['value']}")

Common Questions

Do I need SPF if I have DMARC?

Yes! DMARC relies on SPF (and/or DKIM) passing. Without SPF, DMARC cannot validate your emails.

Can I have multiple SPF records?

No. RFC 7208 requires exactly ONE SPF record per domain. Multiple records cause a PermError.

What if I don't send email from my domain?

Use a null SPF record:

example.com. IN TXT "v=spf1 -all"

This explicitly states no servers are authorized.

Should I use ~all or -all?

Always use -all for maximum protection. SoftFail (~all) is too lenient and often ignored.

How long does DNS propagation take?

Typically 15 minutes to 48 hours. Most DNS servers respect TTL values, so:

# Set low TTL before changes
example.com. 300 IN TXT "v=spf1 include:_spf.google.com -all"

Can SPF protect against all phishing?

No. SPF only validates the envelope sender. It doesn't protect against: - Display name spoofing - Look-alike domains - Compromised accounts

Use SPF + DKIM + DMARC together for comprehensive protection.



API Resources