# Email Deliverability

> Use when mail already sent lands in spam, is rejected, or fails the Gmail/Yahoo bulk-sender checks and the fix is authentication and reputation, not the sending code: SPF/DKIM/DMARC alignment, domain warmup, spam-complaint rate, one-click unsubscribe, BIMI. NOT putting mail on the wire through a provider API (that is `email-connector`).

- Skill: `ericrisco/email-deliverability` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add ericrisco/email-deliverability`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ericrisco/email-deliverability/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: ericrisco (https://skillmd.com/u/ericrisco)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ericrisco/email-deliverability

---


# email-deliverability — make mail authenticate and land in the inbox

This skill owns one layer: **why mail you already send gets filtered, deferred,
or rejected, and the DNS + reputation work that fixes it.** It starts where the
send technically succeeds but the message never reaches the inbox.

The lever is almost never the sending code. It is three DNS records that must
*align*, a complaint rate you must hold down, and a reputation you build slowly
and lose fast. Treat this as ops on a domain, not a code change.

## Not this skill

- **Sending the mail at all** — provider SDK, `sendEmail()` seam, templates,
  retries, batch caps, bounce/complaint webhooks → `../email-connector/SKILL.md`.
  That skill feeds the suppression list; this one explains the reputation it
  protects.
- **Subject lines, open/click optimization, list growth** → `newsletter`.
- **Outbound prospecting sequences, lead lists** → `cold-outreach` (deliverability
  is a *constraint* on it, not the same job).
- **Consent, lawful basis, retention of the address list** → `gdpr-privacy` and
  `data-policy`. Unsubscribe *mechanics* live here; consent *law* does not.
- **Handling the DKIM private key and provider API secrets** →
  `../secure-coding/SKILL.md`.

## The three records that must align

SPF, DKIM, and DMARC are not three ways to do the same thing. DMARC is the
policy; it *passes* only when SPF or DKIM both authenticate **and align** with
the visible `From:` domain. Most "SPF is set but DMARC fails" tickets are an
alignment miss, not a missing record.

| Record | What it asserts | Common break |
|---|---|---|
| SPF (TXT) | This IP/host is allowed to send for the domain | Lists the provider but the `Return-Path` is a different domain → no alignment |
| DKIM (TXT) | This message body+headers are signed by a key the domain published | Provider signs with `provider.net`, not your domain → no alignment; or a deprecated 1024-bit key (publish 2048-bit, rotate periodically) |
| DMARC (TXT at `_dmarc`) | What to do when neither aligns, plus where to send reports | Policy left at `p=none` forever; never tightened |

```dns
; DMARC — start at none to collect reports, then tighten to quarantine/reject.
_dmarc.acme.com.  IN TXT  "v=DMARC1; p=none; rua=mailto:dmarc@acme.com; fo=1"
```

Alignment rule, stated once: the domain in `From:` must match the domain SPF
authenticated (the `Return-Path`/envelope domain) **or** the domain in the DKIM
`d=` tag. Use a custom Return-Path and a domain-keyed DKIM selector through your
provider so at least one aligns. Verifying SPF alone passing is the classic
false comfort.

## Gmail / Yahoo bulk-sender rules

If you send **more than ~5,000 messages/day** to Gmail or Yahoo accounts, these
are enforced, not advisory. Enforcement ramped from late 2025; failures now draw
temporary and permanent rejections, not silent spam-foldering.

1. **SPF + DKIM both set, and DMARC present** at minimum `p=none`. At least one
   of SPF/DKIM must align with `From:`.
2. **One-click unsubscribe** (`List-Unsubscribe` + `List-Unsubscribe-Post`) on
   marketing/promotional mail, honored within **2 days**. Transactional mail
   (password reset, receipt, shipping) is exempt.
3. **Spam-complaint rate** kept low. The hard threshold where filtering kicks in
   is **0.3%** (Postmaster Tools); Google's reliable-inbox target is **below
   0.1%**. Treat 0.3% as the cliff, 0.1% as the speed limit.

```text
List-Unsubscribe: <https://acme.com/u/abc123>, <mailto:unsub@acme.com?subject=unsub>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
```

## BIMI — the verified logo, last

BIMI shows your brand logo (and on Gmail a blue verified checkmark) next to the
message. It is the **last** step, never a fix for placement: it requires you have
*already* passed DMARC at enforcement, and it changes how a delivered message
looks, not whether it gets delivered.

Hard prerequisite: DMARC at `p=quarantine` or `p=reject` with `pct=100`. A record
left at `p=none` does **not** qualify — that is why "add BIMI to escape the spam
folder" is backwards; you cannot publish it until the auth work is done.

Whether the logo (and checkmark) actually render depends on the certificate:

| Approach | Cost / proof | Where it shows |
|---|---|---|
| Self-asserted (no certificate) | Free; logo only | Yahoo, Fastmail — **not** Gmail |
| CMC (Common Mark Certificate) | Cheaper; ~12 months of public logo use, no registered trademark | Gmail + the rest, with checkmark |
| VMC (Verified Mark Certificate) | ~$749–$1,500/yr; needs a registered (or government-modified) trademark | Gmail + the rest, with checkmark |

Active mark-certificate issuers in 2026: DigiCert, GlobalSign, SSL.com (Sectigo
resells; Entrust exited mark certificates in 2025). The logo file must be **SVG
Tiny PS 1.2**, square, with no scripts or external references.

```dns
; BIMI — only valid once DMARC is at quarantine/reject with pct=100.
; a= is the VMC/CMC PEM; omit it for a self-asserted (non-Gmail) logo.
default._bimi.acme.com.  IN TXT  "v=BIMI1; l=https://acme.com/logo.svg; a=https://acme.com/vmc.pem"
```

Do BIMI only after weeks of clean, enforced DMARC. It amplifies a good
reputation; it does not create one.

## Warm a cold domain (decision flow)

A brand-new domain has zero reputation; blasting volume on day one looks exactly
like a spammer. Warm only if the domain genuinely has no history.

- **New domain, no send history** → warm: start ~20-50/day to *engaged*
  recipients, roughly double every few days over 4-6 weeks, watch Postmaster
  reputation before each step up. Send your most-opened content first.
- **Established domain, new provider/IP** → warm the *IP* path, not the domain;
  migrate a slice of volume and ramp.
- **Established domain, same IP, sudden spam folder** → do NOT warm; this is a
  reputation or auth regression. Read the rejection code and Postmaster trend
  first (see the error table).

Skipping warmup to "just send the campaign" is the most common self-inflicted
blacklisting. There is no fast path; reputation is earned by consistent,
low-complaint sending.

## Read the rejection, then act

| Signal | Likely cause | Fix |
|---|---|---|
| `550 5.7.26` | Message unauthenticated — SPF and DKIM both failed/missing | Publish SPF + DKIM through the provider; confirm at least one aligns |
| `dmarc=fail` in headers, SPF=pass | Alignment miss: `Return-Path` ≠ `From:` domain | Set a custom Return-Path on your domain, or align the DKIM `d=` |
| `421`/`451` deferral, then later delivery | Receiver throttling an unwarmed/spiky sender | Slow the ramp; spread volume; warm the domain |
| Postmaster "Bad" domain reputation | Sustained complaints / spam-trap hits | Cut volume, prune unengaged, fix consent, hold under 0.1% complaints |
| Lands in Promotions (not Spam) | Not a failure — bulk/marketing classification | Leave it; do not chase the Primary tab by faking transactional headers |

## Anti-patterns

| Anti-pattern | Why it breaks | Do instead |
|---|---|---|
| "SPF passes, so we're authenticated" | DMARC needs *alignment*; SPF on a mismatched Return-Path still fails DMARC | Verify DMARC result in headers, not SPF alone; align Return-Path or DKIM |
| Leaving DMARC at `p=none` forever | Publishes intent to enforce nothing; spoofers and filters both notice | Collect `rua` reports, then move to `quarantine` then `reject` |
| `p=reject` on day one with no report review | Silently drops your own legitimate sub-streams (CRM, support tools) | Stage `none` → `quarantine` → `reject`, reading reports at each step |
| Blasting full volume from a new domain | Looks like spam; gets throttled/blacklisted with no recovery for weeks | Warm: tiny start to engaged users, ramp over 4-6 weeks |
| Buying/scraping a list to hit volume | Spam traps + complaints spike past 0.3% → domain reputation tanks | Send only to opted-in, engaged recipients; prune the unengaged |
| Faking transactional headers to dodge Promotions | Violates bulk rules; risks rejection, not just foldering | Accept Promotions placement; earn Primary via engagement |
| Routing the unsubscribe to a form that takes a week | Breaks the 2-day one-click honor rule; raises complaints | Honor `List-Unsubscribe-Post` one-click within 2 days, automatically |
| Adding BIMI to fix spam-foldering | BIMI needs DMARC enforcement you do not have yet, and it changes logo display, not placement | Fix auth + reputation first; add BIMI last as a branding layer |

