SPF

SPF (Sender Policy Framework) is a DNS record that lists which servers are allowed to send email from your domain. Without it, anyone on the internet can send mail claiming to be from you. With it, receivers have something to check against, and a strict policy tells them to reject the forgeries. The protocol is specified in RFC 7208.

How receivers validate

When a mail server receives a message claiming to be from you@yourdomain.com:

  1. It queries your domain's TXT records.
  2. It finds the record starting with v=spf1.
  3. It compares the sending server's IP against every mechanism in the record.
  4. The closing mechanism (-all, ~all, +all) decides what to do when nothing matches.

If the sending IP is not authorised and the policy is strict, the receiver rejects the message before delivery.

Anatomy of a record

A typical SPF record:

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

Four parts work together:

  • v=spf1: the version tag. Always the opening. Only one SPF record per domain.
  • ip4:203.0.113.10: a literal IP. The address of an SMTP server you operate.
  • include:_spf.google.com: a delegation. Pulls in Google Workspace's SPF fragment. Providers each maintain their own.
  • -all: the closing rule. Tells receivers what to do when none of the mechanisms above matched.

The closing mechanism

This tag decides whether your SPF actually protects you:

TokenMeaningForged mail outcome
-allStrict failRejected at the SMTP handshake
~allSoft failRouted to spam or quarantined
?allNeutralDelivered. Result logged only.
+allAllow everythingDelivered. SPF is functionally absent.

Sudory marks -all as pass. Everything else is a warn. No record at all is a fail.

The 10-lookup ceiling

SPF allows a maximum of 10 DNS lookups per evaluation. Per RFC 7208 §4.6.4, the terms that consume a lookup are the include, a, mx, ptr, and exists mechanisms, and the redirect modifier. Includes are recursive: a provider's SPF may chain to others, and those count too.

Exceed the limit and the record returns permerror, which most receivers treat as a fail. The same section also imposes a separate limit of two void lookups (queries that return NXDOMAIN or an empty answer); past that, permerror again.

If you're close to the ceiling, consolidate with a flattening service, drop senders that aren't actually sending, or switch to ip4: literals for providers that publish stable IP ranges.

Building your record

1. Collect the includes

Every service that sends on your behalf publishes an SPF fragment. The provider name links to each vendor's SPF setup guide:

ProviderInclude string
Google Workspaceinclude:_spf.google.com
Microsoft 365include:spf.protection.outlook.com
SendGridinclude:sendgrid.net
Postmarkinclude:spf.mtasv.net
Mailguninclude:mailgun.org

Heads-up on lookup counts: include:mailgun.org alone consumes three lookups because of nested includes, and include:spf.protection.outlook.com consumes two. Two transactional vendors plus Workspace can put you past six before you add your own ip4: literals.

2. Compose one record, end with -all

v=spf1 include:_spf.google.com include:sendgrid.net ip4:203.0.113.10 -all

One line, one TXT record. If you already have an SPF record, merge instead of duplicating. Two records starting with v=spf1 is a permerror.

3. Publish at the root

FieldValue
TypeTXT
Host@ (or yourdomain.com)
Valuev=spf1 include:... -all
TTL3600

Common mistakes

  • Ending with +all. Authorises every server on the internet. Almost always a copy-paste accident.
  • Two SPF records. Both starting with v=spf1. Receivers return permerror (RFC 7208 §3.2). Merge into one.
  • Forgetting subdomains. SPF doesn't inherit. Any subdomain that sends mail needs its own record.
  • Staying on ~all forever. "We'll tighten later" rarely happens. Only -all gets forgeries rejected.

Want to check your current setup? Scan your domain. Sudory resolves your TXT records, follows redirect= up to three levels, and tells you exactly which bucket you're in.