Questions
How to check an SPF record
Retrieve a domain's SPF record with `dig example.com TXT +short` and look for the entry beginning `v=spf1`. Two things make it fail silently: more than one such record on the domain, which is a permanent error, and more than ten DNS lookups while evaluating it, which is also a permanent error. Both cause receiving servers to treat the domain as having no SPF at all, and neither produces any warning on your side.
Retrieve the record
`dig example.com TXT +short` returns every TXT record. The SPF one begins `v=spf1`.
`nslookup -type=TXT example.com` does the same where dig is unavailable.
If nothing begins `v=spf1`, the domain has no SPF record. If two entries do, that is the first fault and nothing else matters until it is fixed.
Read the mechanisms
`include:domain` delegates to another domain's record — one for each mail service you use.
`ip4:` and `ip6:` list addresses directly.
`a` and `mx` authorise the domain's own A or MX hosts.
The final mechanism is the policy: `-all` asks receivers to reject unlisted senders, `~all` to accept and mark, `?all` to do nothing.
Count the lookups — the failure nobody sees
Each `include:`, `a`, `mx`, `ptr` and `exists` costs one DNS lookup, and lookups inside an included record count too. The specification caps the total at ten.
Exceeding it is a permanent error: evaluation stops and the result is as if no record existed.
Four or five includes for common services is enough to reach the limit, and adding the fifth breaks the fourth without any notice.
The remedy is to replace includes with the explicit `ip4:` and `ip6:` ranges the provider publishes, which cost no lookup.
The two faults, in order of frequency
Two SPF records. Adding a service by pasting a second record rather than merging into the first. Invalid, and it fails permanently.
Too many lookups. Accumulated as services were added over years.
Both are silent. Your mail may still be delivered — receivers weigh many signals — but the authentication your DMARC policy depends on is not happening.
Merging two records into one
The fix for the first fault is mechanical, and it is worth stating precisely because doing it wrong produces a record that looks right and authorises the wrong senders.
Take the mechanisms from both records — every `include:`, `ip4:`, `ip6:`, `a` and `mx` — and put them in a single record, in one `v=spf1` entry, keeping each exactly as written.
Keep only one terminating `all` mechanism, at the end. Two records each ended with one; the merged record has one, and anything after it is ignored, which is how a merge silently drops half its mechanisms.
Choose the stricter of the two policies, not the looser. If one ended `-all` and the other `~all`, `~all` is the safe choice while you confirm the merged list is complete, and `-all` once it is.
Then delete the second record. Leaving it in place is the fault you were fixing.
Re-check with `dig example.com TXT +short` afterwards and confirm exactly one line begins `v=spf1`. DNS panels vary in how they handle multiple TXT records on one name, and some create a second rather than replacing.
Where the record has to live
SPF is published on the domain that appears in the envelope sender, which is not always the domain in the visible `From:` address and not always the domain you were thinking of.
Mail sent from `example.com` needs the record on `example.com`. Mail sent from `news.example.com` needs its own record on `news.example.com` — it does not inherit the parent's, and this is the most common reason a correctly published record appears to have no effect.
That inheritance gap is also the useful lever: giving a high-volume marketing platform its own subdomain gives it its own ten lookups and keeps the main record short.
A domain that sends no mail at all is worth publishing a record for too: `v=spf1 -all` states that nothing is authorised, which is the strongest thing an unused domain can say about itself.
How VeriFixScan helps
`email.spf.present`, `email.spf.single`, `email.spf.syntax`, `email.spf.all`, `email.spf.lookups` and `email.spf.ptr` read the record and evaluate exactly these conditions, including counting the lookups recursively — the part that is tedious by hand.
It is read alongside DKIM and DMARC, because SPF alone does not determine whether a message is accepted.
It reports what the domain declares. Whether a given message passed is visible only in the receiving server's headers or in DMARC aggregate reports.
Reading the result of a real message
The record tells you what the domain declares. Whether a particular message authenticated is a separate question, and the answer is in the headers of a message that arrived.
Send one to an account you control elsewhere, then open the original — "Show original" in Gmail, "View source" in most other clients. Near the top there is an `Authentication-Results` header written by the receiving server.
`spf=pass` with a `smtp.mailfrom` domain tells you SPF passed and for which domain. `spf=fail` or `spf=softfail` names the sending IP, which is usually enough to identify the service that is not in the record.
`spf=permerror` is the important one: it means the record could not be evaluated at all — two records, or the lookup limit exceeded — and it is the failure that produces no other symptom.
`spf=none` means no record was found for that domain, which on a domain you believe has one usually means the mail is being sent from a subdomain with no record of its own.
Compare the `smtp.mailfrom` domain with the `From:` address. When they differ, SPF can pass while DMARC does not, and that gap is alignment rather than an SPF fault.
One caveat: this tests the path the message actually took. A different service sending on your behalf takes a different path, so a single test confirms one sender and not the domain.
Frequently asked questions
- Can a domain have two SPF records?
- No. RFC 7208 requires exactly one TXT record beginning v=spf1. Two is a permanent error and evaluation fails.
- What happens if I exceed ten DNS lookups?
- Evaluation returns a permanent error and the message is treated as though no SPF record existed. Replacing include: entries with explicit ip4/ip6 ranges is the usual fix.
- Should the record end in -all or ~all?
- ~all while you are still discovering which services send on your behalf; -all once the list is complete. With DMARC in place, ~all plus a DMARC policy is a common and defensible combination.
Sources
Related
VeriFixScan crawls a site and applies its checks to every page it reaches, keeping the evidence behind each finding. Scanning one website is free.
Scan a website