SPF (Sender Policy Framework) provides two primary mechanisms for handling unauthorized emails:
-all
(Fail): Explicit rejection of non-compliant emails~all
(SoftFail): Treats non-compliant emails as suspicious but not definitively malicious
Here's how these mechanisms appear in DNS TXT records:
# Strict Fail implementation
"v=spf1 ip4:192.0.2.0/24 -all"
# SoftFail implementation
"v=spf1 ip4:192.0.2.0/24 ~all"
When implementing SPF policies, consider these technical factors:
Email Forwarding Challenges
SoftFail (~all
) often becomes necessary when dealing with email forwarding services. Here's a common scenario:
Original SPF: "v=spf1 include:_spf.example.com -all"
Forwarded email scenario: Fails SPF check when forwarded through third-party
Security vs. Deliverability Trade-off
The decision matrix for choosing between Fail and SoftFail:
Factor | Fail (-all) | SoftFail (~all) |
---|---|---|
Security | High (blocks unauthorized senders) | Medium (marks but delivers) |
False Positives | Higher risk | Lower risk |
Phishing Protection | Strong | Moderate |
Major providers demonstrate different approaches:
# Google's SPF (SoftFail)
"v=spf1 include:_spf.google.com ~all"
# Enterprise strict implementation example
"v=spf1 ip4:203.0.113.1 ip4:198.51.100.1/24 include:spf.protection.outlook.com -all"
For developers implementing SPF, consider this phased approach:
- Start with SoftFail and monitor rejections
- Analyze SPF failure reports using tools like:
# Sample SPF validation check
import dns.resolver
def check_spf(domain):
try:
answers = dns.resolver.resolve(domain, 'TXT')
for rdata in answers:
if 'v=spf1' in str(rdata):
return str(rdata)
except:
return "No SPF record found"
For systems handling high-value communications:
- Combine SPF Fail with DMARC reject policy for strongest protection
- Implement SPF macro tools for complex environments
- Consider using SPF failure reporting (RFC 6652)
Here's an example DMARC record that complements strict SPF:
"v=DMARC1; p=reject; rua=mailto:dmarc-reports@example.com"
The SPF protocol's Fail (-all)
and SoftFail (~all)
mechanisms represent fundamentally different security postures. Here's how major ESPs implement them:
# Gmail's SPF record (SoftFail) v=spf1 include:_spf.google.com ~all # Enterprise strict example (Hard Fail) v=spf1 ip4:192.0.2.0/24 -all
Large-scale providers show divergent approaches:
# Microsoft 365 (Mixed approach) v=spf1 include:spf.protection.outlook.com -all # Amazon SES (SoftFail default) v=spf1 include:amazonses.com ~all
When implementing SPF validation logic:
// Pseudo-code for SPF evaluation function evaluateSPF(email) { const spfRecord = fetchSPF(email.domain); if (spfRecord.includes('-all')) { return checkIP(strictMode); } else { return checkIP(lenientMode); } }
The forwarding problem explains why SoftFail persists:
# SPF breaks on forwarding (common scenario) Original Server → Forwarding Server → Recipient SPF breaks here
Metrics from enterprise deployments show:
- Hard Fail blocks 15-20% more phishing attempts
- SoftFail reduces false positives by 30-40%
For most organizations, a phased approach works best:
# Transitional SPF record v=spf1 include:_spf.yourprovider.com ~all # Final enforcement (after monitoring) v=spf1 include:_spf.yourprovider.com -all
Essential SPF monitoring tools:
# SPF validation check dig TXT example.com | grep spf # DMARC reporting tool python3 dmarc-parser.py < report.xml