Free tools

SPF record checker

Paste a domain and see its SPF record parsed: every mechanism, the policy for unlisted senders, every include and redirect walked, and the DNS lookup count against the limit of 10. Findings carry a severity, and a lookup that did not complete is said so, never hidden.

What is counted

Each include:, a, mx, ptr, exists: and redirect= term costs one lookup, and every included domain's record is walked the same way, so the count is the total a receiver would perform. ip4:, ip6: and all cost nothing. The checker reads TXT records through the system resolver; it does not evaluate a particular sender IP.

What an SPF record is, and where it lives

SPF, Sender Policy Framework, specified in RFC 7208, is one DNS TXT record listing the hosts allowed to send mail using your domain. A receiving mail server takes the domain out of the SMTP envelope sender (the MAIL FROM address, where bounces go, not the From: header a person reads), looks up that domain's SPF record, and evaluates it against the IP address that opened the connection. The outcome is one of seven results: pass, fail, softfail, neutral, none, permerror, temperror; it feeds the receiver's filtering and, where the domain publishes DMARC, its alignment decision.

The record goes at the domain being checked, as an ordinary TXT record whose value begins v=spf1. Two consequences catch people out. There is no inheritance: if mail.acme.com appears in an envelope sender, it needs its own record: acme.com's does not cover it. And the dedicated SPF resource-record type that early drafts defined is obsolete; §3.1 requires publication as TXT, so a domain that published only the SPF-type record has published nothing receivers will read.

Names like _spf.example.com are not an exception to that. They are simply the hostnames vendors publish their own records on so that you can point an include: at them; your record still lives at your domain.

TXT records also carry domain-ownership proofs; the glossary entry on TXT record verification explains how those work and why a SaaS product should require one.

The syntax, term by term

After the v=spf1 version tag, a record is a space-separated list of terms. Each term is either a mechanism, which can match the connecting IP, or a modifier, written name=value, which changes how the evaluation runs. Mechanisms are tried strictly left to right and the first one that matches ends the evaluation; its qualifier becomes the result. Nothing after that point is read, which is why a term placed after all is dead weight.

MechanismMatches whenDNS lookups
allAlways. It is the catch-all, so it is always the last term that can be reached: anything after it is dead.none
include:<domain>The included domain's own SPF record returns pass for this IP. Only pass counts: a fail inside an include does not fail the outer record, it simply does not match. An include target with no SPF record is a permerror.1, plus everything the included record itself costs
a / a:<domain> / a:<domain>/<cidr>The A or AAAA addresses of the domain (the current domain when no argument is given) contain the connecting IP, optionally widened by a CIDR prefix.1
mx / mx:<domain>The IP is an address of one of the domain's MX hosts: the machines that receive your mail are assumed to also send it.1 (§4.6.4 separately caps the MX names it will resolve at 10)
ip4:<addr>/<cidr>The IP falls inside the literal IPv4 address or range. No DNS is involved.none
ip6:<addr>/<cidr>The IP falls inside the literal IPv6 address or range.none
exists:<domain>The domain, usually built with macros such as %{i} so the name encodes the sending IP, has any A record at all. The answer is ignored; only existence matters. This is how per-IP allow-lists are published.1
ptr / ptr:<domain>The reverse lookup of the connecting IP produces a name under the domain, forward-confirmed. RFC 7208 §5.5 says it SHOULD NOT be published and SHOULD NOT be used; some receivers skip it outright.1, plus an address lookup per name returned

The two modifiers are position-independent, take no qualifier, and may appear at most once each:

ModifierEffectDNS lookups
redirect=<domain>When no mechanism matched, the whole evaluation is replaced by that domain's record and its result becomes yours. §6.1: it is ignored entirely if the record also has an all mechanism, which is the usual reason a redirect silently does nothing.1
exp=<domain>Names a TXT record holding the explanation string a receiver may include in its rejection message on fail. Cosmetic; it changes no result.none (§4.6.4 exempts exp from the limit)

A qualifier is the single character in front of a mechanism that decides what a match means (§4.6.2). Omitting it means +:

QualifierResultWhat it tells a receiver
+passThe sender is authorised. This is the default, so a bare mechanism such as include:_spf.example.com means +include:_spf.example.com.
-failThe sender is explicitly not authorised. Receivers are entitled to reject the message outright.
~softfailNot authorised, but do not reject on that alone; accept and mark. Intended as the in-between state while a domain is still discovering its own senders.
?neutralNo statement either way, treated exactly as if the domain had published no policy for that sender.

So v=spf1 include:_spf.example.net ip4:203.0.113.0/24 ~all reads: mail from a host the record at _spf.example.net authorises passes; mail from anything in 203.0.113.0/24 passes; everything else softfails.

The limit of ten, and how include: chains blow it

§4.6.4 caps the terms that require a DNS query at 10 for the whole evaluation: include, a, mx, ptr, exists and the redirect modifier. The count is recursive, and that is the part that surprises people: an include: whose target record itself contains three includes costs four, not one. Because each vendor's record is maintained by that vendor, your total can cross the limit on a day you changed nothing. We measured what each common vendor's include costs: from one lookup to seven.

Receivers return permerror for any sender whose check runs past the 10th lookup; which senders that hits depends on their order in the record. The same section caps "void" lookups, the ones answering with no data, at two.

There are four honest ways down, in the order worth trying:

  • Delete what no longer sends. Records accumulate ex-vendors; each one is a free lookup back.
  • Give a vendor its own subdomain. Send its mail as notifications.acme.com with its own SPF record. Each domain gets its own budget of 10, and this is the only fix that scales.
  • Replace an include with the ranges it resolves to. ip4:/ip6: terms cost nothing, but you have now pinned a vendor's infrastructure by hand, and mail breaks silently when they renumber. Only do this for a vendor that publishes stable ranges, and diary a re-check.
  • Drop ptr, and drop mx if your mail is inbound-only. Both are commonly present out of habit rather than need.

Automated "SPF flattening" services do the third option for you on a schedule. That removes the maintenance burden but adds a dependency whose failure mode is your mail quietly failing authentication, so treat it as infrastructure, not a fix-and-forget.

~all or -all, and why only one record may exist

-all tells receivers that a host you did not list is not authorised, full stop; they may reject the message. ~all says the same thing but asks them to accept and mark instead. The usual advice (publish ~all while you are still discovering which systems send as your domain, then move to -all) is about your confidence in the list, not about strictness for its own sake.

If you publish DMARC, the choice matters less than it looks: DMARC only counts an SPF pass toward alignment, so softfail and fail are the same non-pass to it and your DMARC policy decides the outcome. Where the difference still bites is the receivers that act on SPF alone. ?all and a record with no all at all are weaker still: both leave unmatched senders neutral, which is the position a domain with no SPF record is already in.

Whatever you choose, publish it exactly once. §3.2 forbids two v=spf1 records at the same name and §4.5 makes the check permerror when a receiver finds them; they do not merge the records or pick the stricter one, they abandon the check. It happens most often when a second provider's setup wizard adds its own TXT record rather than editing the one already there. Merging is just putting both include: terms in one record. Note that this is a different thing from length: DNS transmits a long TXT value as several chunks that are concatenated back together, so one record over 255 characters is legal, while two records never are.

What this checker reports, and what fixes it

Every finding the tool prints is one of the codes below, with a severity attached. They are the checker's complete vocabulary: if a record produces no finding beyond info, there was nothing here to report.

FindingWhat it meansHow to fix it
no_recordNo v=spf1 TXT record at the name, so receivers have no policy to evaluate and the result is none.Publish one TXT record at the domain itself, listing your senders and ending in ~all or -all.
multiple_recordsTwo or more v=spf1 records at the same name. §3.2 forbids it and §4.5 makes the check permerror: receivers do not pick one, they give up on the record entirely.Merge them into a single record by combining their terms, then delete the extras. This usually happens when a vendor's setup wizard adds its own record instead of editing yours.
unknown_termA term that is neither a defined mechanism nor a recognised modifier. An unparseable mechanism is a syntax error, and §4.6.1 makes a syntax error permerror. (An unrecognised modifier, by contrast, §6 says receivers must ignore, so this finding is about the term that will actually break the check.)Correct the typo, or delete the term. Watch for a stray comma, a semicolon, or a mechanism written with = instead of :.
terms_after_allTerms appear after all. Evaluation stops at the first match and all always matches, so those terms are never reached.Move the senders you meant to authorise in front of all, or delete them if they were left over from an older record.
all_pass+all, or a bare all, authorises every host on the internet to send as your domain.Replace it with ~all or -all. There is no legitimate reason to publish +all.
all_neutral?all makes no statement about unlisted senders, which is the same position as having no record for them.Move to ~all once you are reasonably confident you have listed your senders, then to -all.
no_allNo all mechanism and no redirect, so unmatched senders fall through to the implicit neutral of §4.7.End the record with ~all or -all so the policy is explicit.
ptr_mechanismptr is deprecated by §5.5: it is slow, it loads the reverse-DNS operators of every sending network, and receivers may ignore it.Delete it and authorise those senders with ip4:, ip6: or an include: instead.
lookup_limit_exceededMore than 10 DNS-querying terms across the whole recursive evaluation. Receivers return permerror for any sender whose check runs past the 10th lookup; which senders that hits depends on their order in the record.Drop includes for services that no longer send, replace one include with the ip4:/ip6: ranges it currently resolves to, or move a vendor onto its own subdomain with its own record.
redirect_ignoredA redirect= modifier alongside an all mechanism. §6.1 says the redirect is ignored, so the record does not do what it looks like it does.Delete the all mechanism if you meant the redirect to take effect, or delete the redirect if you meant the all.
include_target_missingAn include: or redirect= target publishes no SPF record. §5.2 makes a missing include target a permerror.Remove the include, or ask the vendor which hostname they actually publish their record on; wizard copy often names the wrong one.
record_too_longThe record is long enough that resolvers and receivers in the wild start truncating or refusing it.Shorten it the same way you shorten a lookup count: fewer includes, collapsed IP ranges, senders split onto subdomains.
uncheckedA DNS read the analysis needed did not complete, so the lookup count above is a lower bound and the record is not being called valid.Re-run the check. If it keeps happening, the included domain's nameservers are the thing to look at: receivers hit the same timeout, and a timeout on an include is a temperror for your mail.

How SPF, DKIM and DMARC fit together

SPF answers one narrow question (may this IP send for this envelope domain) and it has one well-known blind spot: forwarding. A forwarder re-sends your message from its own address, which your record does not list, so the check fails through no fault of the configuration.

DKIM covers that gap from the other direction. The sending system signs the message with a private key and publishes the public half at <selector>._domainkey.example.com; the signature travels with the mail and still verifies after a hop. DMARC, published at _dmarc.example.com, is the layer that makes either signal mean something to a recipient: it requires the visible From: domain to align with the domain that passed SPF or DKIM, states what receivers should do when neither aligns, and asks for aggregate reports so you can see who is sending as you before you enforce. Three records, three names, one purpose.

SPF records on a customer's domain

If your product sends mail on behalf of customers, each customer has to add your include: to a TXT record inside their own DNS, the same class of change, and the same stalled-onboarding problem, as pointing a hostname at your platform. DoDomain mints and verifies those records as part of a connect session; the multi-record and email guide covers what each connect path can do with an SPF, DKIM or DMARC record, including the one-include-per-session limit of the Domain Connect template. For the hostname half, the apex-domain guide is the one to read before promising a customer their bare domain will work.

Two checks here pair with this one: the DNS propagation checker reads a single record from the zone's authoritative nameservers and three public resolvers, which is how you tell "the TXT record is wrong" from "the TXT record is right and not yet cached", and the DNS provider detector says which host actually serves the zone you need to edit.

Frequently asked questions

What is the SPF ten-lookup limit?

RFC 7208 §4.6.4 caps the mechanisms that need a DNS query (include, a, mx, ptr, exists, and the redirect modifier) at 10 per evaluation, counted recursively through every include. A record over the limit returns permerror at receivers, which most treat like a failed check, so a domain can break its own mail by adding one more third-party include.

What is the difference between -all, ~all and ?all?

-all (hard fail) tells receivers to reject mail from senders the record does not list; ~all (soft fail) tells them to accept but mark it, which is what DMARC-aligned setups usually publish; ?all (neutral) makes no statement, so it gives DMARC nothing to align with; +all authorises every sender and defeats the purpose of the record.

Why does the checker say partially checked?

A DNS read the analysis depended on, the domain's own TXT, or an included domain's, did not complete. The tool then reports what it could read, prints the lookup count as a lower bound, and refuses to call the record valid, because a clean result from partial data would be a guess.

Does this fix my SPF record?

No; it tells you what is wrong and where. Common fixes: publish exactly one v=spf1 record, remove ptr, replace an include that itself includes many domains with the IP ranges it resolves to, and end with ~all or -all rather than ?all or nothing.

Where does an SPF record go, and do I need a subdomain like _spf?

At the domain itself, as an ordinary TXT record whose value starts with v=spf1. Names such as _spf.example.com are only the include targets vendors publish; they are not where your own record lives. SPF is also not inherited: mail.example.com does not use example.com's record, so a subdomain that sends mail needs its own.

Can I publish two SPF records if I use two email providers?

No. RFC 7208 §3.2 forbids more than one v=spf1 record at the same name and §4.5 makes the check permerror when one is found, so receivers do not choose between them: the whole check fails. Combine both providers into one record by putting both include: terms in it. A single TXT record longer than 255 characters is fine, because DNS splits it into chunks that are joined back together; two separate records are not.

Does SPF protect the From address my recipients see?

Not on its own. SPF authenticates the envelope sender used during the SMTP conversation, which recipients never see; a message can pass SPF while showing any From: header it likes. DMARC is the piece that requires the visible From: domain to align with the SPF (or DKIM) domain, and tells receivers what to do when it does not.

Why did my mail start failing SPF after it was forwarded?

Because forwarding re-sends the message from the forwarder's own IP address, which your record does not list. That is a known limit of SPF, not a misconfiguration, and it is the reason DKIM exists: a DKIM signature travels with the message and survives the hop, so a DMARC policy that accepts either signal keeps forwarded mail deliverable.

Start on the free plan

No card, no sales call: create an app, mint a session, and watch the first domain verify.

More free tools