What this checks
SPF (Sender Policy Framework) is a TXT record on your domain that lists the servers allowed to send email for it. When a message arrives, the receiving server looks up that record and compares the sending server's IP address with the list. A match is a "pass"; no match is a "fail" or a "softfail", depending on how the record ends.
This checker fetches the SPF record of the domain you type, as public DNS resolvers (1.1.1.1 and 8.8.8.8) see it right now. It then reads every term in the record, follows every include: and redirect= to the records they point at, and counts how many DNS lookups the whole chain needs. The limit set by the SPF standard (RFC 7208) is 10, and going over it is the most common reason a record that looks fine still fails.
The result is provider-neutral. A domain that sends through Google Workspace, Microsoft 365, Amazon SES, Ackuna or anything else is judged by the same rules: one record, valid syntax, 10 lookups or fewer, and a sensible ending.
How to read the result
The status at the top is the short version. Pass means the record is valid and nothing needs attention. Warning means mail works today, but something should be fixed. Fail means receivers already treat the record as broken. Missing means the domain has no SPF record at all. Could not check means our resolvers did not get an answer; that says nothing about your record, so try again in a minute.
Below the status you see the record exactly as published, the lookup count as "used / 10", and a tree of the includes with the lookups each one costs. Each finding links to its explanation here.
No SPF record found
The domain publishes no TXT record that starts with v=spf1. Receivers cannot tell which servers may send for it, so your mail gets no SPF credit and anyone can send mail that claims to be from your domain. A TXT record that mentions SPF but does not start with exactly v=spf1 (for example spf2.0/pra or v=spf 1) does not count: receivers ignore it.
The domain does not exist
Public DNS has no entry for this name at all. Either it is not registered, it is not delegated yet, or you typed a subdomain that was never created. Nothing can be published on a name that does not exist.
More than one SPF record
Two or more TXT records start with v=spf1. The standard allows exactly one; with several, receivers return a permanent error and may reject the mail. This usually happens when a second email service adds its own record instead of being merged into the existing one. Keep one record and put every include: in it.
Too many DNS lookups
The record and everything it includes need more than 10 DNS lookups. The mechanisms that cost a lookup are include, a, mx, ptr, exists and the redirect= modifier; ip4, ip6 and all are free. When the count passes 10, receivers stop evaluating and return a permanent error, so the whole record fails even though every individual entry is correct. Remove services you no longer send through, replace an include: of a small provider with its ip4: ranges, or move rarely used senders to a subdomain with its own record.
At the lookup limit
The record uses exactly 10 lookups, which is allowed, so the check passes. But there is no room left: adding one more include, a, mx, exists or redirect= pushes the record over the limit and every receiver starts failing it. Before you add another sending service, free a lookup by replacing an include: with that provider's ip4:/ip6: ranges or by removing a sender you no longer use.
Too many empty lookups
An include: or redirect= that points at a name with no SPF record is a "void lookup". The standard allows at most two per check. More than that is a permanent error. These are usually leftovers from a service you stopped using.
Include target has no SPF record
One of the includes points at a domain that has no SPF record, or does not exist. It adds nothing to your policy, still costs a lookup, and counts towards the void-lookup limit. Remove it, or fix the target if it is your own domain.
Include chain loops
An include points back at a domain that is already in the chain, so the chain never ends. Receivers stop with a permanent error.
Redirect loops
A redirect= modifier sends receivers to another domain's record instead of this one. When that record redirects back, the chain never ends and the check fails permanently. A redirect is also ignored when the record already has an all mechanism, which the checker points out.
The record ends in +all
+all tells receivers that every server on the internet is allowed to send mail for your domain. It makes SPF useless and some receivers treat it as a spam signal. Replace it with ~all or -all.
The record has no "all"
Without a final all or a redirect=, the record ends in an implicit neutral result. Unlisted servers are neither allowed nor refused, which is the same as having no policy. Finish the record with ~all while you are still adding senders, then with -all.
The record ends in ?all
?all is neutral: receivers treat unlisted servers exactly as if there were no record. It is only useful for a short test. Use ~all or -all.
Softfail (~all)
~all means "unlisted servers are probably not mine, but do not reject outright". Receivers mark such mail and let DMARC decide. It is a fine choice while you add senders; once every service you send through is in the record, -all is the stronger ending.
The ptr mechanism
ptr is deprecated. It makes receivers do slow reverse lookups and some ignore it entirely. Replace it with ip4:, ip6: or a: entries.
Syntax error
Something in the record is not valid SPF: a mechanism that does not exist, a malformed IP address, a network length that is out of range, a modifier used twice. The message says which term. Receivers treat a syntax error as a permanent error, so fix it first.
Record too long
A DNS answer larger than about 450 bytes may not fit in a single UDP packet, and a few receivers fail the lookup instead of retrying over TCP. Shorten the record by removing unused senders; very long records are usually a lookup-count problem too.
An include could not be fetched
Our resolvers did not get an answer for one of the included domains, so its lookups are not in the count. Re-check in a few minutes before you change anything.
Macro targets
An include: or exists: can contain macros like %{i} that are expanded per message. The checker counts the lookup but cannot follow it, because the target depends on who is sending.
How to fix it
- Make a list of everything that sends email as your domain: your mail provider, your newsletter tool, your transactional service, your CRM, your help desk, your billing system.
- Find each service's SPF entry in its documentation. It is usually one
include:line. - Write one record:
v=spf1followed by the includes, then~allor-all. Putip4:andip6:entries before includes; they cost nothing. - Count the lookups with this checker before you publish. If you are over 10, replace small providers' includes with their IP ranges, or drop the services you no longer use.
- Publish the record as a TXT record on the apex of your domain (the host is usually
@). Replace the old one; do not add a second. - Wait for DNS to propagate, then re-check. Resolvers cache the old answer for as long as the record's TTL, often an hour.
If you send with Ackuna: add your domain in the console and Ackuna gives you the exact SPF, DKIM and DMARC records to publish, then verifies them for you. SPF is a single include:amazonses.com, which costs one lookup, and one verified domain covers both newsletters and transactional mail through the SMTP relay.
