Skip to content
Guide

Writing an SPF record that says what you mean

An SPF record is one line of text, which makes it look simpler than it is. Evaluation order, a hard cap on DNS lookups and a choice of qualifier all change the answer a receiver reaches, and most broken records are broken in one of a handful of predictable ways.

Updated 5 August 2026 9 minute read

What an SPF record is

An SPF record is a single TXT record published at the domain itself — not at a subdomain, not at a dedicated label — whose value begins with v=spf1. It lists the hosts authorised to use that domain in the envelope sender, and it is evaluated by the receiving server against the IP address currently connected.

A domain may publish exactly one such record. Two TXT records beginning with v=spf1 on the same name is a permerror, and a permerror is not a mild condition: it means the receiver could not determine an answer, and many treat that as no better than a fail. RFC 7208 also retired the dedicated SPF resource record type, so a TXT record is the only correct place for one.

A complete SPF record: one address range of your own, one provider included, and a hard fail for everything else.
example.com. 3600 IN TXT "v=spf1 ip4:192.0.2.0/24 include:_spf.example.net -all"

How a record is evaluated

Mechanisms are checked strictly left to right, and the first one that matches decides the result. Everything after it is ignored, which is why the position of all matters more than anything else in the record: a mechanism written after all can never be reached.

An include mechanism is a nested evaluation, not a textual paste. The receiver evaluates the included domain's record in its own right; if that evaluation produces a pass, the include matches and the outer record's qualifier applies. A fail inside an include does not fail the outer record — evaluation simply moves to the next mechanism. That distinction surprises people who expect include to behave like a macro.

The mechanisms, and which ones cost a lookup

There are eight mechanisms and two modifiers. Six of them query DNS and are therefore rationed; the address literals are free, which is the entire reason SPF flattening exists.

  • ip4 and ip6 match the connecting address against a literal address or CIDR range. Neither costs a DNS lookup.
  • a matches if the named domain — or the current one, if none is named — has an address record equal to the connecting IP. One lookup.
  • mx matches if the connecting IP is an address of one of the domain's mail exchangers. One lookup, plus a cap of ten address lookups while resolving the MX targets.
  • include evaluates another domain's SPF record and matches on a pass. One lookup, plus every lookup that record makes.
  • exists matches if a given name resolves to any address record at all, which is how macro-driven per-user policies are built. One lookup.
  • ptr matches on the reverse DNS of the connecting IP. RFC 7208 deprecates it: it should not be published and should not be used, because it is slow, imposes load on unrelated nameservers and is easily influenced by whoever controls the reverse zone.
  • all matches everything and therefore terminates evaluation. It costs nothing and belongs last.
  • The modifiers are redirect, which replaces the whole record with another domain's and costs a lookup, and exp, which supplies explanatory text for a fail.

Qualifiers, and what each one asks a receiver to do

Every mechanism carries a qualifier. Omitting it means +, so a bare include:_spf.example.net is a pass condition. The qualifier is a request rather than an instruction — the receiver decides what to do with the result it produces, and under DMARC the SPF result matters mainly as an input to the alignment test.

  • + is pass: the host is authorised. This is the default when no qualifier is written.
  • - is fail: the host is not authorised, and the receiver is invited to reject the message.
  • ~ is softfail: the host is probably not authorised, but the domain is not confident enough to ask for rejection. Receivers usually accept and mark.
  • ? is neutral: the record makes no statement at all about this host, which is the same as publishing nothing for it.

The ten-lookup limit

An SPF evaluation may make at most ten DNS-querying mechanism lookups, counted across the whole recursion. The include, a, mx, ptr and exists mechanisms each count one, as does the redirect modifier; ip4, ip6 and all count nothing. Exceeding the limit is a permerror, and the result is the same as having no usable record — but with the added problem that it usually happens gradually and without warning, as a provider inside an include quietly adds another include of its own.

There is a second, less well-known cap on void lookups: queries that return NXDOMAIN or an empty answer. RFC 7208 sets that at two. A record referencing hostnames that no longer resolve can therefore fail long before it reaches ten lookups.

The count is over the whole tree, not the record you wrote. A record with four includes may be well past the limit if one of those providers uses five of its own.

Five lookups before the included records make any of their own, and three of them can grow without you touching your zone. A published record is a single string; this one is broken across lines to annotate it.
v=spf1 include:_spf.example.net     ; 1, plus every lookup that record makes
       include:spf.example.org      ; 2, plus every lookup that record makes
       include:mail.example.com     ; 3, plus every lookup that record makes
       mx                           ; 4
       a:relay.example.com          ; 5
       ip4:192.0.2.0/24             ; free
       -all                         ; free

Ending in -all or ~all

The final mechanism decides what a receiver is told about every host you did not list. A hard fail, -all, states that anything else is forged. A softfail, ~all, states that it is probably forged but stops short of asking for rejection.

While you are still discovering sending systems, ~all is the honest answer, because your record is not yet complete. Once DMARC aggregate reports show that every legitimate source is accounted for, -all is the stronger statement and the one worth arriving at. Under a DMARC policy of reject the practical difference narrows considerably, since an unaligned message will be rejected on the DMARC result whichever qualifier the SPF record ended with — but -all still protects the envelope identity for receivers that check SPF on its own.

Failure modes that are easy to miss

  • Two records. A second v=spf1 TXT record added by a provider's setup wizard produces a permerror for the whole domain.
  • Mechanisms after all. Everything to the right of all is dead text, and a provider appended to the end of an existing record lands there more often than not.
  • Silent growth past ten lookups. Nothing in your own DNS changed; a provider inside an include added one, and your record now permerrors.
  • A record split across TXT strings incorrectly. A single TXT string is capped at 255 octets, so long records are published as several strings that the resolver concatenates with no separator — a missing or duplicated space at a join changes the meaning.
  • Using ptr. Deprecated, slow, and treated with suspicion; replace it with the address ranges it was standing in for.
  • Authorising far more than intended. A wide ip4 range covering a shared hosting provider authorises every other customer on it as well.
  • Assuming SPF covers the visible address. It does not. Without DMARC alignment, an SPF pass says nothing about the header From.

SPF flattening, and what it costs

SPF flattening is the practice of resolving your includes down to the address ranges behind them and publishing those literals instead, so that the record makes no lookups at all and the ten-lookup limit stops being a constraint. It works, and for a domain with many providers it is sometimes the only way to stay inside the limit.

The cost is that you have taken a copy of somebody else's infrastructure. When a provider adds a sending range, their include reflects it the same day and your flattened list does not, so mail that should pass begins to fail. Flattening is therefore only defensible when it is automated, monitored and re-generated on a schedule. Reducing the number of providers that send as your domain is a better answer where it is available.

Check this on your own domain. The spf record checker reads the live records and reports what a receiver would see.

Open the spf record checker

Common questions

What happens when an SPF record exceeds ten DNS lookups?
The evaluation returns permerror, meaning the receiver could not reach a determination. Many receivers treat permerror as harshly as a fail, and a DMARC evaluation cannot count it as an SPF pass, so alignment must then come from DKIM. The limit counts include, a, mx, ptr, exists and redirect across the whole nested evaluation, not just your own record.
Should an SPF record end in -all or ~all?
Use ~all while you are still identifying sending systems, because your record is incomplete and a hard fail would be a false statement. Move to -all once DMARC aggregate reports show every legitimate source authorised. A hard fail is the stronger claim and the one to finish on, but only after the inventory is genuinely complete.
Can a domain publish more than one SPF record?
No. RFC 7208 permits exactly one TXT record beginning with v=spf1 per name, and a second one makes the result a permerror for every message. If two providers each want their own record, merge them into one by adding the second provider's include mechanism to the existing record instead.
Why does SPF fail when my mail is forwarded?
A forwarding server relays the message with the original envelope sender intact, but it connects from its own IP address, which your record has no reason to authorise. The result is a legitimate message with an SPF fail. This is why DKIM matters: its signature travels with the message and can still give DMARC an aligned pass.

Keep reading

Related checks, definitions and guides.