# SPF: who can send emails for you

> How SPF works, how to configure your SPF record using your email provider's documentation, full mechanism syntax, and how to fix common SPF problems.

[SPF](https://verifiedinbox.ai/docs/glossary.md#spf) (Sender Policy Framework, defined in [RFC 7208](https://datatracker.ietf.org/doc/html/rfc7208)) is a public guest list of the servers and services allowed to send email on behalf of your [domain](https://verifiedinbox.ai/docs/glossary.md#domain).

Standard email (SMTP) does not verify the sender address on its own. Without SPF and [DKIM](https://verifiedinbox.ai/docs/dkim.md), an attacker can send messages that appear to come from your domain. Publishing an SPF record protects your domain from impersonation, improves deliverability, and is one of the building blocks required for [DMARC](https://verifiedinbox.ai/docs/dmarc.md) and your [BIMI](https://verifiedinbox.ai/docs/bimi.md) inbox logo.

We strongly recommend configuring an SPF record for every domain you own, and always using your email provider's official documentation to get the exact value they require.

## How SPF works

When a mail server receives a message, it checks the domain in the message's **Return-Path** (also called the `MAIL FROM` or bounce address) rather than the visible `From:` header shown in the inbox:

1. **You publish an SPF record:** Add a single [TXT record](https://verifiedinbox.ai/docs/glossary.md#txt-record) starting with `v=spf1` on your domain (and on any [subdomain](https://verifiedinbox.ai/docs/glossary.md#subdomain) that sends mail).
2. **An email is sent:** A server with IP address `192.0.2.1` connects to the recipient's mail server and specifies a `Return-Path` domain (or `HELO`/`EHLO` domain).
3. **The receiving server checks your list:** The receiver looks up the `v=spf1` TXT record for that `Return-Path` domain and checks whether the sending server's IP address matches your authorized mechanisms.
4. **The result is passed to DMARC:** If the IP matches, SPF returns `Pass`. If it does not match, SPF returns `SoftFail` (`~all`) or `Fail` (`-all`), and [DMARC](https://verifiedinbox.ai/docs/dmarc.md) decides whether to quarantine or reject the message.

Because SPF checks the `Return-Path` domain while readers see the `From:` header, DMARC also checks [alignment](https://verifiedinbox.ai/docs/glossary.md#alignment): the `Return-Path` domain must match your `From:` domain (or be a subdomain of it) for SPF alone to satisfy DMARC.

## How to configure an SPF record

An SPF record must always be published as a DNS [TXT record](https://verifiedinbox.ai/docs/glossary.md#txt-record) (the older dedicated `SPF` DNS record type was retired by RFC 7208). At the root of your domain (`@`), a typical record looks like this:

```text
v=spf1 include:_spf.google.com ~all
```

- `v=spf1` identifies the TXT record as an SPF version 1 policy. Every SPF record must begin with `v=spf1`.
- `include:_spf.google.com` authorizes Google Workspace's sending servers. If you use more than one service (for example Google Workspace for team mail and Postmark for product emails), combine them into the **same** TXT record:
  ```text
  v=spf1 include:_spf.google.com include:spf.mtasv.net ~all
  ```
- `~all` (`SoftFail`) or `-all` (`Fail`) at the very end tells receiving servers that no other IP addresses are authorized to send mail for the domain.

### Domains and subdomains that never send email

If you own parked domains, or subdomains like `www` that never send email, publish a strict no-mail SPF record on them so attackers cannot spoof them:

```text
v=spf1 -all
```

## Reputable, developer-friendly email providers

Always check the official documentation of each service that sends email for you, copy their recommended `include:` mechanism or custom return-path setup, and add it to your domain's DNS settings.

### Postmark

| Field | Value |
|---|---|
| **Typical SPF setup** | Custom `Return-Path` CNAME (`pm-bounces`) or `include:spf.mtasv.net` |
| **Official guide** | [Postmark SPF & Return-Path guide](https://postmarkapp.com/support/article/1090-how-do-i-set-up-spf-on-my-domain) |

### Resend

| Field | Value |
|---|---|
| **Typical SPF setup** | SPF TXT record on a dedicated sending subdomain (`send`) |
| **Official guide** | [Resend domain verification docs](https://resend.com/docs/dashboard/domains/introduction) |

### Amazon SES

| Field | Value |
|---|---|
| **Typical SPF setup** | Custom `MAIL FROM` subdomain with `v=spf1 include:amazonses.com ~all` |
| **Official guide** | [Amazon SES SPF authentication](https://docs.aws.amazon.com/ses/latest/dg/send-email-authentication-spf.html) |

### SendGrid

| Field | Value |
|---|---|
| **Typical SPF setup** | Automated security CNAMEs (or `include:sendgrid.net`) |
| **Official guide** | [SendGrid domain authentication](https://www.twilio.com/docs/sendgrid/ui/account-and-settings/how-to-set-up-domain-authentication) |

### Mailgun

| Field | Value |
|---|---|
| **Typical SPF setup** | `include:mailgun.org` on your sending domain or subdomain |
| **Official guide** | [Mailgun domain verification](https://documentation.mailgun.com/docs/mailgun/user-manual/get-started/verify-domain) |

### Google Workspace

| Field | Value |
|---|---|
| **Typical SPF setup** | `include:_spf.google.com` |
| **Official guide** | [Google Workspace SPF setup](https://support.google.com/a/answer/33786) |

### Microsoft 365

| Field | Value |
|---|---|
| **Typical SPF setup** | `include:spf.protection.outlook.com` |
| **Official guide** | [Microsoft 365 SPF setup](https://learn.microsoft.com/en-us/defender-office-365/email-authentication-spf-configure) |

## Technical reference: SPF syntax, mechanisms & results

Receiving servers evaluate an SPF record from left to right. The first mechanism that matches the connecting server's IP address determines the result. If no mechanism matches and no `redirect=` modifier is present, the default result is `Neutral` (as if `?all` were at the end).

### Qualifiers

Every mechanism can be prefixed with one of four qualifiers. When omitted, `+` (`Pass`) is the default.

| Qualifier | SPF result | Meaning |
|---|---|---|
| `+` *(default)* | `Pass` | The host is authorized to send mail. |
| `-` | `Fail` | The host is **not** authorized (for example `-all`). Receivers may reject the message. |
| `~` | `SoftFail` | The host is not authorized, but the domain is in transition (`~all`). Receivers accept and mark as suspicious, or defer to DMARC. |
| `?` | `Neutral` | Makes no assertion about whether the host is authorized (`?all`). Treated the same as `None`. |

### Mechanisms

| Mechanism | Uses DNS lookup? | What it matches |
|---|---|---|
| `all` | No | Matches all IP addresses. Placed at the very end (`~all` or `-all`) as the default rule; any mechanism after `all` is ignored. |
| `include:<domain>` | Yes | Evaluates the SPF record of `<domain>`. If that evaluation returns `Pass`, the `include:` matches. A `-all` or `~all` inside the included record does not fail your outer record; evaluation simply moves to the next term. If `<domain>` has no SPF record, it triggers a `PermError`. |
| `ip4:<ip-or-cidr>` | No | Matches a single IPv4 address (`ip4:192.0.2.1`, default `/32`) or CIDR block (`ip4:192.0.2.0/24`). |
| `ip6:<ip-or-cidr>` | No | Matches a single IPv6 address (default `/128`) or IPv6 CIDR block (`ip6:2001:db8::/32`). |
| `a` or `a:<domain>` | Yes | Matches the `A` (IPv4) or `AAAA` (IPv6) records of the current domain or `<domain>` (optional `/cidr`). |
| `mx` or `mx:<domain>` | Yes | Matches the IP addresses of the `MX` servers for the current domain or `<domain>` (capped at 10 address lookups per `mx` term). |
| `exists:<domain>` | Yes | Performs an `A` record lookup on `<domain>` (often combined with macros); matches if any `A` record exists. |
| `ptr` *(deprecated)* | Yes | Reverse DNS lookup. **Do not use:** RFC 7208 deprecates `ptr` because it is slow, unreliable, and adds heavy DNS load. |

### Modifiers

Modifiers are optional `name=value` pairs that may appear at most once per record.

| Modifier | Uses DNS lookup? | What it does |
|---|---|---|
| `redirect=<domain>` | Yes | Replaces the record with `<domain>`'s SPF policy when no earlier mechanism matches. Useful when one organization shares a single SPF policy across several domains. Ignored if `all` is present in the record. |
| `exp=<domain>` | No | Specifies a domain whose TXT record provides a custom SMTP rejection string when SPF returns `Fail`. Rarely used. |

### SPF evaluation results

| Result | Meaning | Typical action |
|---|---|---|
| `Pass` | The sending IP is authorized by the SPF record. | Accept (and pass SPF for DMARC if aligned). |
| `Fail` | The sending IP is explicitly unauthorized (`-all`). | Reject (`550 5.7.1`) or apply DMARC policy. |
| `SoftFail` | The sending IP is probably unauthorized (`~all`). | Accept and mark as spam, or apply DMARC policy. |
| `Neutral` | The record makes no assertion (`?all` or no `all` term). | Treat as unauthenticated. |
| `None` | No SPF record exists on the domain, or the domain is invalid. | Treat as unauthenticated. |
| `TempError` | Transient DNS error (timeout or `SERVFAIL`). | Temporarily defer (`451`) and retry later. |
| `PermError` | Permanent error in the published SPF record (syntax error, multiple SPF records, >10 lookups, or >2 void lookups). | Fails SPF authentication until you fix the DNS record. |

## Common issues

- **Multiple SPF records on the same domain (`PermError`).** A domain or subdomain may only have **one** `v=spf1` TXT record. Publishing two separate `v=spf1` records causes SPF to fail immediately with `PermError`. Merge all `include:` and `ip4:`/`ip6:` entries into a single record. Note that long TXT records (>255 characters) split into multiple quoted strings inside a *single* DNS TXT record are concatenated automatically without spaces; make sure there is a space between terms at string boundaries.
- **Too many DNS lookups: the 10-lookup limit (`PermError`).** RFC 7208 allows at most **10** DNS-querying mechanisms and modifiers (`include`, `a`, `mx`, `ptr`, `exists`, and `redirect`) across the entire evaluation, including any nested lookups inside `include:` records. (`all`, `ip4`, and `ip6` do not count toward this limit.) To stay under 10 lookups:
  - Remove `include:` statements for old tools and services you no longer use.
  - **Use separate subdomains for different mail streams.** Each [subdomain](https://verifiedinbox.ai/docs/glossary.md#subdomain) (such as `mail.yourcompany.com` for marketing or `send.yourcompany.com` for transactional receipts) gets its own independent 10-lookup budget.
  - Prefer `ip4:` and `ip6:` over `a` or `mx` for static servers you control.
- **Too many void lookups: the 2-void-lookup limit (`PermError`).** RFC 7208 recommends failing with `PermError` if more than **2** DNS lookups return either an empty answer (`0` records) or `NXDOMAIN` (non-existent domain). This usually happens when an `include:` or `a:` points to a decommissioned service or a typoed domain name.
- **Using `+all` or omitting `all`.** Ending an SPF record with `+all` authorizes every server on the internet to impersonate your domain. Omitting `all` (without `redirect=`) leaves the default as `?all` (`Neutral`). Always end your record with `~all` or `-all`.
- **Unaligned `Return-Path` and email forwarding.** Many third-party email tools use their own domain in the `Return-Path` unless you configure a custom return-path (or custom `MAIL FROM`) subdomain. In addition, when a recipient auto-forwards an email to another inbox, the forwarder's IP address often fails the original sender's SPF check. Configuring [DKIM](https://verifiedinbox.ai/docs/dkim.md) alongside SPF ensures your emails still pass [DMARC](https://verifiedinbox.ai/docs/dmarc.md) even when forwarded.
