> ## Documentation Index
> Fetch the complete documentation index at: https://docs.37audits.com/llms.txt
> Use this file to discover all available pages before exploring further.

# DNS Mail SPF

> Checks the SPF record that lists which servers may send email for the domain and how strictly receivers should treat everything else.

<Info>
  **Security Auditor** — looks up the TXT records of the audited domain and checks that exactly one of them is an SPF record (`v=spf1 …`) ending with a strict terminal mechanism (`-all` or `~all`).
</Info>

<CardGroup cols={2}>
  <Card title="Protection" icon="shield-halved">
    Stops other people from sending email that claims to come from your domain and keeps your real mail out of the spam folder.
  </Card>

  <Card title="Standards" icon="book">
    Follows RFC 7208, Sender Policy Framework (SPF) for Authorizing Use of Domains in Email, Version 1.
  </Card>
</CardGroup>

SPF is a TXT record published at a domain that names the servers allowed to send email for it. Receiving mail servers look the record up for every message that claims to come from the domain and use its terminal mechanism (`-all`, `~all`, `?all` or `+all`) to decide what to do with a message from a server that is not listed. This auditor reads the SPF record of the domain behind the audited URL and checks that it exists, that there is exactly one, and that it ends in a way that actually limits who can send.

## How it works

1. **Domain** — takes the host of the audited URL and strips one leading `www.` (`https://www.example.com/page` is audited as `example.com`). A URL without a host ends the audit with `501`. The auditor never fetches the page.
2. **Lookup** — asks DNS for the TXT records of the domain. A lookup that cannot complete (SERVFAIL, timeout) ends the audit with `500`; it is not reported as a missing record.
3. **Select** — keeps the TXT records that start with `v=spf1` (any letter case). None is a `FAIL` (`400`); more than one is a `FAIL` (`401`), because RFC 7208 treats several records as a permanent error.
4. **Terminal mechanism** — splits the single record on whitespace and takes its first `all` term (with any qualifier `-`, `~`, `?`, `+` or none), or, when there is no `all`, its first `redirect=` modifier. `+all` or a bare `all` fails (`402`); no terminal at all warns (`300`); the neutral `?all` warns (`301`).
5. **Length** — a record longer than 450 UTF-8 bytes warns (`302`), because the TXT answer is about to outgrow a 512-byte UDP packet.
6. **Aggregate** — FAIL wins over WARNING; when every rule passes the audit is `SUCCESS` (`200`) and the message quotes the record.
7. **Specification pin** — every run emits `37A-DnsMailSpfAuditor-100` (`INFORMATION`), naming [RFC 7208, Sender Policy Framework](https://www.rfc-editor.org/rfc/rfc7208) (April 2014). It does not change the overall status.

## What it audits

Every row maps to one `checkRule*` method and one `CheckCode`, in the order the auditor runs them. CheckCode suffixes use status ranges: `100` information, `200` success, `300–399` warning, `400–499` fail, `500–599` error.

| CheckCode | Status | When it fires | Recommendation | Why it matters |
| - | - | - | - | - |
| <a id="37A-DnsMailSpfAuditor-100" />`37A-DnsMailSpfAuditor-100` | `INFORMATION` | Always. Records the specification this auditor implements: RFC 7208 Sender Policy Framework (April 2014). | — (recommendation is `null`; nothing for the site to change) | — |
| <a id="37A-DnsMailSpfAuditor-400" />`37A-DnsMailSpfAuditor-400` | `FAIL` | The domain has no TXT record starting with `v=spf1` (no TXT records at all, or none of them is an SPF record). | Publish one TXT record at `{domain}` such as "v=spf1 include:\<your mail provider> -all" listing every server that sends email for the domain. | Without SPF anyone can send email that claims to come from your domain, and receivers have no way to tell it from your real mail, which hurts deliverability and enables phishing in your name. |
| <a id="37A-DnsMailSpfAuditor-401" />`37A-DnsMailSpfAuditor-401` | `FAIL` | The domain has more than one TXT record starting with `v=spf1`. | Merge the `{count}` SPF records of `{domain}` into a single "v=spf1 ... -all" TXT record and delete the others. | Receivers must treat several SPF records as a permanent error, so the domain gets no SPF protection at all and its email may be rejected. |
| <a id="37A-DnsMailSpfAuditor-402" />`37A-DnsMailSpfAuditor-402` | `FAIL` | The terminal mechanism of the record is `+all` or a bare `all`, which passes every sender. | Replace "`{terminalMechanism}`" with "-all" (or "\~all") in the SPF record of `{domain}` so only the listed servers pass. | Passing every sender makes the record worthless: anyone on the internet can send email as your domain and have it accepted as genuine. |
| <a id="37A-DnsMailSpfAuditor-300" />`37A-DnsMailSpfAuditor-300` | `WARNING` | The record has neither an `all` mechanism (any qualifier) nor a `redirect=` modifier. | Append "-all" (or "\~all") to the SPF record of `{domain}`: "`{spfRecord}` -all". | Without a terminal mechanism receivers get no verdict for unlisted senders, so the record does nothing to stop someone else from sending email as your domain. |
| <a id="37A-DnsMailSpfAuditor-301" />`37A-DnsMailSpfAuditor-301` | `WARNING` | The terminal mechanism of the record is the neutral `?all`. | Replace "`{terminalMechanism}`" with "-all" (or "\~all" while you confirm every legitimate sender is listed) in the SPF record of `{domain}`. | A neutral result tells receivers to treat unlisted senders as if there were no SPF record, so forged email in your domain's name still reaches inboxes. |
| <a id="37A-DnsMailSpfAuditor-302" />`37A-DnsMailSpfAuditor-302` | `WARNING` | The record is longer than 450 UTF-8 bytes. | Shorten the SPF record of `{domain}` from `{recordBytes}` bytes to 450 or fewer: replace listed hosts with an include: of your mail provider or drop senders you no longer use. | Once the TXT answer no longer fits in a UDP packet some receivers fail the lookup and reject or junk legitimate email from the domain. |
| <a id="37A-DnsMailSpfAuditor-200" />`37A-DnsMailSpfAuditor-200` | `SUCCESS` | All rules passed: exactly one `v=spf1` record ending with `-all` or `~all` (or delegating with `redirect=`). The message quotes the record, its length and its terminal mechanism. | — | — |
| <a id="37A-DnsMailSpfAuditor-500" />`37A-DnsMailSpfAuditor-500` | `ERROR` | The TXT lookup of the domain could not complete (SERVFAIL, timeout, resolver exception). | Make sure the name servers of `{domain}` answer TXT queries, then run the audit again. Last error: `{error}`. | — |
| <a id="37A-DnsMailSpfAuditor-501" />`37A-DnsMailSpfAuditor-501` | `ERROR` | The audited URL is missing, malformed or has no hostname, so there is no domain to look up. | Audit a URL with a scheme and a hostname, such as [https://example.com/](https://example.com/), instead of "`{url}`". | — |

## Output documentation

| Status | Description | Test logic |
| - | - | - |
| **SUCCESS** | The domain publishes one strict SPF record | Exactly one `v=spf1` TXT record whose terminal mechanism is `-all`, `~all` or a `redirect=` modifier, at most 450 bytes long (`200`). |
| **WARNING** | The SPF record exists but is weak or fragile | No terminal mechanism (`300`); neutral `?all` (`301`); record longer than 450 bytes (`302`). |
| **FAIL** | The domain has no usable SPF record | No `v=spf1` record (`400`); more than one (`401`); terminal `+all` or bare `all` (`402`). |
| **ERROR** | The auditor could not look up the domain | DNS lookup failed (`500`); the URL has no domain (`501`). |
| **INFORMATION** | Specification pin | Always emitted (`100`); does not change overall status. |

Every check carries `domain`, `txtRecords`, `spfRecords`, `spfRecord`, `recordCount`, `terminalMechanism` and `recordBytes` in its `data`, so the record the verdict was taken from is visible in the report.

## Risks and considerations

### Why SPF matters

* Without SPF, or with a record that passes everyone, phishing campaigns can send email as your domain and receivers cannot tell it from your real mail; DMARC also cannot be enforced without SPF or DKIM.
* A broken record (several records, an oversized answer) is worse than none for deliverability: receivers return a permanent error and may reject your legitimate mail.

### `-all`, `~all`, `?all`, `+all`

* `-all` (fail) asks receivers to reject unlisted senders; `~all` (softfail) asks them to accept but mark the message, and is the usual first step while you confirm every legitimate sender is listed.
* `?all` (neutral) and `+all` (pass) give no protection. `redirect=other.example` hands the whole evaluation to another domain's record and counts as a terminal; that other record is not followed by this auditor.
* The auditor reads the first `all` term in the record; RFC 7208 ignores anything after it.

### What changed in this version

* `+all` (and a bare `all`) now **fails** with `402`; the previous version only warned.
* The two "no terminal mechanism" warnings no longer fire together: a record with no `all` warns once with `300`, a record with `?all` warns once with `301`.
* A `redirect=` modifier now counts as a terminal mechanism instead of warning.
* A URL without a hostname is an `ERROR` (`501`) instead of a `FAIL`.
* A DNS lookup that cannot complete is an `ERROR` (`500`) instead of a "missing SPF record" `FAIL`.

### What this auditor does not check

* It does not evaluate the mechanisms themselves: `include:` chains, the 10-lookup limit, `a`/`mx`/`ip4`/`ip6` validity or whether the listed hosts really send your mail.
* It only looks at the domain of the audited URL (minus one `www.`), not at subdomains used for mail.
* DKIM and DMARC have their own auditors.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.