Decay University · Part 2: Authentication: proving you are you

Lesson 9 of 64

SPF explained: the list of who may send as your domain

What SPF does, a real record read token by token, every mechanism and qualifier, and the two quiet failure modes that void a record without warning.

Last updated 19 July 2026

SPF is a single TXT record in your DNS that lists every server allowed to send email using your domain. When a receiving mail server gets a message claiming to come from you, it fetches that record and checks whether the server that handed over the message is on the list. On the list, SPF passes. Off the list, your own record specifies how harshly the miss should be treated. That is the entire idea. The complications, and there are two big ones we'll get to, come from how the list is written and evaluated.

The problem it solves

SMTP, the protocol that moves email between servers (we walked through it in what happens when you hit send), was designed with no identity check at all. A sending server announces "this message is from billing@yourcompany.com" and the receiving server has no protocol-level reason to doubt it. Anyone with a rented server can claim to be anyone. That design decision is from the 1980s and we all still live with it.

SPF, the Sender Policy Framework, is the retrofit. You, the domain owner, publish the authoritative list of servers that legitimately send your mail. Receivers compare the connecting server against it. The standard is RFC 7208, and it is one of the more readable RFCs if you ever want the source.

A real record, read left to right

Here is a typical record for a domain that sends through Google Workspace and one self-hosted server:

v=spf1 include:_spf.google.com ip4:203.0.113.25 ~all

Token by token:

v=spf1 is the version marker. It must come first, and it is what makes this TXT record an SPF record rather than any other TXT record sitting on your domain. No v=spf1, no SPF.

include:_spf.google.com says "also consult the SPF record published at _spf.google.com, and if the connecting server would pass under that record, it passes under mine." This is delegation. You don't know Google's sending IPs and you don't want to; Google maintains its own list and you point at it. The name is slightly misleading: rather than copying the other record in, the receiver asks "would that record pass this server?" Nearly every email tool you sign up for will hand you an include to paste.

ip4:203.0.113.25 authorizes one literal IP address. If the connecting server is exactly this IP, pass. This is how you authorize a server you run yourself.

~all is the catch-all, and the tilde in front of it is a qualifier (next section). It means: any server not matched by anything earlier in the record gets a soft fail. Every mechanism is evaluated left to right, first match wins, and all matches everything, so it always goes last.

The mechanisms

A mechanism is a test the receiver runs against the connecting server. The ones you will actually meet:

  • ip4: and ip6: match a literal IPv4 or IPv6 address, or a range written like ip4:203.0.113.0/24. Cheap and exact.
  • include: delegates to another domain's SPF record, as above.
  • a matches if the connecting server's IP is one of the addresses your domain's A record points to. Useful when your web server also sends mail.
  • mx matches if the connecting server is one of your MX hosts, the servers that receive your mail. A leftover from an era when the same box usually did both jobs.
  • all matches everything. It exists to carry the final qualifier.

There are a few rarer ones (exists, ptr, the redirect modifier) that you can look up in RFC 7208 when you meet them in the wild. The ptr mechanism is explicitly discouraged by the RFC itself; if an agency left one in your record, that's a smell.

Qualifiers: how hard to fail

Any mechanism can be prefixed with one of four qualifiers, which set the result when that mechanism matches:

  • + means pass. It is the default, so include: and +include: are identical, and almost nobody writes the plus.
  • - means fail. Hard no.
  • ~ means softfail: "probably not legitimate, but don't reject outright." Treated as a mark against the message, weighed with everything else.
  • ? means neutral: "I make no statement." Nearly useless in practice.

In real records the qualifier decision collapses to one question: does your record end in ~all or -all? Fifteen years ago that choice mattered a lot. Under DMARC (two lessons from now) it matters less than folklore says, because DMARC only asks whether SPF produced a pass; softfail and fail are both simply "not a pass." What you must never do is end in +all, which authorizes the entire internet to send as you. Our health check flags +all as a critical finding, and yes, we still see it in the wild.

The domain it actually checks

Here is the part most people get wrong, and it changes everything downstream. SPF does not evaluate the From address your reader sees. It evaluates the domain in the envelope sender, the RFC5321.MailFrom, the address that appears in the Return-Path header of a delivered message. You met the difference between the envelope and the visible From in anatomy of an email; SPF is the reason that difference stops being trivia.

The consequence: when your newsletter tool sends with a bounce address on its own domain (bounce-123@mailer.example.net), SPF is evaluated against the tool's domain and the tool's record. It passes without you lifting a finger, because they wrote that record. Whether a pass on their domain does you any good is the alignment question, and it gets its own lesson. For now, hold onto this: an SPF pass tells you the envelope domain authorized the server. Nothing more.

One edge case worth a sentence now, because it resurfaces when you read DMARC reports later: some messages travel with an empty envelope sender (bounces do, by design), and for those, receivers run SPF against the domain the sending server announced in its opening SMTP greeting instead.

One record. Exactly one.

RFC 7208 requires exactly one SPF record per domain. Add a second and the standard's answer is a permanent error, called a permerror, which voids the record: receivers get no usable SPF result for your mail at all, and under DMARC that counts as not a pass. The record you had, the one that worked for years, stops counting.

This is not a rare misconfiguration. It is one of the most common failures our checkers catch. MailerLite's setup asks you to add their include to your SPF record, and the mistake we see is pasting it as a second v=spf1 record instead of merging it into the existing one. Both records look correct individually. Together they are worse than nothing. If a record you added won't validate, this guide walks the usual causes in order.

The fix is always a merge: one record, all your sources in it.

v=spf1 include:_spf.google.com include:mlsend.com ~all

The 10-lookup trap

The second silent killer. Evaluating an SPF record costs DNS lookups: every include, a, mx, and redirect counts against a hard budget of 10 for the whole evaluation, nested includes included; the rare ptr and exists count too. (ip4 and ip6 are free, which is one reason they're nice.) Cross the budget and the result is, again, permerror. Your record is syntactically perfect, every source in it is legitimate, and it might as well not exist.

Includes nest, which is what makes this treacherous. One vendor's include can pull in three more underneath it, and you can't see that by reading your own record. You add the CRM (the customer-database tool, which sends mail too), then the survey tool, then the transactional service, and each addition looks like one line. Somewhere around the fifth tool the budget quietly runs out.

Of all the ways a working setup rots, this one leaves no fingerprints: nobody edits the record and it still breaks, because a vendor restructured their own includes underneath yours. The record you verified in January can exceed the limit in June with zero changes on your side. The free health check counts the lookups on your record as it resolves today, which is the only honest way to know where you stand.

SPF authorizes servers, and that is the whole of what it does. The next lesson adds the second identity: a cryptographic signature that rides inside the message itself and survives the journey.

Terms from this lesson

  • SPF (Sender Policy Framework) - the DNS TXT record listing which servers may send email using your domain, defined in RFC 7208.
  • mechanism - one test inside an SPF record (ip4, include, a, mx, all), evaluated left to right, first match wins.
  • qualifier - the prefix on a mechanism (+, -, ~, ?) that sets the result when it matches: pass, fail, softfail, or neutral.
  • include - the mechanism that delegates to another domain's SPF record, used to authorize an email tool's servers without listing their IPs yourself.
  • softfail (~all) - the catch-all result meaning "probably not authorized, treat with suspicion but don't reject outright."
  • Return-Path (envelope sender) - the bounce address SPF is actually evaluated against, which is often not the From address your reader sees.
  • permerror - a permanent SPF error (two records, or too many lookups) that makes every SPF check on your mail fail.
  • 10-lookup limit - the hard budget of 10 DNS lookups per SPF evaluation; exceeding it voids the record with a permerror.

Check yourself

1. Your domain has a working SPF record and a colleague adds a second one for a new tool. What happens?

2. Which address does SPF evaluate?

3. A record ends in +all. What does that mean?

4. Your SPF record hasn't been edited in a year but now exceeds 10 DNS lookups because a vendor restructured their includes. What is the result?