Email Deliverability
Email deliverability essentials including SPF, DKIM, DMARC, and inbox placement
You are an expert in email deliverability, covering authentication protocols (SPF, DKIM, DMARC), sender reputation, and inbox placement optimization.
## Key Points
- `v=spf1` — SPF version identifier
- `include:_spf.google.com` — authorize Google Workspace servers
- `include:amazonses.com` — authorize Amazon SES
- `-all` — fail (reject) anything not explicitly authorized
1. Sending server signs the email headers and body with a private key.
2. The signature is added as a `DKIM-Signature` header.
3. The receiving server retrieves the public key from DNS using the selector.
4. It verifies the signature to confirm the message was not modified in transit.
- DMARC policy must be `quarantine` or `reject`
- Logo must be a Tiny PS SVG (specific SVG profile)
- A Verified Mark Certificate (VMC) from DigiCert or Entrust is required for Gmail
- Implement all three authentication protocols (SPF, DKIM, DMARC) before sending any production email. They are table stakes for deliverability in 2026.
## Quick Example
```
Sending Server → Recipient Server
│
├─ SPF: "Is this server allowed to send for this domain?"
├─ DKIM: "Was this message tampered with in transit?"
└─ DMARC: "What should I do if SPF or DKIM fails?"
```
```dns
; Basic SPF record
example.com. IN TXT "v=spf1 include:_spf.google.com include:amazonses.com include:mail.zendesk.com -all"
```skilldb get email-template-skills/Email DeliverabilityFull skill: 358 linesEmail Deliverability — Email Templates
You are an expert in email deliverability, covering authentication protocols (SPF, DKIM, DMARC), sender reputation, and inbox placement optimization.
Core Philosophy
Overview
Deliverability is the measure of whether your emails reach the recipient's inbox rather than being filtered to spam or rejected entirely. It depends on three pillars: authentication (proving you are who you say you are), reputation (your track record as a sender), and content quality (what you are sending). Even a perfectly designed email template is worthless if it lands in spam.
Core Concepts
Email Authentication Stack
The three protocols work together as layers of defense:
Sending Server → Recipient Server
│
├─ SPF: "Is this server allowed to send for this domain?"
├─ DKIM: "Was this message tampered with in transit?"
└─ DMARC: "What should I do if SPF or DKIM fails?"
SPF (Sender Policy Framework)
SPF is a DNS TXT record that lists which servers are authorized to send email for your domain:
; Basic SPF record
example.com. IN TXT "v=spf1 include:_spf.google.com include:amazonses.com include:mail.zendesk.com -all"
Breakdown:
v=spf1— SPF version identifierinclude:_spf.google.com— authorize Google Workspace serversinclude:amazonses.com— authorize Amazon SES-all— fail (reject) anything not explicitly authorized
SPF mechanisms:
| Mechanism | Meaning |
|---|---|
+all | Allow all (never use this) |
~all | Soft fail — accept but mark |
-all | Hard fail — reject unauthorized |
include:domain | Include another domain's SPF record |
ip4:203.0.113.0/24 | Authorize an IP range |
a | Authorize the domain's A record IP |
mx | Authorize the domain's MX record IPs |
DKIM (DomainKeys Identified Mail)
DKIM attaches a cryptographic signature to each outgoing message. The recipient verifies it against a public key published in DNS:
; DKIM public key record
selector1._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC..."
How it works:
- Sending server signs the email headers and body with a private key.
- The signature is added as a
DKIM-Signatureheader. - The receiving server retrieves the public key from DNS using the selector.
- It verifies the signature to confirm the message was not modified in transit.
A DKIM-Signature header looks like:
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=example.com; s=selector1;
h=from:to:subject:date:message-id;
bh=abc123...;
b=xyz789...
DMARC (Domain-based Message Authentication, Reporting, and Conformance)
DMARC ties SPF and DKIM together and tells receiving servers what to do when authentication fails:
; DMARC record
_dmarc.example.com. IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@example.com; ruf=mailto:dmarc-forensic@example.com; adkim=s; aspf=s; pct=100"
Parameters:
| Tag | Meaning | Values |
|---|---|---|
p | Policy | none (monitor), quarantine (spam), reject (block) |
rua | Aggregate report recipient | Email address |
ruf | Forensic report recipient | Email address |
adkim | DKIM alignment | r (relaxed), s (strict) |
aspf | SPF alignment | r (relaxed), s (strict) |
pct | Percentage of messages to apply policy | 1-100 |
DMARC Deployment Progression
Roll out DMARC gradually:
; Phase 1: Monitor only — collect reports, no enforcement
_dmarc.example.com. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.com"
; Phase 2: Quarantine 10% — test impact
_dmarc.example.com. IN TXT "v=DMARC1; p=quarantine; pct=10; rua=mailto:dmarc@example.com"
; Phase 3: Quarantine 100%
_dmarc.example.com. IN TXT "v=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc@example.com"
; Phase 4: Full reject
_dmarc.example.com. IN TXT "v=DMARC1; p=reject; rua=mailto:dmarc@example.com; ruf=mailto:dmarc-forensic@example.com"
Implementation Patterns
DNS Configuration for a New Domain
; SPF — authorize your ESP (e.g., SendGrid) and Google Workspace
example.com. IN TXT "v=spf1 include:sendgrid.net include:_spf.google.com -all"
; DKIM — add selector records (provided by your ESP)
s1._domainkey.example.com. IN CNAME s1.domainkey.u12345.wl.sendgrid.net.
s2._domainkey.example.com. IN CNAME s2.domainkey.u12345.wl.sendgrid.net.
; DMARC — start in monitoring mode
_dmarc.example.com. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com"
; Return-Path / Bounce domain (custom CNAME for ESP bounce handling)
em.example.com. IN CNAME u12345.wl.sendgrid.net.
Verifying Authentication with Command Line
# Check SPF record
dig +short TXT example.com | grep spf
# Check DKIM record
dig +short TXT selector1._domainkey.example.com
# Check DMARC record
dig +short TXT _dmarc.example.com
# Send a test email and check headers
# In the received email, look for:
# Authentication-Results: spf=pass dkim=pass dmarc=pass
Programmatic Authentication Check
import dns from "dns/promises";
async function checkEmailAuth(domain: string) {
const results: Record<string, string> = {};
// SPF
try {
const txtRecords = await dns.resolveTxt(domain);
const spf = txtRecords.flat().find((r) => r.startsWith("v=spf1"));
results.spf = spf || "NOT FOUND";
} catch {
results.spf = "DNS ERROR";
}
// DMARC
try {
const dmarcRecords = await dns.resolveTxt(`_dmarc.${domain}`);
const dmarc = dmarcRecords.flat().find((r) => r.startsWith("v=DMARC1"));
results.dmarc = dmarc || "NOT FOUND";
} catch {
results.dmarc = "DNS ERROR";
}
// MX
try {
const mxRecords = await dns.resolveMx(domain);
results.mx = mxRecords.map((r) => `${r.priority} ${r.exchange}`).join(", ");
} catch {
results.mx = "DNS ERROR";
}
return results;
}
Bounce and Complaint Handling
// Webhook handler for ESP bounce/complaint events (e.g., SendGrid Event Webhook)
import express from "express";
const app = express();
app.use(express.json());
app.post("/webhooks/email-events", async (req, res) => {
const events = req.body;
for (const event of events) {
switch (event.event) {
case "bounce":
if (event.type === "permanent") {
// Hard bounce: invalid address — suppress immediately
await suppressEmail(event.email, "hard_bounce");
console.log(`Hard bounce: ${event.email} — ${event.reason}`);
} else {
// Soft bounce: temporary issue — retry, suppress after 3 attempts
await recordSoftBounce(event.email);
console.log(`Soft bounce: ${event.email} — ${event.reason}`);
}
break;
case "spamreport":
// Recipient marked as spam — suppress immediately
await suppressEmail(event.email, "spam_complaint");
console.log(`Spam complaint: ${event.email}`);
break;
case "unsubscribe":
await unsubscribeEmail(event.email);
break;
case "dropped":
console.log(`Dropped: ${event.email} — ${event.reason}`);
break;
}
}
res.sendStatus(200);
});
async function suppressEmail(email: string, reason: string) {
// Add to suppression list — never send to this address again
await db.suppressionList.upsert({
email,
reason,
suppressedAt: new Date(),
});
}
async function recordSoftBounce(email: string) {
const record = await db.softBounces.findOne({ email });
const count = (record?.count || 0) + 1;
if (count >= 3) {
await suppressEmail(email, "repeated_soft_bounce");
} else {
await db.softBounces.upsert({ email, count, lastBounceAt: new Date() });
}
}
List Hygiene and Warm-Up
// IP/domain warm-up schedule for a new sending infrastructure
const warmUpSchedule = [
{ day: 1, dailyVolume: 50 },
{ day: 2, dailyVolume: 100 },
{ day: 3, dailyVolume: 250 },
{ day: 4, dailyVolume: 500 },
{ day: 5, dailyVolume: 1000 },
{ day: 6, dailyVolume: 2000 },
{ day: 7, dailyVolume: 5000 },
{ day: 8, dailyVolume: 10000 },
{ day: 9, dailyVolume: 20000 },
{ day: 10, dailyVolume: 40000 },
// Continue doubling until full volume
];
// During warm-up, send to most engaged users first
async function getWarmUpRecipients(limit: number) {
return await db.users
.find({ emailVerified: true, lastOpenedAt: { $gte: thirtyDaysAgo } })
.sort({ lastOpenedAt: -1 })
.limit(limit);
}
BIMI (Brand Indicators for Message Identification)
BIMI displays your brand logo in supporting email clients (Gmail, Yahoo, Apple Mail):
; BIMI record — requires DMARC p=quarantine or p=reject
default._bimi.example.com. IN TXT "v=BIMI1; l=https://example.com/brand/logo.svg; a=https://example.com/brand/vmc.pem"
Requirements:
- DMARC policy must be
quarantineorreject - Logo must be a Tiny PS SVG (specific SVG profile)
- A Verified Mark Certificate (VMC) from DigiCert or Entrust is required for Gmail
Email Headers for Deliverability
// Set proper headers when sending
const mailOptions = {
from: '"Company Name" <notifications@mail.example.com>',
to: recipient,
subject: "Your Order Confirmation",
html: renderedHtml,
text: plainText,
headers: {
// List-Unsubscribe (required by Gmail and Yahoo since Feb 2024)
"List-Unsubscribe": "<https://example.com/unsubscribe?token=abc>, <mailto:unsubscribe@example.com?subject=unsubscribe>",
"List-Unsubscribe-Post": "List-Unsubscribe=One-Click",
// Precedence header for transactional emails
Precedence: "transactional",
// Feedback-ID for Gmail Postmaster Tools categorization
"Feedback-ID": "campaign123:account456:example.com",
},
};
Best Practices
- Implement all three authentication protocols (SPF, DKIM, DMARC) before sending any production email. They are table stakes for deliverability in 2026.
- Start DMARC at
p=noneto collect reports, then gradually increase enforcement. Jumping straight top=rejectcan block legitimate email you forgot to authorize. - Use a dedicated subdomain for transactional email (e.g.,
mail.example.com) and a separate one for marketing (e.g.,news.example.com). This isolates reputation. - Include
List-UnsubscribeandList-Unsubscribe-Postheaders on all non-critical emails. Gmail and Yahoo require one-click unsubscribe for bulk senders since February 2024. - Process bounces and spam complaints immediately. Continuing to send to addresses that bounce or complain damages sender reputation.
- Warm up new IPs and domains gradually. Start with your most engaged recipients and scale volume over 2-4 weeks.
- Monitor your sender reputation with Google Postmaster Tools (for Gmail) and Microsoft SNDS (for Outlook).
- Always send both HTML and plain-text versions. Missing plain-text is a spam signal for many filters.
- Keep spam complaint rate below 0.1% (Gmail's threshold) and bounce rate below 2%.
- Clean your list regularly. Remove addresses that have not engaged in 6-12 months.
Common Pitfalls
- Too many SPF lookups: SPF allows a maximum of 10 DNS lookups. Each
include:counts as a lookup. Exceeding 10 causes SPF to fail. Use SPF flattening tools to merge includes. - Missing alignment: DMARC requires that the domain in
From:aligns with either the SPF return-path domain or the DKIMd=domain. A mismatch causes DMARC to fail even if SPF and DKIM individually pass. - Not monitoring DMARC reports: Without monitoring
ruareports, you cannot detect unauthorized use of your domain or find misconfigured legitimate senders. - Sending from a no-reply address without proper authentication:
noreply@example.comstill needs SPF, DKIM, and DMARC just like any other address. - Ignoring the warm-up period: Sending high volume from a new IP or domain immediately triggers spam filters. ISPs trust senders who build volume gradually.
- Purchased or scraped email lists: Sending to lists that were not organically collected results in high bounce rates, spam complaints, and potential blacklisting.
- Missing unsubscribe mechanism: Since February 2024, Gmail and Yahoo require one-click
List-Unsubscribefor senders exceeding 5,000 messages per day. Non-compliance results in filtering. - Using shared IPs without understanding risk: Shared sending IPs mean your reputation is influenced by other senders on the same IP. For high-volume senders, dedicated IPs provide more control.
- Not checking blacklists: Regularly check your sending IPs against blacklists (Spamhaus, Barracuda, SORBS) using tools like MXToolbox. A blacklisted IP means rejected email.
Anti-Patterns
Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.
Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.
Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.
Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.
Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.
Install this skill directly: skilldb add email-template-skills
Related Skills
Dark Mode Email
Dark mode support patterns for email templates across major email clients
Email Accessibility
Accessible email design patterns for inclusive, standards-compliant email templates
Email Testing
Email testing workflows using Litmus, Email on Acid, Mailtrap, and other QA tools
Mjml
Building responsive email templates with the MJML markup language and toolchain
React Email
Building email templates with React Email components and rendering pipeline
Responsive Email
Responsive email CSS patterns for consistent rendering across clients and devices