
Email Best Practices
- 7.1k installs
- 136 repo stars
- Updated May 28, 2026
- resend/email-best-practices
email-best-practices is an agent skill for Use when building email features, emails going to spam, high bounce rates, setting up SPF/DKIM/DMARC authentication, implementing email capture, ensuring compliance (CAN-SPAM, G
About
Use when building email features emails going to spam high bounce rates setting up SPF DKIM DMARC authentication implementing email capture ensuring compliance CAN-SPAM GDPR CASL handling webhooks retry logic making emails accessible alt text headings contrast screen readers or deciding transactional vs marketing name email-best-practices description Use when building email features emails going to spam high bounce rates setting up SPF DKIM DMARC authentication implementing email capture ensuring compliance CAN-SPAM GDPR CASL handling webhooks retry logic making emails accessible alt text headings contrast screen readers or deciding transactional vs marketing license MIT metadata author Resend version 1 0 2 homepage https resend com agent-skills source https github com resend email-best-practices openclaw links repository https github com resend email-best-practices documentation https resend com docs email-best-practices-skill Email Best Practices Guidance for building deliverable compliant user-friendly emails Architecture Overview User Email Form Validation Double Opt-In Consent Recorded Suppression Check Ready to Send Idempotent Send Retry Email API Webhook Events Delivered Bo.
- **Spam issues?** Check [Deliverability](./references/deliverability.md) first - authentication problems are the most com
- Gmail/Yahoo reject unauthenticated emails.
- **Accessibility?** Most emails fail basic accessibility checks.
- See [Accessibility](./references/accessibility.md) for `lang`/`dir`, presentational tables, headings, alt text, `<title>
- Use when building email features, emails going to spam, high bounce rates, setting up SPF/DKIM/DMARC authentication, imp
Email Best Practices by the numbers
- 7,090 all-time installs (skills.sh)
- +100 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #94 of 2,203 Security skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 4, 2026 (Skillselion catalog sync)
email-best-practices capabilities & compatibility
- Capabilities
- **spam issues?** check [deliverability](./refere · gmail/yahoo reject unauthenticated emails. · **accessibility?** most emails fail basic access · see [accessibility](./references/accessibility.m · use when building email features, emails going t
- Use cases
- documentation
What email-best-practices says it does
**Spam issues?** Check [Deliverability](./references/deliverability.md) first—authentication problems are the most common cause.
Gmail/Yahoo reject unauthenticated emails.
**Accessibility?** Most emails fail basic accessibility checks.
npx skills add https://github.com/resend/email-best-practices --skill email-best-practicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 7.1k |
|---|---|
| repo stars | ★ 136 |
| Security audit | 3 / 3 scanners passed |
| Last updated | May 28, 2026 |
| Repository | resend/email-best-practices ↗ |
When should developers use email-best-practices and what problem does it solve?
Use when building email features, emails going to spam, high bounce rates, setting up SPF/DKIM/DMARC authentication, implementing email capture, ensuring compliance (CAN-SPAM, GDPR, CASL), handling we
Who is it for?
Developers working with email-best-practices patterns described in the skill documentation.
Skip if: Skip when cached docs are empty or the task is outside the skill's documented scope.
When should I use this skill?
Use when building email features, emails going to spam, high bounce rates, setting up SPF/DKIM/DMARC authentication, implementing email capture, ensuring compliance (CAN-SPAM, GDPR, CASL), handling we
What you get
Grounded guidance and workflows from SKILL.md for email-best-practices.
- authentication DNS records
- compliant email templates
- webhook and retry configurations
By the numbers
- Published as version 1.0.2 in skill metadata
- MIT licensed with source at github.com/resend/email-best-practices
Files
Email Best Practices
Guidance for building deliverable, compliant, user-friendly emails.
Architecture Overview
[User] → [Email Form] → [Validation] → [Double Opt-In]
↓
[Consent Recorded]
↓
[Suppression Check] ←──────────────[Ready to Send]
↓
[Idempotent Send + Retry] ──────→ [Email API]
↓
[Webhook Events]
↓
┌────────┬────────┬─────────────┐
↓ ↓ ↓ ↓
Delivered Bounced Complained Opened/Clicked
↓ ↓
[Suppression List Updated]
↓
[List Hygiene Jobs]Quick Reference
| Need to... | See |
|---|---|
| Set up SPF/DKIM/DMARC, fix spam issues | Deliverability |
| Build password reset, OTP, confirmations | Transactional Emails |
| Plan which emails your app needs | Transactional Email Catalog |
| Build newsletter signup, validate emails | Email Capture |
| Send newsletters, promotions | Marketing Emails |
| Ensure CAN-SPAM/GDPR/CASL compliance | Compliance |
| Decide transactional vs marketing | Email Types |
| Handle retries, idempotency, errors | Sending Reliability |
| Process delivery events, set up webhooks | Webhooks & Events |
| Manage bounces, complaints, suppression | List Management |
| Make emails accessible (screen readers, alt text, contrast) | Accessibility |
Start Here
New app? Start with the Catalog to plan which emails your app needs (password reset, verification, etc.), then set up Deliverability (DNS authentication) before sending your first email.
Spam issues? Check Deliverability first—authentication problems are the most common cause. Gmail/Yahoo reject unauthenticated emails.
Marketing emails? Follow this path: Email Capture (collect consent) → Compliance (legal requirements) → Marketing Emails (best practices).
Production-ready sending? Add reliability: Sending Reliability (retry + idempotency) → Webhooks & Events (track delivery) → List Management (handle bounces).
Accessibility? Most emails fail basic accessibility checks. See Accessibility for lang/dir, presentational tables, headings, alt text, <title>, and contrast.
name: Sync Skills
on:
push:
branches: [main]
paths:
- 'SKILL.md'
- 'references/**'
- 'scripts/**'
- 'assets/**'
workflow_dispatch:
permissions:
contents: read
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Trigger sync on resend-skills
env:
GH_TOKEN: ${{ secrets.SYNC_SKILLS_TO_RESEND_SKILLS }}
run: |
gh workflow run sync-from-repo.yml \
--repo resend/resend-skills \
--field repo=email-best-practices \
--field skill-path=. \
--field skill-name=email-best-practices \
--field sha=${{ github.sha }} \
--field reviewer=${{ github.actor }}
╔══════════════════════════════════════╗
║ _____ __ __ _ ___ _ ║
║ | ____| \/ | / \ |_ _| | ║
║ | _| | |\/| | / _ \ | || | ║
║ | |___| | | |/ ___ \ | || |___ ║
║ |_____|_| |_/_/ \_\___|_____| ║
║ ║
║ Best Practices ║
╚══════════════════════════════════════╝Email Best Practices Skill
A comprehensive agent skill for building production-ready email systems. Covers everything from DNS authentication to webhook processing, with a focus on deliverability, compliance, and reliability.
Installation
npx skills add resend/email-best-practicesWhat This Skill Covers
Getting Started
- Planning which emails your app needs (password reset, verification, order confirmations)
- Setting up email authentication (SPF, DKIM, DMARC) so emails reach inboxes
Sending Emails
- Transactional email design (subject lines, content structure, mobile-first)
- Marketing email best practices (consent, segmentation, unsubscribe)
- Compliance requirements by region (CAN-SPAM, GDPR, CASL)
Production Infrastructure
- Idempotency and retry logic to prevent duplicates
- Webhook processing for delivery events
- Suppression lists and list hygiene automation
Structure
email-best-practices/
├── SKILL.md # Start here - routes to the right resource
└── references/
├── deliverability.md # SPF/DKIM/DMARC, sender reputation
├── transactional-emails.md # Password resets, OTPs, confirmations
├── transactional-email-catalog.md # Email combinations by app type
├── marketing-emails.md # Newsletters, campaigns, consent
├── email-capture.md # Validation, verification, opt-in
├── compliance.md # CAN-SPAM, GDPR, CASL
├── email-types.md # Transactional vs marketing
├── sending-reliability.md # Idempotency, retry logic, errors
├── webhooks-events.md # Delivery events, webhook setup
└── list-management.md # Suppression lists, hygieneQuick Start
Open SKILL.md - it has a routing table that directs you to the right resource based on what you need to do.
License
MIT
Email Accessibility
Emails must be readable by screen readers, dark-mode clients, translation tools, and AI agents — not just sighted readers on a default inbox. The rules below are mechanical. Apply them every time.
Rules
Set lang and dir on <html> and on <body>'s direct children (Serious)
Both attributes are needed in two places: on <html> and on the direct children of <body>. Several email clients strip the attributes from <html>, which is why duplicating them on the body's children is the single most common accessibility failure in production email.
<html lang="en" dir="ltr">
<head>
<title>Your weekly product updates</title>
</head>
<body>
<div lang="en" dir="ltr">
<!-- email content -->
</div>
</body>
</html>lang: a BCP 47 language tag (en,pt-BR,ja,ar)dir:ltr,rtl, orauto
Fallbacks when the correct values aren't available (use only when you genuinely don't know):
dir="auto"— lets the user agent infer direction from contentlang="und"— marks the language as undetermined
Both fallbacks are worse than the correct value but much better than nothing. For multi-locale templates, pass the locale through; do not hardcode en.
Mark layout tables as presentational (Serious)
Any <table> used for layout must have role="presentation" (or the equivalent role="none"). Otherwise screen readers announce "table, row 1 of N" for every layout row and the email becomes unusable. Prefer avoiding layout tables entirely; when you can't, mark them.
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
<tr>
<td>...</td>
</tr>
</table>Leave a <table> without role="presentation" only when the data is genuinely tabular (line items, comparison rows). Tabular data should also use <th scope="col"> for column headers.
Use a single <h1> and nest headings in order (Mild)
Most emails should have one <h1> that names the email, with subheadings nested in order:<h1> → <h2> → <h3>. Never skip levels. Never fake a heading with bold <p>.
<h1>Order confirmation</h1>
<h2>Items</h2>
<h2>Shipping</h2>
<h3>Address</h3>
<h3>Tracking</h3>Headings are how assistive tech and AI clients navigate and summarize the email.
Exception: very short messages (SMS-style notifications, single-sentence alerts) may not need a heading at all. If the email body is one or two sentences, skip the <h1> rather than wrap a heading around the only content.
Every link must have discernible text (Serious)
Every <a> must contain text content that a screen reader can announce. The most common failure is a linked image with no alt text.
A linked image is never decorative. It's functional, so its alt must describe what clicking does, not just what the image looks like.
<!-- Wrong: linked image with no accessible name -->
<a href="/order/123">
<img src="view-order.png" alt="">
</a>
<!-- Right: alt describes the action -->
<a href="/order/123">
<img src="view-order.png" alt="View order #123">
</a>
<!-- Also right: visible text alongside the image -->
<a href="/order/123">
<img src="icon.png" alt="">
View order #123
</a>When the visible link text can't carry enough information, add visually hidden text inside the <a> (see goodemailcode.com/email-accessibility/visually-hidden-text). aria-label and title on <a> have limited support in email clients. Prefer real text content or visually hidden text.
Link text must describe the destination (Moderate)
Even when a link has text, it must describe where the link goes. Never use "click here," "learn more," "read more," or bare URLs. Screen reader users often navigate by jumping between link texts with no surrounding context.
<!-- Wrong -->
<a href="...">click here</a>
<a href="...">https://resend.com/blog/...</a>
<!-- Right -->
<a href="...">Read the 2026 accessibility report</a>Write meaningful alt text — and use alt="" for decorative images (Critical)
Two distinct rules, both mandatory. alt must always be present; the value depends on the image's role.
Meaningful images (product shots, charts, screenshots, anything carrying information): describe purpose and key details in context.
<!-- Wrong: redundant, vague -->
<img src="..." alt="image">
<img src="..." alt="photo of a bike">
<!-- Right: purpose + key details -->
<img src="..." alt="Red bicycle leaning against a brick wall on a rainy street">Decorative images (spacers, dividers, background flourishes, pure branding ornaments): use an empty alt="". This tells screen readers to skip them. Never omit the attribute entirely — omitting it and alt="" are not equivalent; some screen readers announce the filename when alt is absent.
<img src="divider.png" alt="" role="presentation">If an image conveys no information that isn't already in the surrounding text, it is decorative. If it's inside an <a>, it is not decorative — see the "Every link must have discernible text" rule.
Include a <title> tag (Serious)
Many clients and assistive technologies read <title> before anything else. It's also shown when the email is viewed as a web page. Treat it like the subject line, not the brand name.
<head>
<title>Your weekly product updates from Resend</title>
</head>If the per-email title is hard to populate, a generic but specific fallback like "Email from {Brand Name}" still beats nothing.
Hit 4.5:1 color contrast, then check dark mode (Serious)
- Body text and links: 4.5:1 minimum against the background (WCAG AA)
- Large text (≥18pt, or ≥14pt bold): 3:1 minimum
- Never rely on color alone to convey meaning (error states, status badges) — pair it with text or an icon
Verify with the WebAIM Contrast Checker or browser devtools.
Dark mode. Outlook, Apple Mail, and others force dark mode and derive dark colors from your light ones. Healthy starting contrast keeps the auto-inverted version readable. Always preview in dark mode before shipping.
Priority order
When you can't fix everything, fix in this order:
1. Critical — missing or misused alt on images 2. Serious — lang/dir (on <html> and body children), role="presentation" on layout tables, links without discernible text, missing <title>, color contrast 3. Moderate — non-descriptive link text ("click here") 4. Mild — missing <h1> (skip the fix for very short messages)
Authoring checklist
Run this on every template:
- [ ]
<html>haslanganddir; direct children of<body>also havelanganddir - [ ]
<title>set on<head>, specific to this email (not the brand name) - [ ] Layout
<table>elements haverole="presentation"(orrole="none") - [ ] At most one
<h1>(or none, for very short messages);<h2>/<h3>nested in order - [ ] Every
<a>has discernible text — visible text, descriptive alt on linked images, or visually hidden text - [ ] Every link describes its destination — no "click here," "learn more," or bare URLs
- [ ] Every meaningful image has descriptive
alt; every decorative image has an explicitalt="" - [ ] No linked image with
alt=""(linked images are functional, never decorative) - [ ] Body text passes 4.5:1 contrast and stays readable in dark mode
- [ ] Plain-text alternative is sent alongside the HTML version
Testing
- Automated. Run the email through Parcel's accessibility checker (the same tool the EMC report uses; available on the free plan). It catches most of the rules above.
- Screen reader pass. macOS VoiceOver (
Cmd+F5) or NVDA on Windows. Listen top to bottom; if anything is confusing, fix the markup. - Contrast. WebAIM Contrast Checker.
- Dark mode. Send a test to Outlook (Windows/web), Apple Mail with dark appearance, Gmail iOS and Android.
Automated tests do not catch everything. They will not tell you whether alt text actually matches the image, whether headings make semantic sense, or whether text inside an image is readable on narrow viewports. Even the three brands that passed every automated check in the EMC 2026 report had judgment-level issues like generic alt text on decorative images, alt text that didn't match the image, and 10px footer text. Treat automation as a floor, not a ceiling.
Related
- Transactional Emails — content patterns for password resets, OTPs, receipts
- Marketing Emails — newsletter and campaign best practices
- Compliance — legal requirements that overlap with accessibility (e.g., clear unsubscribe text)
Tooling
When generating templates with React Email, the latest version handles several of the structural rules: <Html> sets lang/dir, <Img> defaults to alt="", <Markdown> tables render role="presentation", and <Preview> emits a <title>. Upgrade with npm install react-email@latest. The content rules — heading hierarchy, descriptive alt and link text, contrast, the linked-image rule — still have to be applied by hand.
Email Compliance
Legal requirements for email by jurisdiction. Not legal advice—consult an attorney for your specific situation.
Quick Reference
| Law | Region | Key Requirement | Penalty |
|---|---|---|---|
| CAN-SPAM | US | Opt-out mechanism, physical address | $53k/email |
| GDPR | EU | Explicit opt-in consent | €20M or 4% revenue |
| CASL | Canada | Express consent, opt-out mechanism | $1M (individual) to $10M (organization) CAD |
CAN-SPAM (United States)
Requirements:
- Accurate header info (From, To, Reply-To)
- Non-deceptive subject lines
- Physical mailing address in every email
- Clear opt-out mechanism
- Honor opt-out within 10 business days
Transactional emails: Can send without opt-in if related to a transaction and not promotional.
GDPR (European Union)
Requirements:
- Explicit opt-in consent (not pre-checked boxes)
- Consent must be freely given, specific, informed
- Easy to withdraw consent (as easy as giving it)
- Right to access data and deletion ("right to be forgotten")
- Process unsubscribe immediately
Consent records: Document who, when, how, and what they consented to.
Transactional emails: Can send based on contract fulfillment or legitimate interest.
CASL (Canada)
Consent types:
- Express consent: Explicit opt-in (ideal)
- Implied consent: Existing business relationship (2 years) or inquiry (6 months)
Requirements:
- Clear sender identification that will be valid for 60 days after send
- Unsubscribe functional for 60 days after send
- Process unsubscribe no later than 10 business days
- Keep consent records 3 years after expiration
Other Regions
| Region | Law | Key Points |
|---|---|---|
| Australia | Spam Act 2003 | Consent required, honor unsubscribe within 5 days |
| UK | PECR + GDPR | Same as GDPR |
| Brazil | LGPD | Similar to GDPR, explicit consent for marketing |
Unsubscribe Requirements Summary
| Law | Timing | Notes |
|---|---|---|
| CAN-SPAM | 10 business days | Must work 30 days after send |
| GDPR | Immediately | Must be as easy as opting in |
| CASL | 10 business days | Must work 60 days after send |
Universal best practices: Prominent link, one-click when possible, no login required, free, confirm action.
List-Unsubscribe Header (Required for Bulk Senders)
Gmail, Yahoo, and Microsoft require List-Unsubscribe headers. Without them, bulk emails may be rejected or spam-filtered.
Required headers:
headers: {
'List-Unsubscribe': '<https://example.com/unsubscribe>',
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
}Your unsubscribe endpoint must:
- Accept POST requests — return
200or202with a blank page - Display standard unsubscribe page for GET requests
- Stop sending within 48 hours of the request
Managing preferences vs Unsubscribe from all
Most legistlations require a one-click unsubscribe. Managing preferences is a nice-to-have and can lead to lower unsubscribe rate but doesn't replace Unsubscribe. If possible, offer both.
Consent Management
Record:
- Email address
- Date/time of consent
- Method (form, checkbox)
- What they consented to
- Source (which page/form)
Storage: Database with timestamps, audit trail of changes, link to user account.
Data Retention
| Law | Requirement |
|---|---|
| GDPR | Keep only as long as necessary, delete when no longer needed |
| CASL | Keep consent records 3 years after expiration |
Best practice: Have clear retention policy, honor deletion requests promptly, review and clean regularly.
Privacy Policy Must Include
- What data you collect
- How you use data
- Who you share data with
- User rights (access, deletion)
- How to contact about privacy
International Sending
Best practice: Follow the most restrictive requirements (usually GDPR) to ensure compliance across all regions.
Related
- Email Capture - Implement consent forms and double opt-in
- Marketing Emails - Consent and unsubscribe requirements
- List Management - Handle unsubscribes and deletion requests
Email Deliverability
Maximizing the chances that your emails are delivered successfully to the recipients.
Email Authentication
Required by Gmail/Yahoo/Microsoft - unauthenticated emails will be rejected or spam-filtered.
SPF (Sender Policy Framework)
Specifies which servers can send email for your domain.
v=spf1 include:amazonses.com ~all- Add TXT record to DNS
- Use
~all(soft fail)
DKIM (DomainKeys Identified Mail)
Cryptographic signature proving email authenticity.
- Your email service will provide you with a TXT record
DMARC
Policy for handling SPF/DKIM failures + reporting.
v=DMARC1; p=none; rua=mailto:dmarc@example.comRollout: p=none (monitor) → p=quarantine; pct=25 → p=reject
Learn more: https://resend.com/blog/dmarc-policy-modes
Verify Your Setup
Check DNS records directly:
# SPF record
dig TXT example.com +short
# DKIM record (replace 'resend' with your selector)
dig TXT resend._domainkey.example.com +short
# DMARC record
dig TXT _dmarc.example.com +shortExpected output: Each command should return your configured record. No output = record missing.
Sender Reputation
IP Warming
New IP/domain? Gradually increase volume:
| Week | Daily Volume |
|---|---|
| 1 | 50-100 |
| 2 | 200-500 |
| 3 | 1,000-2,000 |
| 4 | 5,000-10,000 |
Start with engaged users. Send consistently. Don't rush.
Learn more: https://resend.com/docs/knowledge-base/warming-up
Maintaining Reputation
Do: Send to engaged users, keep bounce <4%, complaints <0.1%, remove inactive subscribers.
Don't: Send to purchased lists, ignore bounces/complaints, send inconsistent volumes
Bounce Handling
| Type | Cause | Action |
|---|---|---|
| Hard bounce | Permanent failure to deliver | Remove immediately |
| Soft bounce | Transient failure to deliver | Retry: 1h → 4h → 24h, remove after 3-5 failures |
Targets: <1% good, 1-3% acceptable, 3-4% concerning, >4% critical
Complaint Handling
Targets: <0.01% excellent, 0.01-0.05% good, >0.05% critical
Reduce complaints:
- Only send to opted-in users
- Make unsubscribe easy and immediate
- Use clear sender names and "From" addresses
Feedback loops: Set up with Gmail (Postmaster Tools), Yahoo, Microsoft SNDS. Remove complainers immediately.
Infrastructure
Dedicated sending domain: Use different subdomains for different sending purposes (e.g., t.example.com for transactional emails and m.example.com for marketing emails).
DNS TTL: Low (300s) during setup, high (3600s+) after stable.
Troubleshooting
Emails going to spam? Check in order: 1. Authentication (SPF, DKIM, DMARC) 2. List-Unsubscribe header — required by Gmail/Yahoo since Feb 2024 (see Compliance) 3. Sender reputation (blacklists, complaint rates) 4. Content 5. Sending patterns (sudden volume spikes)
Diagnostic tools:
- Google Postmaster Tools - Domain reputation and spam rates
- mail-tester.com - Send a test email, get deliverability score
- MXToolbox - Check blacklist status
Related
- List Management - Handle bounces and complaints to protect reputation
- Sending Reliability - Retry logic and error handling
Email Capture Best Practices
Collecting email addresses responsibly with validation, verification, and proper consent.
Email Validation
Client-Side
HTML5:
<input type="email" required>Best practices:
- Validate on blur or with short debounce
- Show clear error messages
- Don't be too strict (allow unusual but valid formats)
- Client-side validation ≠ deliverability
Server-Side (Recommended)
Always validate server-side—client-side can be bypassed.
Check:
- Email format (RFC 5322)
- Domain exists (DNS lookup)
- Domain has MX records
- Optionally: disposable email detection
Recommended tools: https://resend.com/blog/best-email-verification-apis
Double opt-in
Confirms address belongs to user and is deliverable.
Process
1. User submits email 2. Send verification email with unique link/token 3. User clicks link 4. Mark as verified 5. Allow access/add to list
Timing: Send immediately, include expiration (24-48 hours), allow resend after 60 seconds, limit resend attempts (3/hour).
Single vs Double Opt-In
| Single Opt-In | Double Opt-In | |
|---|---|---|
| Process | Add to list immediately | Require email confirmation first |
| Pros | Lower friction, faster growth | Verified addresses, better engagement, meets GDPR/CASL |
| Cons | Higher invalid rate, lower engagement | Some users don't confirm |
| Use for | Account creation, transactional | Marketing lists, newsletters |
Recommendation: Double opt-in for all marketing emails.
Form Design
Email Input
- Use
type="email"for mobile keyboard - Include placeholder ("you@example.com")
- Clear error messages ("Please enter a valid email address" not "Invalid")
Consent Checkboxes (Marketing)
- Unchecked by default (required)
- Specific language about what they're signing up for
- Separate checkboxes for different email types
- Link to privacy policy
☐ Subscribe to our weekly newsletter with product updates
☐ Send me promotional offers and dealsDon't: Pre-check boxes, use vague language, hide in terms.
Form Layout
- Keep simple and focused
- One primary action
- Clear value proposition
- Mobile-friendly
- Accessible (labels, ARIA)
Error Handling
Invalid Email
- Show clear error message
- Suggest corrections for common typos (@gmial.com → @gmail.com)
- Allow user to fix and resubmit
Already Registered
- Accounts: "This email is already registered. [Sign in]"
- Marketing: "You're already subscribed! [Manage preferences]"
- Don't reveal if account exists (security)
Rate Limiting
- Limit verification emails (3/hour per email)
- Rate limit form submissions
- Use CAPTCHA sparingly if needed
- Monitor for abuse patterns
Verification Emails
Content:
- Clear purpose ("Verify your email address")
- Prominent verification button
- Expiration time
- Resend option
- "I didn't request this" notice
- Don't include OTP/2FA codes in subject line or preview text as it discourages opens
Design:
- Mobile-friendly
- Large, tappable button
- Clear call-to-action
See Transactional Emails for detailed email design guidance.
Related
- Compliance - Legal requirements for consent (GDPR, CASL)
- Marketing Emails - What happens after capture
- Deliverability - How validation improves sender reputation
Email Types: Transactional vs Marketing
Understanding the difference between transactional and marketing emails is crucial for compliance, deliverability, and user experience. This guide explains the distinctions and provides a catalog of transactional emails your app should include.
When to Use This
- Deciding whether an email should be transactional or marketing
- Understanding legal distinctions between email types
- Planning what transactional emails your app needs
- Ensuring compliance with email regulations
- Setting up separate sending infrastructure
Transactional vs Marketing: Key Differences
Transactional Emails
Definition: Emails that facilitate or confirm a transaction the user initiated or expects. They're directly related to an action the user took or are legal notices you're required to serve.
Characteristics:
- User-initiated or expected
- Time-sensitive and actionable
- Required for the user to complete an action
- Does not include promotional material or offers
- Can be sent without explicit opt-in (with limitations)
Examples:
- Password reset links
- Order confirmations
- Account verification
- OTP/2FA codes
- Shipping notifications
Analogy: Think of transactional emails for everything that would leave you with a paper receipt in the real world: invoices, parking ticket, booking confirmation, etc.
Marketing Emails
Definition: Emails sent for promotional, advertising, or informational purposes that are not directly related to a specific transaction or legal requirement.
Characteristics:
- Promotional or informational content
- Not time-sensitive to complete a transaction
- Require explicit opt-in (consent)
- Must include unsubscribe options
- Subject to stricter compliance requirements
Examples:
- Newsletters
- Abandoned cart
- Product announcements
- Promotional offers
- Company updates
- Educational content
Legal Distinctions
CAN-SPAM Act (US)
Transactional emails:
- Can be sent without opt-in
- Must be related to a transaction
- Cannot contain promotional content (with exceptions)
- Must identify sender and provide contact information
Marketing emails:
- Require opt-out mechanism (not opt-in in US)
- Must include clear sender identification
- Must include physical mailing address
- Must honor opt-out requests within 10 business days
GDPR (EU)
Transactional emails:
- Can be sent based on legitimate interest or contract fulfillment
- Must be necessary for service delivery
- Cannot contain marketing content without consent
Marketing emails:
- Require explicit opt-in consent
- Must clearly state purpose of data collection
- Must provide easy unsubscribe
- Subject to data protection requirements
CASL (Canada)
Transactional emails:
- Can be sent without consent if related to ongoing business relationship
- Must be factual and not promotional
Marketing emails:
- Require express or implied consent
- Must include unsubscribe mechanism
- Must identify sender clearly
When to Use Each Type
Use Transactional When:
- User needs the email to complete an action
- Email confirms a transaction or account change
- Email provides security-related information
- Email is expected based on user action
- Content is time-sensitive and actionable
- You're required to serve a notification for compliance
Use Marketing When:
- Promoting products or services
- Sending newsletters or updates
- Sharing educational content
- Announcing features or company news
- Content is not required for a transaction
Hybrid Emails: The Gray Area
Some emails mix transactional and marketing content. This isn't best practice and should be avoided.
Best practice: Keep transactional and marketing separate.
Example of problematic hybrid:
- Newsletter (marketing) with a small order status update (transactional)
Transactional Email Catalog
For a complete catalog of transactional emails and recommended combinations by app type, see Transactional Email Catalog.
Quick reference - Essential emails for most apps: 1. Email verification - Required for account creation 2. Password reset - Required for account recovery 3. Welcome email - Good user experience
The catalog includes detailed guidance for:
- Authentication-focused apps
- Newsletter / content platforms
- E-commerce / marketplaces
- SaaS / subscription services
- Financial / fintech apps
- Social / community platforms
- Developer tools / API platforms
- Healthcare / HIPAA-compliant apps
Sending Infrastructure
Separate subdomains
Best practice: Use separate sending subdomains for transactional and marketing emails.
Benefits:
- Protect transactional deliverability
- Different authentication domains
- Independent reputation
- Easier compliance management
Implementation:
- Use different subdomains (e.g.,
t.example.comfor transactional,m.example.comfor marketing)
Email Service Considerations
Choose an email service that:
- Provides reliable delivery for transactional emails
- Offers separate sending domains
- Has good API for programmatic sending
- Provides webhooks for delivery events
- Supports authentication setup (SPF, DKIM, DMARC)
Services like Resend are designed for transactional emails and provide the infrastructure and tools needed for reliable delivery. They also offer powerful marketing features.
Related Topics
- Transactional Emails - Best practices for sending transactional emails
- Marketing Emails - Best practices for marketing emails
- Compliance - Legal requirements for each email type
- Deliverability - Ensuring transactional emails are delivered
List Management
Maintaining clean email lists through suppression, hygiene, and data retention.
Suppression Lists
A suppression list prevents sending to addresses that should never receive email.
What to Suppress
| Reason | Action | Can Unsuppress? |
|---|---|---|
| Hard bounce | Add immediately | No (address invalid) |
| Complaint (spam) | Add immediately | No (legal requirement) |
| Soft bounce (3x) | Add after threshold | Yes, after 30-90 days |
| Manual removal | Add on request | Only if user requests |
Implementation
// Suppression list schema
interface SuppressionEntry {
email: string;
reason: 'hard_bounce' | 'complaint' | 'unsubscribe' | 'soft_bounce' | 'manual';
created_at: Date;
source_email_id?: string; // Which email triggered this
}
// Check before every send
async function canSendTo(email: string): Promise<boolean> {
const suppressed = await db.suppressions.findOne({ email });
return !suppressed;
}
// Add to suppression list
async function suppressEmail(email: string, reason: string, sourceId?: string) {
await db.suppressions.upsert({
email: email.toLowerCase(),
reason,
created_at: new Date(),
source_email_id: sourceId,
});
}Pre-Send Check
Always check suppression before sending:
async function sendEmail(to: string, emailData: EmailData) {
if (!await canSendTo(to)) {
console.log(`Skipping suppressed email: ${to}`);
return { skipped: true, reason: 'suppressed' };
}
return await resend.emails.send({ to, ...emailData });
}List Hygiene
Regular maintenance to keep lists healthy.
Automated Cleanup
| Task | Frequency | Action |
|---|---|---|
| Remove hard bounces | Real-time (via webhook) | Immediate suppression |
| Remove complaints | Real-time (via webhook) | Immediate suppression |
| Process unsubscribes | Real-time | Remove from marketing lists |
| Review soft bounces | Daily | Suppress after 3 failures |
| Remove inactive | Monthly | Re-engagement → remove |
Learn more: https://resend.com/docs/knowledge-base/audience-hygiene
Re-engagement Campaigns
Before removing inactive subscribers:
1. Identify inactive: No opens/clicks in 45-90 days 2. Send re-engagement: "We miss you" or "Still interested?" 3. Wait 14-30 days for response 4. Remove non-responders from active lists
async function runReengagement() {
const inactive = await getInactiveSubscribers(90); // 90 days
for (const subscriber of inactive) {
if (!subscriber.reengagement_sent) {
await sendReengagementEmail(subscriber);
await markReengagementSent(subscriber.email);
} else if (daysSince(subscriber.reengagement_sent) > 30) {
await removeFromMarketingLists(subscriber.email);
}
}
}Data Retention
Email Logs
| Data Type | Recommended Retention | Notes |
|---|---|---|
| Send attempts | 90 days | Debugging, analytics |
| Delivery status | 90 days | Compliance, reporting |
| Bounce/complaint events | 3 years | Required for CASL |
| Suppression list | Indefinite | Never delete |
| Email content | 30 days | Storage costs |
| Consent records | 3 years after expiry | Legal requirement |
Retention Policy Implementation
// Daily cleanup job
async function cleanupOldData() {
const now = new Date();
// Delete old email logs (keep 90 days)
await db.emailLogs.deleteMany({
created_at: { $lt: subDays(now, 90) }
});
// Delete old email content (keep 30 days)
await db.emailContent.deleteMany({
created_at: { $lt: subDays(now, 30) }
});
// Never delete: suppressions, consent records
}Metrics to Monitor
| Metric | Target | Alert Threshold |
|---|---|---|
| Bounce rate | <2% | >2% |
| Complaint rate | <0.05% | >0.05% |
| Suppression list growth | Stable | Sudden spike |
Transactional vs Marketing Lists
Keep separate:
- Transactional: Can send to anyone with account relationship
- Marketing: Only opted-in subscribers
Suppression applies to both: Hard bounces and complaints suppress across all email types.
Unsubscribe is marketing-only: User unsubscribing from marketing can still receive transactional emails (password resets, order confirmations).
Related
- Webhooks & Events - Receive bounce/complaint notifications
- Deliverability - How list hygiene affects sender reputation
- Compliance - Legal requirements for data retention
Marketing Email Best Practices
Promotional emails that require explicit consent and provide value to recipients.
Core Principles
1. Consent first - Explicit opt-in required (especially GDPR/CASL) 2. Value-driven - Provide useful content, not just promotions 3. Respect preferences - Let users control frequency and content types
Opt-In Requirements
Explicit Opt-In
What counts:
- User checks unchecked box
- User clicks "Subscribe" button
- User completes form with clear subscription intent
What doesn't count:
- Pre-checked boxes
- Opt-out model
- Assumed consent from purchase
- Purchased/rented lists
Informed Consent
Disclose: email types, frequency, sender identity, how to unsubscribe.
✅ "Subscribe to our weekly newsletter with product updates and tips" ❌ "Sign up for emails"
Double Opt-In (Recommended)
1. User submits email 2. Send confirmation email with verification link 3. User clicks to confirm 4. Add to list only after confirmation
Benefits: Verifies deliverability, confirms intent, reduces complaints, required in some regions (Germany).
Unsubscribe Requirements
Must be:
- Prominent in every email
- One-click (preferred)
- Immediate (GDPR) or within 10 days (CAN-SPAM) (immediate preferred)
- Free, no login required
Preference center options: Frequency (daily/weekly/monthly), content types, complete unsubscribe.
Content and Design
Subject Lines
- Clear and specific (50 chars or less for mobile)
- Create curiosity without misleading
- A/B test regularly
✅ "Your weekly digest: 5 productivity tips" ❌ "You won't believe what happened!"
Structure
Above fold: Value proposition, primary CTA, engaging visual
Body: Scannable (short paragraphs, bullets), clear hierarchy, multiple CTAs
Footer: Unsubscribe link, company info, physical address (CAN-SPAM), social links
Mobile-First
- Single column layout
- 44x44px minimum buttons
- 16px minimum text
- Test on iOS, Android, dark mode
Segmentation
Segment by: Behavior (purchases, activity), demographics, preferences, engagement level, signup source.
Benefits: Higher open/click rates, lower unsubscribes, better experience.
Personalization
Options: Name in subject/greeting, location-specific content, behavior-based recommendations, purchase history.
Don't over-personalize - can feel intrusive. Use data you have permission to use.
Frequency and Timing
Frequency: Start conservative, increase based on engagement, let users set preferences, monitor unsubscribe rates.
Timing: Weekday mornings (9-11 AM local), Tuesday-Thursday often best. Test your specific audience.
List Hygiene
Remove immediately: Hard bounces, unsubscribes, complaints
Remove after inactivity: Send re-engagement campaign first, then remove non-responders
Monitor: Bounce rate <2%, complaint rate <0.05%
Required Elements (All Marketing Emails)
- Clear sender identification
- Physical mailing address (CAN-SPAM)
- Unsubscribe mechanism
- Indication it's marketing (GDPR)
Related
- Compliance - Detailed legal requirements by region
- Email Capture - Collecting consent properly
- List Management - Maintaining list hygiene
Sending Reliability
Ensuring emails are sent exactly once and handling failures gracefully.
Idempotency
Prevent duplicate emails when retrying failed requests.
The Problem
Network issues, timeouts, or server errors can leave you uncertain if an email was sent. Retrying without idempotency risks sending duplicates.
Solution: Idempotency Keys
Send a unique key with each request. If the same key is sent again, the server returns the original response instead of sending another email.
// Generate deterministic key based on the business event
const idempotencyKey = `password-reset-${userId}-${resetRequestId}`;
await resend.emails.send({
from: 'noreply@example.com',
to: user.email,
subject: 'Reset your password',
html: emailHtml,
}, {
headers: {
'Idempotency-Key': idempotencyKey
}
});Key Generation Strategies
| Strategy | Example | Use When |
|---|---|---|
| Event-based | order-confirm-${orderId} | One email per event (recommended) |
| Request-scoped | reset-${userId}-${resetRequestId} | Retries within same request |
| UUID | crypto.randomUUID() | No natural key (generate once, reuse on retry) |
Best practice: Use deterministic keys based on the business event. If you retry the same logical send, the same key must be generated. Avoid Date.now() or random values generated fresh on each attempt.
Key expiration: Idempotency keys are typically cached for 24 hours. Retries within this window return the original response. After expiration, the same key triggers a new send—so complete your retry logic well within 24 hours.
Retry Logic
Handle transient failures with exponential backoff.
When to Retry
| Error Type | Retry? | Notes |
|---|---|---|
| 5xx (server error) | ✅ Yes | Transient, likely to resolve |
| 429 (rate limit) | ✅ Yes | Wait for rate limit window |
| 4xx (client error) | ❌ No | Fix the request first |
| Network timeout | ✅ Yes | Transient |
| DNS failure | ✅ Yes | May be transient |
Exponential Backoff
async function sendWithRetry(emailData, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await resend.emails.send(emailData);
} catch (error) {
if (!isRetryable(error) || attempt === maxRetries - 1) {
throw error;
}
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
await sleep(delay + Math.random() * 1000); // Add jitter
}
}
}
function isRetryable(error) {
return error.statusCode >= 500 ||
error.statusCode === 429 ||
error.code === 'ETIMEDOUT';
}Backoff schedule: 1s → 2s → 4s → 8s (with jitter to prevent thundering herd)
Error Handling
Common Error Codes
| Code | Meaning | Action |
|---|---|---|
| 400 | Bad request | Fix payload (invalid email, missing field) |
| 401 | Unauthorized | Check API key |
| 403 | Forbidden | Check permissions, domain verification |
| 404 | Not found | Check endpoint URL |
| 422 | Validation error | Fix request data |
| 429 | Rate limited | Back off, retry after delay |
| 500 | Server error | Retry with backoff |
| 503 | Service unavailable | Retry with backoff |
Error Handling Pattern
try {
const result = await resend.emails.send(emailData);
await logSuccess(result.id, emailData);
} catch (error) {
if (error.statusCode === 429) {
await queueForRetry(emailData, error.retryAfter);
} else if (error.statusCode >= 500) {
await queueForRetry(emailData);
} else {
await logFailure(error, emailData);
await alertOnCriticalEmail(emailData); // For password resets, etc.
}
}Queuing for Reliability
For critical emails, use a queue to ensure delivery even if the initial send fails.
Benefits:
- Survives application restarts
- Automatic retry handling
- Rate limit management
- Audit trail
Simple pattern: 1. Write email to queue/database with "pending" status 2. Process queue, attempt send 3. On success: mark "sent", store message ID 4. On retryable failure: increment retry count, schedule retry 5. On permanent failure: mark "failed", alert
Timeouts
Set appropriate timeouts to avoid hanging requests.
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
try {
await resend.emails.send(emailData, { signal: controller.signal });
} finally {
clearTimeout(timeout);
}Recommended: 10-30 seconds for email API calls.
Related
- Webhooks & Events - Process delivery confirmations and failures
- List Management - Handle bounces and suppress invalid addresses
Transactional Email Catalog
A comprehensive catalog of transactional emails organized by category, plus recommended email combinations for different app types.
When to Use This
- Planning what transactional emails your app needs
- Choosing the right emails for your app type
- Understanding what content each email type should include
- Implementing transactional email features
Email Combinations by App Type
Use these combinations as a starting point based on what you're building.
Authentication-Focused App
Apps where user accounts and security are core (login systems, identity providers, account management).
Essential:
- Email verification
- Password reset
- OTP / 2FA codes
- Security alerts (new device, password change)
- Account update notifications
Optional:
- Welcome email (must not be promotional)
- Account deletion confirmation
Newsletter / Content Platform
Apps focused on content delivery and subscriptions.
Essential:
- Email verification
- Password reset
- Welcome email (must not be promotional)
- Subscription confirmation
Optional:
- OTP / 2FA codes
- Account update notifications
E-commerce / Marketplace
Apps where users buy products or services.
Essential:
- Email verification
- Password reset
- Welcome email (must not be promotional)
- Order confirmation
- Shipping notifications
- Invoice / receipt
- Payment failed notices
Optional:
- OTP / 2FA codes
- Security alerts
- Subscription confirmations (for recurring orders)
SaaS / Subscription Service
Apps with paid subscription tiers and ongoing billing.
Essential:
- Email verification
- Password reset
- Welcome email (must not be promotional)
- OTP / 2FA codes
- Security alerts
- Subscription confirmation
- Subscription renewal notice
- Payment failed notices
- Invoice / receipt
Optional:
- Account update notifications
- Feature change notifications (for breaking changes)
Financial / Fintech App
Apps handling money, payments, or sensitive financial data.
Essential:
- Email verification
- Password reset
- OTP / 2FA codes (required for sensitive actions)
- Security alerts (all types)
- Account update notifications
- Transaction confirmations
- Invoice / receipt
- Payment failed notices
Optional:
- Welcome email (must not be promotional)
- Compliance notices
Social / Community Platform
Apps focused on user interaction and community features.
Essential:
- Email verification
- Password reset
- Welcome email (must not be promotional)
- Security alerts
Optional:
- OTP / 2FA codes
- Account update notifications
- Activity notifications (mentions, replies)
Developer Tools / API Platform
Apps targeting developers with API access and integrations.
Essential:
- Email verification
- Password reset
- OTP / 2FA codes
- Security alerts
- API key notifications (creation, expiration)
- Subscription confirmation
- Payment failed notices
Optional:
- Welcome email (must not be promotional)
- Usage alerts (approaching limits)
- Feature change notifications
Healthcare / HIPAA-Compliant App
Apps handling protected health information.
Essential:
- Email verification
- Password reset
- OTP / 2FA codes (required)
- Security alerts (all types, detailed)
- Account update notifications
- Appointment confirmations
Optional:
- Welcome email (must not be promotional)
- Compliance notices
Note: Healthcare apps have strict requirements. Emails should contain minimal PHI and link to secure portals for sensitive information.
---
Full Email Catalog
Authentication & Security
Email Verification / Account Verification
When to send: Immediately after user signs up or changes email address.
Purpose: Verify the email address belongs to the user.
Content should include:
- Clear verification link or code
- Expiration time (typically 24-48 hours)
- Instructions on what to do
- Security notice if link is clicked by mistake
Best practices:
- Send immediately (within seconds)
- Include expiration notice
- Provide resend option
- Link to support if issues
OTP / 2FA Codes
When to send: When user requests two-factor authentication code.
Purpose: Provide time-sensitive authentication code.
Content should include:
- The OTP code (clearly displayed)
- Expiration time (typically 5-10 minutes)
- Security warnings
- Instructions on what to do if not requested
Best practices:
- Send immediately
- Code should be large and easy to read
- Include expiration prominently
- Warn about sharing codes
- Provide "I didn't request this" link
Password Reset
When to send: When user requests password reset.
Purpose: Allow user to securely reset forgotten password.
Content should include:
- Reset link (with token)
- Expiration time (typically 1 hour)
- Security warnings
- Instructions if not requested
Best practices:
- Send immediately
- Link expires quickly (1 hour)
- Include IP address and location if available
- Provide "I didn't request this" link
- Don't include the old password
Security Alerts
When to send: When security-relevant events occur (login from new device, password change, etc.).
Purpose: Notify user of account security events.
Content should include:
- What happened (clear description)
- When it happened
- Location/IP if available
- Action to take if suspicious
- Link to security settings
Best practices:
- Send immediately
- Be clear and specific
- Include actionable steps
- Provide way to report suspicious activity
Account Management
Welcome Email
When to send: Immediately after successful account creation and verification.
Purpose: Welcome new users and guide them to next steps (must not be promotional).
Content should include:
- Welcome message
- Key features or next steps
- Links to important resources
- Support contact information
Best practices:
- Send after email verification
- Keep it focused and actionable
- Don't overwhelm with information
- Set expectations about future emails
Account Update Notifications
When to send: When user changes account settings (email, password, profile, etc.).
Purpose: Confirm account changes and provide security notice.
Content should include:
- What changed
- When it changed
- Action to take if unauthorized
- Link to account settings
Best practices:
- Send immediately after change
- Be specific about what changed
- Include security notice
- Provide easy way to revert if needed
E-commerce & Transactions
Order Confirmations
When to send: Immediately after order is placed.
Purpose: Confirm order details and provide receipt.
Content should include:
- Order number
- Items ordered with quantities
- Pricing breakdown
- Shipping address
- Estimated delivery date
- Order tracking link (if available)
Best practices:
- Send within minutes of order
- Include all order details
- Make it easy to print or save
- Provide customer service contact
Shipping Notifications
When to send: When order ships, with tracking updates.
Purpose: Notify user that order has shipped and provide tracking.
Content should include:
- Order number
- Tracking number
- Carrier information
- Expected delivery date
- Tracking link
- Shipping address confirmation
Best practices:
- Send when order ships
- Include tracking number prominently
- Provide carrier tracking link
- Update on major tracking milestones
Invoices and Receipts
When to send: After payment is processed.
Purpose: Provide payment confirmation and receipt.
Content should include:
- Invoice/receipt number
- Payment amount
- Payment method
- Items/services purchased
- Payment date
- Downloadable PDF (if applicable)
Best practices:
- Send immediately after payment
- Include all payment details
- Make it easy to download/save
- Include tax information if applicable
Subscriptions & Billing
Subscription Confirmations
When to send: When user subscribes or changes subscription.
Purpose: Confirm subscription details and billing information.
Content should include:
- Subscription plan details
- Billing amount and frequency
- Next billing date
- Payment method
- Link to manage subscription
Best practices:
- Send immediately after subscription
- Clearly state billing terms
- Provide easy cancellation option
- Include support contact
Subscription Renewal Notices
When to send: Before subscription renews (typically 3-7 days before).
Purpose: Notify user of upcoming renewal and charge.
Content should include:
- Renewal date
- Amount to be charged
- Payment method on file
- Link to update payment method
- Link to cancel if desired
Best practices:
- Send with enough notice (3-7 days)
- Be clear about amount and date
- Make it easy to update payment method
- Provide cancellation option
Payment Failed Notices
When to send: When subscription payment fails.
Purpose: Notify user of payment failure and provide resolution steps.
Content should include:
- What happened
- Amount that failed
- Reason for failure (if available)
- Steps to resolve
- Link to update payment method
- Consequences if not resolved
Best practices:
- Send immediately after failure
- Be clear about consequences
- Provide easy resolution path
- Include support contact
Notifications & Updates
Feature Announcements (Transactional)
When to send: When a feature the user is using changes significantly.
Purpose: Notify users of changes that affect their use of the service.
Content should include:
- What changed
- How it affects the user
- What action (if any) is needed
- Link to more information
Best practices:
- Only for significant changes
- Focus on user impact
- Provide clear next steps
- Link to documentation
Note: General feature announcements are marketing emails. Only send as transactional if the change directly affects an active feature the user is using.
Related Topics
- Email Types - Understanding transactional vs marketing
- Transactional Emails - Best practices for sending transactional emails
- Compliance - Legal requirements for each email type
Transactional Email Best Practices
Clear, actionable emails that users expect and need—password resets, confirmations, OTPs.
Core Principles
1. Clarity over creativity - Users need to understand and act quickly 2. Action-oriented - Clear purpose, obvious primary action 3. Time-sensitive - Send immediately (within seconds)
Subject Lines
Be specific and include context:
| ✅ Good | ❌ Bad |
|---|---|
| Reset your password for [App] | Action required |
| Your order #12345 has shipped | Update on your order |
| Your 2FA code for [App] | Security code: 12345 |
| Verify your email for [App] | Verify your email |
Include identifiers when helpful: order numbers, account names, expiration times.
Pre-Header
The text snippet after subject line. Use it to:
- Reinforce subject ("This link expires in 1 hour")
- Add urgency or context
- Call-to-action preview
Keep under 90 characters.
Content Structure
Above the fold (first screen):
- Clear purpose
- Primary action button
- Time-sensitive details (expiration)
Hierarchy: Header → Primary message → Details → Action button → Secondary info
Format: Short paragraphs (2-3 sentences), bullet points, bold for emphasis, white space.
Mobile-First Design
60%+ emails are opened on mobile.
- Layout: Single column, stack vertically
- Buttons: 44x44px minimum, full-width on mobile
- Text: 16px minimum body, 20-24px headings
- OTP codes: 24-32px, monospace font
Sender Configuration
| Field | Best Practice | Example |
|---|---|---|
| From Name | App/company name, consistent | [App Name] |
| From Email | Subdomain, real address | hello@mail.example.com |
| Reply-To | Monitored inbox | support@example.com |
Avoid noreply@ - users reply to transactional emails.
Code and Link Display
OTP/Verification codes:
- Large (24-32px), monospace font
- Centered, clear label
- Include expiration nearby
- Make copyable
Buttons:
- Large, tappable (44x44px+)
- Contrasting colors
- Clear action text ("Reset Password", "Verify Email")
- HTTPS links only
Error Handling
Resend functionality:
- Allow after 60 seconds
- Limit attempts (3 per hour)
- Show countdown timer
Expired links:
- Clear "expired" message
- Offer to send new link
- Provide support contact
"I didn't request this":
- Include in password resets, OTPs, security alerts
- Link to security contact
- Log clicks for monitoring
Webhooks and Events
Receiving and processing email delivery events in real-time.
Event Types
| Event | When Fired | Use For |
|---|---|---|
email.sent | Email accepted by Resend | Confirming send initiated |
email.delivered | Email delivered to recipient server | Confirming delivery |
email.bounced | Email bounced (hard or soft) | List hygiene, alerting |
email.complained | Recipient marked as spam | Immediate unsubscribe |
email.opened | Recipient opened email | Engagement tracking |
email.clicked | Recipient clicked link | Engagement tracking |
Webhook Setup
1. Create Endpoint
Your endpoint must:
- Accept POST requests
- Return 2xx status quickly (within 5 seconds)
- Handle duplicate events (idempotent processing)
app.post('/webhooks/resend', async (req, res) => {
// Return 200 immediately to acknowledge receipt
res.status(200).send('OK');
// Process asynchronously
processWebhookAsync(req.body).catch(console.error);
});2. Verify Signatures
Always verify webhook signatures to prevent spoofing.
import { Webhook } from 'svix';
const webhook = new Webhook(process.env.RESEND_WEBHOOK_SECRET);
app.post('/webhooks/resend', (req, res) => {
try {
const payload = webhook.verify(
JSON.stringify(req.body),
{
'svix-id': req.headers['svix-id'],
'svix-timestamp': req.headers['svix-timestamp'],
'svix-signature': req.headers['svix-signature'],
}
);
// Process verified payload
} catch (err) {
return res.status(400).send('Invalid signature');
}
});3. Register Webhook URL
Configure your webhook endpoint in the Resend dashboard or via API.
Processing Events
Bounce Handling
async function handleBounce(event) {
const { email_id, email, bounce_type } = event.data;
if (bounce_type === 'hard') {
// Permanent failure - remove from all lists
await suppressEmail(email, 'hard_bounce');
await removeFromAllLists(email);
} else {
// Soft bounce - track and remove after threshold
await incrementSoftBounce(email);
const count = await getSoftBounceCount(email);
if (count >= 3) {
await suppressEmail(email, 'soft_bounce_limit');
}
}
}Complaint Handling
async function handleComplaint(event) {
const { email } = event.data;
// Immediate suppression - no exceptions
await suppressEmail(email, 'complaint');
await removeFromAllLists(email);
await logComplaint(event); // For analysis
}Delivery Confirmation
async function handleDelivered(event) {
const { email_id } = event.data;
await updateEmailStatus(email_id, 'delivered');
}Idempotent Processing
Webhooks may be sent multiple times. Use event IDs to prevent duplicate processing.
async function processWebhook(event) {
const eventId = event.id;
// Check if already processed
if (await isEventProcessed(eventId)) {
return; // Skip duplicate
}
// Process event
await handleEvent(event);
// Mark as processed
await markEventProcessed(eventId);
}Error Handling
Retry Behavior
If your endpoint returns non-2xx, webhooks will retry with exponential backoff:
- Retry 1: ~30 seconds
- Retry 2: ~1 minute
- Retry 3: ~5 minutes
- (continues for ~24 hours)
Best Practices
- Return 200 quickly - Process asynchronously to avoid timeouts
- Be idempotent - Handle duplicate deliveries gracefully
- Log everything - Store raw events for debugging
- Alert on failures - Monitor webhook processing errors
- Queue for processing - Use a job queue for complex handling
Testing Webhooks
Local development: Use ngrok or similar to expose localhost.
ngrok http 3000
# Use the ngrok URL as your webhook endpointVerify handling: Send test events through Resend dashboard or manually trigger each event type.
Ingest webhooks for data storage
Related
- List Management - What to do with bounce/complaint data
- Sending Reliability - Retry logic when sends fail
Skill Tests
Tests for the email-best-practices skill using the RED-GREEN methodology from writing-skills.
Skill Type
This is a reference/technique skill (not discipline-enforcing), so tests focus on:
- Retrieval — Does the agent find the right resource for a given problem?
- Application — Does the agent produce correct, specific guidance?
- Gap — Are common email scenarios adequately covered?
Test Scenarios
| # | Scenario | Tests | Key Resource(s) |
|---|---|---|---|
| 1 | Emails going to spam | Retrieval + Application | deliverability.md |
| 2 | Multi-region compliance | Retrieval + Application | compliance.md |
| 3 | Retry logic + idempotency | Application | sending-reliability.md |
| 4 | Webhook bounce/complaint handling | Application | webhooks-events.md, list-management.md |
| 5 | New SaaS email infrastructure | Retrieval + Application + Gap | All resources |
Running Tests
Each scenario in scenarios/ is a self-contained prompt. Run with a subagent:
RED (baseline): Run the prompt as-is — no skill loaded. The agent uses only general knowledge.
GREEN (with skill): Prepend: You have access to an email best practices skill with resources in <path>. Read SKILL.md first, then read the relevant resource file(s).
Compare outputs against results.md for expected correctness criteria.
Results
See results.md for the full RED vs GREEN comparison and gap analysis from the initial test campaign.
Scenario 1: Emails Going to Spam
Prompt
You are an AI coding assistant. A developer asks you:
"My transactional emails (password resets, order confirmations) are going to spam in Gmail. What do I do to fix this?"
Answer with specific, actionable steps. Include exact DNS records, commands to verify, and threshold numbers where relevant.
Format your response as a numbered action plan.Expected Correctness Criteria
The agent MUST include these skill-specific details:
Authentication (deliverability.md)
- [ ] SPF record example:
v=spf1 include:amazonses.com ~all - [ ] DKIM: provider supplies the record
- [ ] DMARC:
v=DMARC1; p=none; rua=mailto:dmarc@example.com - [ ] DMARC rollout:
p=none→p=quarantine; pct=25→p=reject - [ ] Verification commands:
dig TXT example.com +short,dig TXT resend._domainkey.example.com +short,dig TXT _dmarc.example.com +short
Thresholds (deliverability.md)
- [ ] Bounce targets: <1% good, 1-3% acceptable, 3-4% concerning, >4% critical
- [ ] Complaint targets: <0.01% excellent, 0.01-0.05% good, >0.05% critical
IP Warming (deliverability.md)
- [ ] Week 1: 50-100/day
- [ ] Week 2: 200-500/day
- [ ] Week 3: 1,000-2,000/day
- [ ] Week 4: 5,000-10,000/day
Infrastructure (deliverability.md)
- [ ] Dedicated subdomains:
t.example.com(transactional),m.example.com(marketing) - [ ] DNS TTL: 300s during setup, 3600s+ after stable
Troubleshooting order (deliverability.md)
- [ ] Check in order: 1. Authentication, 2. List-Unsubscribe header, 3. Reputation, 4. Content, 5. Sending patterns
Diagnostic tools (deliverability.md)
- [ ] Google Postmaster Tools
- [ ] mail-tester.com
- [ ] MXToolbox blacklist check
Scenario 2: Multi-Region Email Compliance
Prompt
You are an AI coding assistant. A developer asks you:
"I'm building an email newsletter for my SaaS product. I have users in the US, EU, and Canada. What legal requirements do I need to follow? Give me a comparison table of requirements by region and the specific implementation steps."
Be specific about penalty amounts, timing requirements for unsubscribe processing, and consent record requirements.Expected Correctness Criteria
Penalties (compliance.md)
- [ ] CAN-SPAM: $53k/email
- [ ] GDPR: EUR 20M or 4% revenue
- [ ] CASL: $1M (individual) to $10M (organization) CAD
Consent types (compliance.md)
- [ ] CAN-SPAM: opt-out model (can send without opt-in)
- [ ] GDPR: explicit opt-in (no pre-checked boxes)
- [ ] CASL express: explicit opt-in
- [ ] CASL implied: existing relationship (2 years) or inquiry (6 months)
Unsubscribe timing (compliance.md)
- [ ] CAN-SPAM: 10 business days, must work 30 days after send
- [ ] GDPR: immediately, as easy as opting in
- [ ] CASL: 10 business days, must work 60 days after send
CASL specifics (compliance.md)
- [ ] Sender identification valid 60 days after send
- [ ] Keep consent records 3 years after expiration
Consent records (compliance.md)
- [ ] Record: email, date/time, method, what consented to, source
International sending (compliance.md)
- [ ] Best practice: follow GDPR (most restrictive) for all regions
Managing preferences vs unsubscribe (compliance.md)
- [ ] One-click unsubscribe required; preference management is nice-to-have, doesn't replace unsubscribe
List-Unsubscribe header (compliance.md)
- [ ] Required by Gmail/Yahoo since Feb 2024
- [ ] Headers:
List-UnsubscribeURL +List-Unsubscribe-Post: List-Unsubscribe=One-Click - [ ] Endpoint: POST returns 200/202, GET shows unsubscribe page
- [ ] Stop sending within 48 hours
Scenario 3: Retry Logic + Idempotency
Prompt
You are an AI coding assistant. A developer asks you:
"I'm using the Resend API to send transactional emails. I need to implement retry logic with idempotency to prevent duplicate sends. Show me a TypeScript implementation with idempotency keys, exponential backoff, and proper error code handling."
Include specific HTTP error codes and which ones to retry vs not retry, idempotency key generation strategies, and backoff timing.Expected Correctness Criteria
Idempotency keys (sending-reliability.md)
- [ ] Event-based key example:
order-confirm-${orderId}(recommended) - [ ] Request-scoped example:
reset-${userId}-${resetRequestId} - [ ] UUID fallback:
crypto.randomUUID()— generate once, reuse on retry - [ ] Warns against
Date.now()or random values generated fresh on each attempt - [ ] Key expiration: 24 hours — complete retry logic within this window
Error codes (sending-reliability.md)
- [ ] Retry: 5xx (server error), 429 (rate limit), network timeout, DNS failure
- [ ] Do NOT retry: 400 (bad request), 401 (unauthorized), 403 (forbidden), 404 (not found), 422 (validation)
Backoff (sending-reliability.md)
- [ ] Exponential: 1s -> 2s -> 4s -> 8s
- [ ] Cap at 30 seconds
- [ ] Jitter to prevent thundering herd
- [ ] Max retries: 3
Timeout (sending-reliability.md)
- [ ] AbortController pattern with 10-30 second timeout
Queuing (sending-reliability.md)
- [ ] Queue pattern for critical emails: write pending -> attempt send -> mark sent/schedule retry -> mark failed + alert
Scenario 4: Webhook Bounce/Complaint Handling
Prompt
You are an AI coding assistant. A developer asks you:
"I need to set up Resend webhooks to handle bounces and complaints. Show me how to implement this with signature verification using svix, idempotent event processing, and proper bounce/complaint handling (when to suppress, when to retry). Include TypeScript code."
Be specific about: svix verification headers, hard vs soft bounce handling thresholds, and complaint handling requirements.Expected Correctness Criteria
Webhook setup (webhooks-events.md)
- [ ] Endpoint must return 2xx within 5 seconds
- [ ] Return 200 immediately, process asynchronously
Svix verification (webhooks-events.md)
- [ ] Import from 'svix'
- [ ] Headers:
svix-id,svix-timestamp,svix-signature - [ ] Verify before processing, return 400 on invalid signature
Idempotent processing (webhooks-events.md)
- [ ] Use event ID to deduplicate
- [ ] Check if already processed before handling
- [ ] Mark as processed after handling
Event types (webhooks-events.md)
- [ ]
email.sent,email.delivered,email.bounced,email.complained,email.opened,email.clicked
Bounce handling (webhooks-events.md + list-management.md)
- [ ] Hard bounce: suppress immediately, remove from all lists
- [ ] Soft bounce: track count, suppress after 3 failures
- [ ] Suppression entry schema includes: email, reason, created_at, source_email_id
Complaint handling (webhooks-events.md + list-management.md)
- [ ] Immediate suppression — no exceptions
- [ ] Remove from all lists
- [ ] Log for analysis
Suppression unsuppress rules (list-management.md)
- [ ] Hard bounce: cannot unsuppress (address invalid)
- [ ] Complaint: cannot unsuppress (legal requirement)
- [ ] Soft bounce (3x): can unsuppress after 30-90 days
- [ ] Manual removal: only if user requests
Pre-send check (list-management.md)
- [ ] Always check suppression before sending
Retry behavior (webhooks-events.md)
- [ ] Non-2xx triggers retries: ~30s -> ~1min -> ~5min (continues ~24 hours)
Scenario 5: New SaaS Email Infrastructure Plan
Prompt
You are an AI coding assistant. A developer asks you:
"I'm building a new SaaS app and need to plan my entire email infrastructure. I need to know: (1) what types of transactional emails I should plan for, (2) how to set up DNS authentication, (3) how to warm up my sending domain, (4) how to handle bounces/complaints in production, and (5) what compliance requirements I need for international users. Give me a comprehensive implementation roadmap."
Be specific about: IP warming schedules (daily volumes by week), bounce rate thresholds, complaint rate thresholds, DNS record formats, and legal requirements by region.Expected Correctness Criteria
Email planning (transactional-email-catalog.md)
- [ ] References the transactional email catalog for SaaS planning
- [ ] Covers at minimum: verification, password reset, OTP/2FA, security alerts, billing
DNS authentication (deliverability.md)
- [ ] SPF, DKIM, DMARC records with examples
- [ ] DMARC rollout strategy (none -> quarantine; pct=25 -> reject)
- [ ] Dedicated subdomains for transactional vs marketing
Warming (deliverability.md)
- [ ] Correct weekly schedule: 50-100 / 200-500 / 1k-2k / 5k-10k
- [ ] Start with engaged users, send consistently
Bounce/complaint handling (deliverability.md + list-management.md + webhooks-events.md)
- [ ] Bounce thresholds: <1% good, >4% critical
- [ ] Complaint thresholds: <0.01% excellent, >0.05% critical
- [ ] Hard bounce: immediate suppression
- [ ] Soft bounce: suppress after 3 failures
- [ ] Complaint: immediate suppression
- [ ] Pre-send suppression check
Compliance (compliance.md)
- [ ] Covers CAN-SPAM, GDPR, CASL
- [ ] Correct penalty amounts
- [ ] Correct unsubscribe timing by region
- [ ] Recommends GDPR as global standard
Data retention (list-management.md)
- [ ] Send attempts: 90 days
- [ ] Bounce/complaint events: 3 years
- [ ] Suppression list: indefinite
- [ ] Email content: 30 days
- [ ] Consent records: 3 years after expiry
Cross-resource synthesis
- [ ] Agent references multiple resource files (not just one)
- [ ] "Start Here" routing from SKILL.md is followed
Related skills
Forks & variants (2)
Email Best Practices has 2 known copies in the catalog totaling 6.4k installs. They canonicalize to this original listing.
How it compares
Pick email-best-practices over generic copywriting skills when deliverability, DNS authentication, and compliance matter as much as message text.
FAQ
What does email-best-practices do?
Use when building email features, emails going to spam, high bounce rates, setting up SPF/DKIM/DMARC authentication, implementing email capture, ensuring compliance (CAN-SPAM, GDPR, CASL), handling webhooks, retry logic,
When should I invoke email-best-practices?
Use when building email features, emails going to spam, high bounce rates, setting up SPF/DKIM/DMARC authentication, implementing email capture, ensuring compliance (CAN-SPAM, GDPR, CASL), handling webhooks, retry logic,
Where is the source documentation?
Ground claims in SKILL.md excerpts and linked reference files from the cached docs.
Is Email Best Practices safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.