# Deliverability and authentication (area 6)

Three green DNS records do not prove that a newsletter passes DMARC. Judge each sending stream on a message it actually delivered: the receiver's Authentication-Results, the DKIM signature's d= and h= tags, and the one-click headers. Then read the mailbox providers' own data with the right denominators.

Provider requirements, SMTP reply codes, Postmaster's dashboards, Gmail's stream separation advice, the Spamhaus blocklists page and Microsoft's SNDS FAQ were read on 2026-11-06 from the pages in `sources.yaml`; the Microsoft header format on 2026-11-02. Re-read them before quoting a number or a code.

## Sender inventory (DL01)

Start from `stack.md`, then look for senders it missed:

- DMARC aggregate reports for every primary domain: every source IP and DKIM d= that sends as the domain.
- Gmail Postmaster Tools: the domains verified and the traffic they show.
- Seed inbox headers: one received message per stream (newsletter, lifecycle, billing, support, product notifications, agencies).

Record per stream: platform, From domain, Return-Path domain, DKIM d= and selector, purpose, owner, approximate daily volume per receiving provider. Any sender found only in reports or headers is DL01 WARN and joins the inventory.

## What the providers require

### Gmail, all senders

- SPF or DKIM; valid forward and reverse DNS for sending IPs; TLS; RFC 5322 formatting.
- User-reported spam rate below 0.3% in Postmaster Tools.

### Gmail, bulk senders

- Bulk means close to 5,000 or more messages a day to personal Gmail accounts, counted on the primary domain including subdomains. The status is permanent once reached, so one campaign day can make a domain a bulk sender for good.
- SPF and DKIM both; a DMARC record whose policy can be none; the From domain aligned with the SPF or the DKIM domain.
- Marketing and subscribed messages: one-click unsubscribe with both List-Unsubscribe and List-Unsubscribe-Post headers, plus a visible unsubscribe link in the body. Transactional messages such as password resets are excluded.
- DKIM keys of 1,024 bits or more for personal Gmail; 2,048 recommended.
- Spam rate: keep it below 0.1% and never reach 0.3%. Mitigation needs 7 consecutive days below 0.3%.
- Enforcement increased from November 2025 with temporary and permanent rejections.

### Yahoo

- All senders: SPF or DKIM, spam rate below 0.3%, valid forward and reverse DNS, RFC 5321 and 5322 compliance.
- Bulk senders: SPF and DKIM; DMARC with at least p=none that must pass; relaxed alignment is acceptable; a working List-Unsubscribe with one-click (RFC 8058 POST highly recommended, mailto accepted); a visible body link; unsubscribes honoured within 2 days.
- Yahoo publishes no numeric bulk threshold. Its complaint rate is calculated on mail delivered to the inbox.

### Microsoft consumer mailboxes

- Applies to domains sending 5,000 or more messages a day to Microsoft consumer services with the same visible From domain. A business mailbox read in the Outlook app is not a consumer recipient.
- SPF and DKIM must both pass, and DMARC must pass with at least one aligned identity; p=none is allowed.
- Failing mail is rejected with 550 5.7.515. Microsoft's updated announcement replaced the earlier junk-folder plan with rejection from 5 May 2025.
- Microsoft recommends working unsubscribe links but does not state an RFC 8058 mandate the way Gmail does. Build one-click once for every provider; just do not cite Microsoft as its source.

## One-click unsubscribe (RFC 8058)

- List-Unsubscribe contains exactly one HTTPS URI; it may also carry a mailto.
- List-Unsubscribe-Post is exactly `List-Unsubscribe=One-Click`.
- The URI identifies the recipient and list, ideally with an opaque token.
- The POST carries no cookies or authorisation, and the sender must not redirect it.
- Both headers are covered by a valid DKIM signature and listed in its h= tag; otherwise receivers should not offer one-click.
- Testing the endpoint is a seed test (S04) because it unsubscribes someone: run it only with approval, on a seed's token from a mailbox the team controls. Send the POST without cookies, expect a 2xx with no redirect, then confirm the seed is suppressed in every sender.

## DMARC and SPF standards

- RFC 9989 (May 2026, Standards Track) obsoletes RFC 7489 and RFC 9091. The pct tag is replaced by a t test-mode tag; the organisational domain is found by a DNS tree walk capped at eight queries instead of a public suffix list; relaxed alignment stays the default. Where the subdomain tree is deep, publish the record at the author domain.
- RFC 7208 SPF: at most 10 DNS-querying mechanisms (include, a, mx, ptr, exists, and the redirect modifier, counted through nested includes). More is a permerror. Two SPF records on one name is also a permerror.
- DMARC policy changes are drafts for the domain owner. Never propose quarantine or reject before every legitimate sender is inventoried and aligned; a rushed reject drops product and support mail the inventory missed.

## Read-only DNS checks

```bash
dig +short TXT example.com                          # one SPF record, count lookups by hand
dig +short TXT _dmarc.example.com                   # policy, rua, alignment tags
dig +short TXT cio1._domainkey.example.com          # selector taken from a received DKIM-Signature s= tag
dig +short CNAME cio1._domainkey.example.com        # many providers delegate DKIM by CNAME
```

- Read the selector from a received message's DKIM-Signature. An empty TXT answer for a guessed selector proves nothing.
- DKIM key length: join the quoted chunks of the TXT answer first. The base64 p= value of an RSA key is about 216 characters for 1,024 bits and about 392 for 2,048 bits. To be exact, decode it locally: `echo '<p value>' | base64 -d | openssl rsa -pubin -inform DER -noout -text | head -1` prints the key size.

## Header check on a received message

1. From a seed inbox the team controls, save the raw source of one recent message per stream (most clients offer a "show original" or "view source" option; verify the label in the client used). A headers-only copy, such as a desktop client's headers box, checks authentication but not the body link.
2. Trust the Authentication-Results headers the receiving mailbox added: the top header plus adjacent headers from the same receiver. Some receivers write one header per method. Microsoft writes no authserv-id. Headers further down, added before the receiving mailbox, prove nothing.
3. Run:

```bash
python3 scripts/check_message_headers.py newsletter.txt lifecycle.txt --class marketing
python3 scripts/check_message_headers.py receipt.txt --class transactional
python3 scripts/check_message_headers.py partner-update.txt --class marketing --volume low
python3 scripts/check_message_headers.py newsletter.txt --microsoft-volume low
```

4. Read the receiver line the script prints: which headers it merged, their authserv-ids (or "authserv-id missing" for Microsoft) and which lower headers it ignored. Confirm they belong to the receiving mailbox.
5. The alignment line is an approximation (same domain or same organisational domain by label count). Under RFC 9989 the receiver walks DNS, so its dmarc= result is the authority.
6. A marketing message with no DKIM-Signature at all is DL04 FAIL at any volume, matching the rule below. If the receiver reports dkim=pass but the file has no DKIM-Signature header, the script says so: save the full raw source again.

How the script reads the body link (DL07):

- It reads the HTML parts when there are any, because that is what Gmail and Yahoo show; the plain text part counts only when there is no HTML. Words inside URLs never count, so a preferences link whose address contains "unsubscribe" is still a preferences link.
- PASS: unsubscribe wording in English, German, French, Spanish, Dutch, Italian or Portuguese on a link or within 80 characters of one in the same block, or a body link that reuses the HTTPS List-Unsubscribe URL (then confirm the label reads as unsubscribe in the message's language).
- WARN: a preferences link only; unsubscribe wording away from any link; or links whose wording the script does not recognise. Read that body and rate it by hand against DL07.
- FAIL, at any volume: no link in the body at all.

`--volume bulk` is the default because Gmail counts bulk status on the primary domain, subdomains included, and never removes it: once one stream crosses the threshold, judge the domain's other streams against the bulk rules too. Pass `--volume low` only when DL02 shows the domain below every provider's threshold; DL05 and the DL07 header checks then report WARN, while a body with no link and an unsigned marketing message stay FAIL.

`--microsoft-volume bulk` is also the default. Pass `--microsoft-volume low` when DL02 shows the From domain under 5,000 a day to Microsoft consumer mailboxes; a DL06 failure then reports WARN ("below Microsoft's 5,000 a day threshold") instead of FAIL.

The script never prints To, Cc or Delivered-To. A file with no body reports "NOT CHECKED DL07 body". It exits 1 on any FAIL and 2 when a file cannot be read. The regression fixtures (a Microsoft-format headers-only save, a one-header-per-method receiver, a preferences link with "unsubscribe" in its URL, a Swedish newsletter, an unsigned message, a DKIM i= tag on a subdomain of d= and wording away from any link) are listed in [worked-example.md](worked-example.md).

## Postmaster Tools and complaint data

- Dashboards in the current interface (v2): Compliance status, Spam rate, Feedback loop, Authentication, Encryption, Delivery errors. Google's deprecation page says the Domain and IP Reputation dashboards are being retired and a new Reputation dashboard will replace them, with no date, and the v2 API, generally available since 3 February 2026, has no reputation data. Its dashboards help page still describes them, so read one only if the account still shows it (read 2026-11-06).
- Compliance status values: Compliant, Needs work, No data found. It applies to messages to personal Gmail accounts and to primary domains only.
- DNS status reflects the settings seen when mail arrived, and fixes can take up to 7 days to show.
- Spam rate covers DKIM-authenticated mail delivered to the inbox and then marked as spam. Data is in UTC; low-volume days can be missing.
- Yahoo complaint feedback and Microsoft SNDS and JMRP need enrolment; not being enrolled is itself DL12 WARN for high-volume streams. SNDS reports per IP address (green under 10% spam, yellow, red over 90%), not per domain, so on shared IPs that data sits with the ESP. JMRP enrolment starts from the SNDS site.

Denominators:

- Use the provider's own rate for its requirement: Postmaster for Gmail, inbox-delivered mail for Yahoo.
- A blended ESP rate hides problems: 9 complaints among 3,000 inbox deliveries is 0.3%; the same 9 over 100,000 attempts across all providers is 0.009%.
- ESP complaint counts are partial. Braze calls its spam metric indicative because feedback loops report only part of complaints.
- Hard bounce rate per send or per 30-day stream: hard bounces divided by attempted deliveries for that stream. The bands below are the audit's own defaults; check the platform's account review thresholds, which may be stricter.

## Rules

### DL01 sender-inventory

- WARN: a sender found in headers, DMARC reports or Postmaster that the inventory missed.

### DL02 bulk-status

- INFO: record bulk status per provider (Gmail about 5,000 a day to personal Gmail on the primary domain; Microsoft 5,000 a day to consumer services on one From domain; Yahoo no number). Check daily volume on send days, not the monthly average.

### DL03 spf

- FAIL: two SPF records on one name, more than 10 DNS-querying mechanisms, or the envelope (Return-Path) domain not authorised.

### DL04 dkim

- WARN: a 1,024-bit key (Gmail recommends 2,048); only SPF aligns, so forwarding will break DMARC.
- FAIL: a key under 1,024 bits; no DKIM signature on a marketing stream.

### DL05 dmarc

- WARN: no rua address.
- FAIL: no DMARC record for a bulk domain, or dmarc=fail on a received message from a bulk stream.

### DL06 microsoft-both-pass

- FAIL: SPF or DKIM fails on a stream whose From domain sends 5,000 or more a day to Microsoft consumer mailboxes.
- WARN: the same failure below that volume (`--microsoft-volume low`); fix it before the domain reaches 5,000 a day.

### DL07 one-click-and-body-link

- FAIL (bulk marketing streams): no HTTPS List-Unsubscribe; List-Unsubscribe-Post missing or wrong; either header missing from the h= of a passing DKIM signature; the POST needs cookies or a login; the endpoint redirects; or no visible unsubscribe or preferences link in the body (the script fails a body with no link at all; apply the rest by hand after its WARN for unrecognised wording).
- WARN: the body has only a preferences link, or a low-volume marketing stream lacks one-click.
- Body link (seed S05): a login or password prompt, or more than one page to finish, is WARN. It is FAIL where decisions.md records US commercial email under CAN-SPAM: the FTC's compliance guide allows no step beyond a reply email or a visit to a single web page as a condition of opting out (read 2026-11-02).

### DL08 transport-and-dns

- WARN: no TLS evidence in Received headers; Postmaster shows DNS or encryption needing work.

### DL09 postmaster-compliance

- FAIL: any requirement shows Needs work.
- WARN: No data found (not enough mail to evaluate).
- Re-read after a fix: up to 7 days to show.

### DL10 spam-rate

- PASS: Gmail rate below 0.10%.
- WARN: 0.10% to below 0.30%.
- FAIL: 0.30% or more on any day; Yahoo 0.3% or more on inbox-delivered mail.
- NOT CHECKED: no Postmaster rate. Postmaster covers personal Gmail only, so a programme that mostly reaches Workspace or Microsoft 365 mailboxes often shows none. Missing data is never 0%: record it, and read ESP complaint counts as a lead.

### DL11 hard-bounces (audit default)

- PASS: 1% or less. WARN: above 1% to 2%. FAIL: above 2%.

### DL12 reputation-and-errors

- WARN: rising Postmaster delivery errors, including reasons that cite low IP or domain reputation; a reputation dashboard, where the account still shows one, reading Low; or not enrolled in Yahoo or Microsoft feedback programmes at bulk volume (on shared IPs, ask the ESP for its SNDS view instead).
- FAIL: a reputation dashboard reading Bad, or Microsoft SNDS red.
- An account with no reputation dashboard is not a finding, because Google is retiring them. Rate the rest of DL12 and write "reputation dashboard not available" with the read date.

### DL13 link-and-image-domains

- WARN: click tracking or images served from a shared provider domain instead of an HTTPS subdomain of the brand.

### DL14 sms-registration

- FAIL: US long-code or toll-free SMS sent without the registration or verification the provider requires. Verify the current rules in the provider console.

### DL15 push-reachability

- WARN: push journeys target users without OS permission or valid tokens, with no fallback channel.

### DL16 provider-rejections

Count 30 days of bounces and deferrals per stream by receiving provider and SMTP reply, from the ESP's bounce data or delivery events. Aggregate in the ESP or warehouse by recipient domain group and reply code; never export recipient addresses, and replace any address inside a sample reply text with `<redacted>`. Postmaster's Delivery errors dashboard adds Gmail's own view. Codes were read on 2026-11-02 from the Gmail SMTP error reference, the Yahoo SMTP error page and the Microsoft 5.7.515 page in `sources.yaml`; re-read them before quoting one.

- FAIL on a bulk stream (P0, with DL05 to DL07):
  - Gmail 5.7.26 (SPF and DKIM both fail, or the domain's DMARC policy rejects), 5.7.27 (bulk mail fails SPF), 5.7.30 (bulk mail fails DKIM), 5.7.32 (From not aligned with the SPF or DKIM domain), 5.7.40 (no DMARC record or no policy), 5.7.25 (no valid reverse DNS) or 5.7.29 (bulk mail without TLS).
  - The matching Gmail deferrals: 4.7.26 (unauthenticated mail rate-limited), 4.7.27, 4.7.30, 4.7.32, 4.7.40, 4.7.23 and 4.7.29. Deferred mail often arrives on retry, but the provider is already enforcing that requirement.
  - Microsoft 550 5.7.515, and 550 5.7.509 when the message failed DMARC on a domain whose policy is quarantine or reject, at any volume (Microsoft's DMARC handling post; verify the reply text live).
  - A Yahoo 553 or 554 reply saying the message failed authentication against the domain's DMARC or DKIM policy.
- WARN:
  - Gmail 4.7.28 or 5.7.28 (an unusual rate of unsolicited mail); read with DL10 and DL12.
  - Gmail 4.7.24 or 5.7.24 (suspicious entries in the SPF record); read with DL03.
  - Yahoo 421 or 451 deferrals citing complaints or unusual traffic, and TS-prefixed deferrals.
- Not DL16: Gmail 451 4.7.26 when the text says a temporary DNS failure stopped the DMARC decision, and 504 5.7.40, which is about SMTP login.
- Blocklists: look up the brand-owned domains (From, Return-Path, DKIM d=, link and image domains) and any dedicated sending IPs on Spamhaus's reputation checker (check.spamhaus.org, linked from its blocklists page: SBL, XBL, PBL and CSS list IPs, DBL lists domains; verify live). Also look up any other operator that a provider reply in the window names.
  - FAIL (P0 on a bulk stream): Spamhaus lists a brand-owned domain or a dedicated IP, or a provider reply in the 30 days cites a listing.
  - WARN: any other listing, such as a smaller operator's, or a listed shared provider IP or domain. Raise shared listings with the ESP.
  - Record the operator, the listed item, whether it is brand-owned, dedicated or shared, the lookup date and the delisting route.
- Evidence: counts per provider and code for the window, with one sample reply text per code, any address replaced by `<redacted>`. Never paste the raw export.

### DL17 stream-separation

- WARN: marketing and transactional or service messages go out from the same From address, or share one IP when the sender uses several. Gmail's sender guidelines ask for one From address per message category and, with several IPs, a different IP per message type (read 2026-11-02).
- A promotion inside a transactional message is PC07, not DL17.

Cold email domains, mailboxes and warm-up are out of scope; they belong to email-sender-infrastructure (planned). Sequencers and their outbound gates belong to gtm-workflow-design. The authentication rules here apply to marketing and lifecycle streams. A sequencer that reaches the same people still joins the sender inventory (DL01) and the send log, so FC04, FC08 and MH03 see it.
