What Is an SPF Record? Syntax, Limits and Failure Modes
Summary
What is an SPF record? It is a single DNS TXT record, starting with v=spf1, that lists which servers may send mail using your domain in the envelope sender. Receivers compare the connecting IP against it and return pass, fail or softfail. It is capped at 10 DNS lookups, breaks on forwarding, and only helps DMARC when the Return-Path domain matches your From domain.
What is an SPF record? It is a single DNS TXT record on your domain that lists which servers are allowed to send mail using that domain in the envelope sender. A receiving server reads it, compares the connecting IP against the list, and returns pass, fail, softfail or neutral. It authenticates the sending path, not the message, and it says nothing about the From address a person reads.
That distinction explains most SPF tickets we see in traces.
An SPF record is an allowlist of senders, published in DNS
SPF stands for Sender Policy Framework, specified in RFC 7208. The record is a TXT entry at the root of the domain that starts with v=spf1. A minimal one looks like this:
example.com. IN TXT "v=spf1 ip4:203.0.113.10 include:_spf.provider.example ~all"Read it left to right. The receiver checks each term in order and stops at the first match. The final all term catches everything that did not match earlier.
The domain being checked is not the From header. It is the domain in the SMTP MAIL FROM command, also called the envelope sender or Return-Path. If that is empty, as on bounces, the receiver falls back to the HELO name.

The mechanisms: ip4, ip6, a, mx, include, exists
Each term is a mechanism, optionally prefixed by a qualifier. Six cover nearly every real record.
ip4:/ip6:match a literal address or CIDR range. They cost no lookups.amatches the A/AAAA records of a domain. It costs one lookup.mxmatches the hosts in the domain's MX records. It costs one lookup plus one per host.include:matches whatever another domain's SPF record matches. It costs one lookup, plus everything nested inside it.exists:matches any A record for a constructed name. It costs one lookup.ptrmatches on reverse DNS of the connecting IP. It costs lookups, and RFC 7208 says not to use it.
Qualifiers set the result when a term matches. + is pass and is the default, - is fail, ~ is softfail, ? is neutral. So -all means "reject anything not listed" and ~all means "accept but flag".
Prefer ip4: and ip6: whenever you control the address. They cost zero lookups and cannot break when someone else's DNS changes.
The 10-lookup limit is the constraint that breaks real records
RFC 7208 caps a single SPF evaluation at 10 terms that trigger DNS queries: include, a, mx, ptr, exists and the redirect modifier. Nested includes count. Past 10, the result is permerror, and the check fails permanently rather than falling back.
There is a second limit that gets less attention. Terms that return an empty answer or NXDOMAIN are void lookups, and the spec says to stop at two. A stale include: pointing at a vendor you dropped last year is a void lookup you are paying for.
Here is how teams actually hit the ceiling. Google Workspace takes one include. A transactional provider takes one. A marketing platform takes one, and it nests three more inside. A support desk, a CRM and a billing tool add four. You are at 11 without having done anything unusual.
The failure is silent. Mail still sends, and some receivers still deliver it, but SPF no longer authenticates anything for you. If you rely on DMARC alignment through SPF, that path is gone until you flatten the record or remove senders.
Three fixes work, in order of preference:
Remove senders you no longer use. Audit includes against your last 90 days of DMARC aggregate reports.
Move bulk traffic to a subdomain such as
mail.example.comwith its own SPF record and its own 10-lookup budget.Flatten includes into literal
ip4:ranges, but only with automation. Vendors rotate IPs, and a hand-flattened record goes stale without any alert.

Publish exactly one SPF record per domain
A domain must not publish two SPF records. If a receiver finds two v=spf1 TXT entries, the result is permerror, same as the lookup limit. This happens when one person adds a record for a new tool while another already has one in place.
The fix is mechanical. Merge the terms into a single record, with one v=spf1 at the start and one all at the end. Everything the vendor documentation tells you to add goes inside the existing record as another include:, never as a new TXT entry.
Two smaller traps. A TXT string over 255 characters must be split into multiple quoted strings inside the same record, which most DNS providers do for you. And keep the whole response under roughly 450 bytes where you can, since larger answers may fall back from UDP to TCP and some resolvers handle that badly.
Softfail or hardfail: pick based on what you can see
The all term is a policy statement, and the default advice ("use -all") skips the part that matters. A hard fail is only safe when you know every legitimate sender.
Our rule of thumb from reading traces:
~allwhile you are still inventorying senders, or if you forward mail and cannot control the path.-allonce DMARC reports show every legitimate source passing for a few weeks.Never
+all, which authorizes the entire internet, and never?allon a production domain.
In practice the difference is smaller than people expect. Most large receivers treat SPF as one signal among several, and with a DMARC policy in place the DMARC verdict is what decides. Still, a -all on an incomplete record is how you lose invoices.

SPF breaks on forwarding, and that is by design
SPF validates the IP that connected to the receiving server. When a message is forwarded, a mailing list redistributes it, or an alias service relays it, the connecting IP is the forwarder's, not yours. The SPF check fails even though you did nothing wrong.
You cannot fix this inside SPF. That is the reason DKIM exists alongside it: a signature survives the hop, an IP list does not. A domain that depends on SPF alone will see authentication failures on every forwarded message.
It is also why SPF alone does not satisfy DMARC for forwarded mail. Plan for DKIM to carry those messages.
SPF alignment: passing is not the same as matching your From domain
A message can pass SPF and still fail DMARC. DMARC needs the domain that passed SPF, the Return-Path domain, to match the visible From domain. Many shared providers use their own bounce domain by default, so SPF passes for provider-mail.net and says nothing about example.com.
Check this in the raw headers. In the Authentication-Results line, look at smtp.mailfrom=. If it is not your domain or a subdomain of it, SPF is passing without helping you.
The remedy is a custom Return-Path on your own subdomain, which most serious senders support. Postmark, Resend, Mailgun and SendGrid all document this setup, though it is not always on by default.
Inbox providers now expect authentication from every sender
Since February 2024, Google requires all senders to authenticate with SPF or DKIM, and senders of more than 5,000 messages a day to Gmail must have SPF, DKIM and DMARC in place. The details are in Google's email sender guidelines. Yahoo set matching rules at the same time.
This moved SPF from "nice to have" to a delivery prerequisite. A missing or broken record now shows up as rejections and spam placement, not just a lower reputation score.
Our read after a couple of years of traces: the rules mostly catch domains with no authentication at all and senders with high spam complaint rates. A correct SPF record by itself will not get you into the inbox, but a missing one will keep you out.
How to check and fix an SPF record in four steps
Do these in order, and re-check after each DNS change.
Look up the current record. Run
dig +short TXT example.comand find the entry startingv=spf1. If there are two, merge them.Count lookups. Add up
include,a,mx,exists,redirect, and everything nested in the includes. If you are at 8 or higher, plan the cleanup now, not at the 11th.List real senders. Use DMARC aggregate reports or Authentication-Results headers from test sends. Add only the sources that appear.
Send a test and read the header. The result should read
spf=passwithsmtp.mailfromon your domain.
DNS changes take time to propagate, and the TTL on your current record decides how long old answers survive. Lower it to 300 seconds a day before a planned change.
A worked record for a SaaS company with three senders
Take a product that sends from Google Workspace, a transactional provider and a support desk. A clean record looks like this:
example.com. IN TXT "v=spf1 include:_spf.google.com include:spf.transactional.example include:spf.helpdesk.example -all"That is three includes, and each one hides its own nested lookups. Count them before you publish, because the visible length tells you nothing about the real budget. A record with three includes can already sit at 7 or 8 lookups.
Now split by purpose. Keep human mail on the root domain. Put product notifications on notify.example.com with a record that only lists the transactional provider. The root record stays short, the subdomain gets its own 10-lookup budget, and a reputation problem on one stream no longer drags the other.
This split also makes DMARC reports readable. Each stream has its own Return-Path domain, so a failure points at one sender instead of a pile of them.
What SPF will not do
It will not stop someone spoofing your From address, because it never looks at that header. It will not encrypt anything. It will not tell you why a message landed in spam. And it will not scale past the 10-lookup budget without ongoing maintenance.
Treat it as one of three records you publish together: SPF for the sending path, DKIM for message integrity, DMARC for the policy that ties them to your From domain. If you have SPF in place and nothing else, the next concrete step is publishing a DMARC record at p=none and reading the reports for two weeks.