# How to validate SPF record syntax

> Validate SPF syntax by checking the DNS TXT record's v=spf1 version tag, mechanism grammar, and the 10 DNS-lookup limit defined in RFC 7208.

An SPF record's syntax is valid when it is published as a DNS TXT record that begins with the exact version tag `v=spf1`, uses only the mechanisms and modifiers defined in RFC 7208, and stays inside the protocol's DNS lookup limits. A single misplaced qualifier, an unrecognized mechanism name, or a lookup count over 10 makes the whole record return a permanent error, or permerror, instead of a partial result. Validating syntax means checking the published record against this grammar before checking whether SPF is passing on real mail.

## Quick takeaways

- SPF records must be published as DNS TXT (type 16) records only. The dedicated SPF resource record (type 99) was dropped from the protocol, so receivers do not need to query it.
- A valid record must start with the exact version string `v=spf1`, ending at a space or the end of the record. A near match such as `v=spf10` is not a valid SPF record.
- The grammar defines exactly eight mechanisms: `all`, `include`, `a`, `mx`, `ptr`, `ip4`, `ip6`, and `exists`. A token outside that list breaks the grammar.
- Any single syntax error anywhere in the record makes evaluation return permerror for the whole record, with no partial credit for the parts that were written correctly.
- Terms that trigger a DNS query, `include`, `a`, `mx`, `ptr`, `exists`, and `redirect`, are capped at 10 total per evaluation. Exceeding that cap also returns permerror.
- A domain must not publish more than one SPF record that an authorization check would select. Zero matching records returns "none"; more than one returns permerror.

## How SPF syntax validation works

[RFC 7208 defines SPF](https://www.rfc-editor.org/rfc/rfc7208) as a DNS-published policy that a receiving mail system evaluates through a function called `check_host()`. Validating syntax means confirming that a published record parses correctly under that function's grammar, before asking whether any particular message passes.

The record must exist as a TXT record. [RFC 7208 Section 14.1](https://www.rfc-editor.org/rfc/rfc7208#section-14.1) notes that a dedicated SPF resource record type (type 99) was defined early in the protocol's life but dropped, because migration to it was judged very unlikely. Publishing SPF as anything other than a TXT record is not part of the current specification.

Inside that TXT string, the record must open with a version section of exactly `v=spf1`. [RFC 7208 Section 4.5](https://www.rfc-editor.org/rfc/rfc7208#section-4.5) treats the version section as ending at the first space or at the end of the record, so a string that only starts with those characters but continues differently, such as `v=spf10`, is not recognized as SPF.

After the version tag, the grammar is closed. [RFC 7208 Section 4.6.1](https://www.rfc-editor.org/rfc/rfc7208#section-4.6.1) defines a record as a version followed by terms, where each term is either a directive (an optional qualifier plus one of the eight defined mechanisms) or a modifier (`redirect`, the explanation modifier `exp`, or an unrecognized `name=value` pair). Terms are separated by one or more spaces. The qualifier before a mechanism is one of `+`, `-`, `?`, or `~`, and defaults to `+` when omitted. The [SPF hub](/learning/spf) covers how these mechanisms fit into a full record; this article stops at whether the grammar itself is well formed. For a term-by-term breakdown of mechanism and qualifier meaning, see [SPF record syntax explained](/learning/spf-record-syntax-explained-mechanisms-qualifiers).

An unrecognized modifier is not automatically an error. [RFC 7208 Section 6](https://www.rfc-editor.org/rfc/rfc7208#section-6) states that unrecognized modifiers must be ignored no matter where or how often they appear. There is no equivalent tolerance for mechanisms in the grammar: a token that does not match one of the eight defined mechanism names, and does not match the modifier grammar either, falls outside every production in the record syntax. That gap is what the Section 4.6 evaluation rule turns into a permerror; it is not a separately quoted forgiveness clause the way unrecognized modifiers get.

## When SPF syntax validation fails

Treat a record as failing validation, and stop before checking anything downstream, when any one of these conditions is true:

- **A syntax error exists anywhere in the record.** [RFC 7208 Section 4.6](https://www.rfc-editor.org/rfc/rfc7208#section-4.6) states that `check_host()` returns permerror immediately on a syntax error, "without further interpretation or evaluation." A record with nine correct terms and one broken term is still a broken record.
- **More than one record is published.** [RFC 7208 Section 3.2](https://www.rfc-editor.org/rfc/rfc7208#section-3.2) requires that a domain not publish multiple records that an authorization check would select. Zero matching records produces the result "none"; more than one produces permerror.
- **DNS-querying terms exceed 10.** [RFC 7208 Section 4.6.4](https://www.rfc-editor.org/rfc/rfc7208#section-4.6.4) limits `include`, `a`, `mx`, `ptr`, `exists`, and `redirect` to 10 total per evaluation. `ip4`, `ip6`, and `all` do not count toward that limit, because they resolve without a DNS query. Each `mx` or `ptr` term is also separately capped at 10 address-record lookups.
- **Void lookups exceed the recommended limit.** The same section states that implementations should limit lookups that return no answer, a "void lookup," to two before returning permerror.
- **A TXT character-string exceeds its bounds.** [RFC 7208 Section 3.3](https://www.rfc-editor.org/rfc/rfc7208#section-3.3) caps a single TXT character-string at 255 octets. Longer records are split across multiple strings, which receivers concatenate without inserting spaces. Record content is US-ASCII.

> A permerror is not a warning. [RFC 7208 Section 8.7](https://www.rfc-editor.org/rfc/rfc7208#section-8.7) describes permerror as meaning the published records "could not be correctly interpreted" and states the condition "definitely requires DNS operator intervention to be resolved." A receiver rejecting on permerror at SMTP time is expected to use reply code 550 with enhanced status code 5.5.2, so a broken record can turn into hard rejections at enforcement, not just a missed pass.

## A worked SPF record example

The record below is illustrative only. Do not publish it as written; a real record must list the domain's actual sending sources, not a documentation example.

```text
v=spf1 include:spf.provider.example ip4:203.0.113.0/24 -all
```

Reading it against the grammar:

- `v=spf1` is the mandatory version tag, present exactly once at the start.
- `include:spf.provider.example` is a directive using the `include` mechanism. No qualifier is written before it, so it defaults to `+` (pass). `include` is a DNS-querying term and counts toward the 10-lookup limit.
- `ip4:203.0.113.0/24` is a directive using the `ip4` mechanism with the reserved documentation range from RFC 5737. `ip4` does not require a DNS query, so it does not count toward the lookup limit.
- `-all` is a directive using the `all` mechanism with the `-` qualifier, requesting fail for anything that reaches this term without matching an earlier one. `all` also does not query DNS.

A record built from only these four terms uses one of the ten available DNS-querying lookups. A domain that chains several `include` mechanisms, each of which may itself contain further `include` or `redirect` terms, can reach the limit quickly, which is why the lookup count matters as much as the individual mechanism syntax.

![SPF record mechanisms and DNS lookup costs](/images/editorial/validate-spf-syntax/validate-spf-syntax-terms.webp "1200x1078")

*Source: Palisade.*

## What to check next

Work in this order once a record is in hand:

- Query the domain's TXT records at the authoritative name server and at a second public resolver, and confirm exactly one record begins with `v=spf1`.
- Read that record against the grammar above: version tag, only the eight defined mechanisms, valid qualifiers, and modifiers that are either `redirect`, `exp`, or an ignorable unrecognized `name=value` pair.
- Count every `include`, `a`, `mx`, `ptr`, `exists`, and `redirect` term, including any reached through a chain of includes, and confirm the total is 10 or fewer.
- If the record fails any of these checks, correct the DNS record directly. A permerror is a DNS-operator problem under RFC 7208, not something a receiver or sender application can work around.

SPF syntax is one input into the broader email authentication picture alongside DKIM and DMARC alignment; the [email authentication overview](/learning/email-authentication) covers how the three fit together, and [what SPF is](/learning/what-is-spf) covers the mechanism at a higher level than the syntax rules here.

## Check SPF syntax before troubleshooting delivery further

Run the domain through [Palisade's SPF Record Checker](/tools/spf) to look up the published record, validate its syntax, and count its DNS lookups without registering an account. Do this after reading the record manually so a tool result confirms, rather than replaces, what the grammar rules above already show.

[Check SPF](/tools/spf)

A syntax check confirms the published record parses correctly and counts its DNS lookups. It does not confirm that every legitimate sending source is included in the record, and it does not show whether a specific delivered message actually passed SPF; that requires the message's own authentication result.

## Sources and further reading

- [RFC 7208: Sender Policy Framework (SPF) for Authorizing Use of Domains in Email](https://www.rfc-editor.org/rfc/rfc7208)
- [Palisade SPF Record Checker](https://www.palisade.email/tools/spf)

## Frequently asked questions

### Does a valid SPF syntax check prove that mail from the domain will pass SPF?

No. A syntax check confirms that the published record parses correctly under the RFC 7208 grammar. Whether a specific message passes SPF also depends on the sending IP address matching an authorized mechanism and the identifier used for the check, which a syntax check alone does not evaluate.

### Can a domain publish more than one SPF record?

No. RFC 7208 requires that a domain not publish multiple records that an authorization check would select. Zero matching records returns the result "none," and more than one matching record returns permerror, the same outcome as a record with a syntax error.

### What happens after an SPF record returns permerror?

A permerror means the published record could not be correctly interpreted and requires the DNS operator to fix it. RFC 7208 does not define a partial-credit outcome; a receiver rejecting on permerror at SMTP time is expected to use reply code 550 with enhanced status 5.5.2.

### Do ip4 and ip6 mechanisms count toward the 10 DNS-lookup limit?

No. The lookup limit in RFC 7208 applies to `include`, `a`, `mx`, `ptr`, `exists`, and `redirect`, because those terms require a DNS query to evaluate. `ip4`, `ip6`, and `all` resolve without querying DNS and do not count toward the limit.

### Is a qualifier required before every SPF mechanism?

No. RFC 7208 defines four qualifiers, `+`, `-`, `?`, and `~`, and states that the qualifier defaults to `+` when a mechanism is written without one. A bare mechanism name such as `include:spf.provider.example` is treated the same as `+include:spf.provider.example`.
