---
name: spf_dkim_dmarc_setup
description: "Set up and fix SPF, DKIM, and DMARC for a sending domain: inventory senders, build the three records, stay under the 10 lookup SPF limit, get alignment with the visible From domain, and roll DMARC out from p=none monitoring to quarantine and reject without blocking real mail. Use when a domain needs email authentication, mail is landing in spam or being rejected with errors like Gmail 5.7.26 or Outlook 550 5.7.515, a bulk sender requirement must be met, a new sending service is being added, or DMARC reports show unknown or misaligned sources."
---

# SPF, DKIM, and DMARC Setup

## Purpose
Publish three DNS records that work together: SPF says which hosts may send for the envelope domain, DKIM signs mail so receivers can verify it was not altered and came from a domain that holds a key, and DMARC ties both to the visible From domain and tells receivers what to do when neither aligns. The deliverable is records that pass, align, and are staged so enforcement does not block legitimate mail.

Helper: `bin/check_auth.py` validates record strings and alignment offline, with no network access. Tag tables, limits, and four worked setups are in `references/record-reference.md`. Read that file before building records for an unfamiliar domain shape.

## Workflow

### 1. Inventory before touching DNS
List every source that sends with this domain: staff mail, transactional mail, marketing platform, CRM, helpdesk, invoicing, monitoring alerts, calendar invites, forwarded mail from websites. For each one record the From domain it uses, who operates it, and whether it supports a custom MAIL FROM subdomain and DKIM signing in your domain.

Also list subdomains that send, and domains owned by the same organization that send nothing. Non-sending domains need defensive records too, see step 6.

Do not skip this. Every failed rollout seen in practice is an inventory failure: enforcement turns on, then a forgotten invoicing tool gets blocked.

### 2. Read what is already published
```bash
dig +short TXT example.com                 # SPF lives at the apex, among other TXT records
dig +short TXT _dmarc.example.com          # DMARC
dig +short TXT google._domainkey.example.com   # DKIM, one query per known selector
dig +short MX example.com
```
Save the current values before changing anything, so a rollback is a paste, not a reconstruction. Watch for two TXT records starting `v=spf1` at one name, which is a permerror, and for a DMARC record at the wrong name.

Selectors cannot be discovered by scanning DNS. Get them from each provider's setup screen, or from the `s=` field in the DKIM-Signature header of a real message that source sent.

### 3. Build SPF, and count the lookups
One record, starting `v=spf1`, ending in `-all` for domains that send mail. Use `~all` only as a short staging step while you confirm the inventory, and say so when you do.

Count DNS-causing terms: `include`, `a`, `mx`, `ptr`, `exists`, and `redirect` each cost one, and terms inside an included record cost too. The limit is 10 for the fully expanded record, and passing it returns permerror. RFC 7208 section 4.6.4 also caps void lookups at 2.

```bash
python3 bin/check_auth.py --spf "v=spf1 include:_spf.google.com include:sendgrid.net -all"
```
The script reports the static count, which is a floor. To get the real number, expand each include by hand with `dig +short TXT <included-domain>` and count the DNS terms in what comes back. One include from a large provider can cost several lookups on its own.

When the count is the problem, fix it in this order:
1. Drop senders that no longer send. Stale includes are the usual cause.
2. Move a busy third party onto its own sending subdomain with its own SPF record. Lookup budgets are per record, so splitting senders across subdomains is the durable fix.
3. Replace `a` and `mx` with explicit `ip4:` or `ip6:` terms when the hosts are stable.
4. Remove `ptr`. It is deprecated and slow.

Avoid automatic SPF flattening as a first move. Flattened records go stale when a provider changes its ranges, and then mail fails with no change on your side. If flattening is used, it must be a service that republishes on a schedule, and that dependency belongs in the report.

### 4. Set up DKIM per source
Generate or obtain a 2048 bit RSA key for each signing source. RFC 8301 sets 1024 bits as the floor and 2048 as what signers should use, and it forbids rsa-sha1, so confirm the signer uses rsa-sha256.

Publish the public key at `<selector>._domainkey.<domain>`. A 2048 bit key will not fit in one 255 character TXT string, so publish it as chunked strings in a single record. Then enable signing at the provider and confirm the selector in the header of a live message, not just in the provider's console.

```bash
python3 bin/check_auth.py --dkim "v=DKIM1; k=rsa; p=<base64 key from the provider>"
```
The script flags an empty `p=` (revoked key), a key that decodes to a 1024 bit size, test mode `t=y`, and a corrupt base64 value.

Rotate with a new selector rather than by editing the existing record: publish the new selector, switch the signer, wait past the TTL and the longest retry window you care about, then revoke the old key by publishing it with an empty `p=`, or delete it. Add Ed25519 (RFC 8463) only as a second signature alongside RSA. Receiver support is uneven, so Ed25519 alone leaves some receivers with nothing to verify.

### 5. Check alignment, which is where setups quietly fail
DMARC passes when SPF or DKIM passes and the passing identifier aligns with the domain in the visible From header. Both can pass and DMARC still fails, when a platform sends with its own envelope domain and signs with its own domain.

- SPF aligns when the MAIL FROM domain aligns with the From domain.
- DKIM aligns when the signature `d=` domain aligns with the From domain.
- Relaxed alignment, the default, compares organizational domains, so `send.example.com` aligns with `example.com`. Strict requires an exact match. Set strict only when you control every subdomain sender.

```bash
python3 bin/check_auth.py --align --from-domain example.com \
  --mailfrom-domain bounce.platform.example.net --dkim-domain example.com \
  --spf-pass --dkim-pass
```
Aim for both mechanisms aligned. One aligned mechanism passes DMARC but leaves no margin: a key rotation mistake or an envelope change then takes delivery down.

The usual fix order is custom DKIM signing in your domain first, because it survives forwarding better than SPF, then a custom MAIL FROM subdomain under your domain so SPF aligns as well.

### 6. Publish DMARC in monitoring mode
At `_dmarc.<domain>`:

```
v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com; adkim=r; aspf=r; fo=1
```

Current standard is RFC 9989 with aggregate reporting in RFC 9990 and failure reporting in RFC 9991, which replaced RFC 7489 in May 2026. Records still start `v=DMARC1`.

Three details that cost people weeks:
- `rua` must point at a mailbox or report service that actually parses the XML. Unread reports make `p=none` permanent and useless.
- If the report address is in a different domain, that domain needs an authorization record at `<your-domain>._report._dmarc.<report-domain>` with the value `v=DMARC1`. Without it, receivers that check may discard your reports.
- Name an owner for the report feed and write down the exit condition for each stage before you start. A rollout with no exit condition stalls.

For a domain that sends no mail, skip monitoring and publish enforcement plus a null SPF policy:
```
example.com        TXT  v=spf1 -all
_dmarc.example.com TXT  v=DMARC1; p=reject; sp=reject; np=reject; rua=mailto:dmarc-reports@example.com
```

### 7. Roll out by gates, not by calendar
Move only when the gate is met. Typical dwell is a couple of weeks per stage because you need at least one full sending cycle, including monthly and quarterly mail, but the gate is the evidence, not the date.

| Stage | Record change | Gate to move on |
|---|---|---|
| Monitor | `p=none` with `rua` | Every source in the reports is identified, and every legitimate source passes aligned |
| Quarantine | `p=quarantine` | No legitimate source has failed aligned in the last several report cycles |
| Reject | `p=reject` | Same, and someone with authority has accepted that unknown senders will be refused |
| Subdomains | add `sp=reject` and `np=reject` | Subdomain inventory is complete, including subdomains that send nothing |

Notes on staging controls:
- `pct` was the old percentage lever, and older guides lean on it. RFC 9989 retired `pct`, so migrated receivers ignore it and unmigrated ones may honor it. Do not make `pct` your only safety control.
- A safer partial step is to enforce on a low-risk subdomain or a secondary domain first, watch the reports, then apply the same policy to the main domain.
- Keep `rua` in the record at every stage, including after `p=reject`. Reports are how you catch the next tool someone adds.

Rollback is one DNS edit back to the previous policy. Keep the previous record text in the report so the rollback takes a minute.

### 8. Verify with a real message
DNS records alone prove nothing about production mail. Send from each source to a mailbox you control and read the Authentication-Results header. You want `dmarc=pass` with the From domain you expected, plus `dkim=pass` and `spf=pass` naming your domains. A sample header and how to read it are in the reference file.

```bash
python3 bin/check_auth.py --dmarc "v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com"
```

Then check the requirement that triggered the work, if there was one:
- Gmail and Yahoo, from February 2024: all senders need SPF or DKIM, valid forward and reverse DNS, and TLS. Senders at roughly 5,000 or more messages a day to personal accounts need SPF and DKIM, a DMARC record with `p=none` as the floor, From alignment, one-click unsubscribe on marketing mail, and a spam rate under 0.3 percent. Google describes 0.10 percent as the level to stay under.
- Microsoft, from May 5, 2025: the same 5,000 a day threshold for outlook.com, hotmail.com, and live.com needs SPF passing, DKIM passing, and DMARC at `p=none` or stricter with alignment. Non-compliant mail is rejected with 550 5.7.515.

## Output Contract
Report, in this order:
1. Domain and scope covered, including subdomains and non-sending domains.
2. Inventory table: source, From domain, envelope domain, DKIM selector and `d=` domain, aligned or not.
3. Exact records to publish, one block per DNS name, with the previous value beside any record being replaced.
4. SPF lookup count, static and expanded, with the per-include breakdown when the expanded count is above 7.
5. DMARC stage: current policy, the stage being published now, the gate for the next stage, and who owns the report feed.
6. Verification evidence: the Authentication-Results lines from a real test message per source, or a plain statement that no test message has been seen yet.
7. Open risks: unidentified report sources, senders with only one aligned mechanism, stale includes, 1024 bit keys still in use.

## Operating Rules
1. Every factual claim about limits, tags, and provider requirements comes from the standards and provider pages in the research notes. If a provider's current threshold cannot be confirmed, say it is unconfirmed rather than repeating a number from memory.
2. Never publish `+all`. Never leave a sending domain on `~all` without naming it as a staging step with an end condition.
3. Never turn on `p=quarantine` or `p=reject` from DNS inspection alone. Reports showing legitimate sources passing aligned are the precondition.
4. Never delete an existing DKIM selector or SPF include on the same day a new one is published. Overlap first, remove after the TTL and retry window have passed.
5. Do not treat a passing SPF or DKIM result as a DMARC pass. Alignment is a separate check, every time.
6. Do not advise relying on `pct`, `rf`, or `ri`. RFC 9989 retired all three.
7. Keep private keys out of DNS tickets, chat, and reports. Only the public half in `p=` is published, and it comes from the provider or the key tool, never typed by hand.
8. Copy base64 key material and record values from source. A single altered character in a DKIM key produces a failure that looks like a signing problem.
9. State plainly when a recommendation is practice rather than a standard, for example key rotation intervals and stage dwell times. The RFCs do not set either.
10. If a domain's mail is forwarded heavily, mailing lists, alumni forwarding, calendar relays, say that SPF will break on forwarded mail and that aligned DKIM is what carries those messages. Do not promise that correct records fix forwarding.
