email skill

This commit is contained in:
2026-01-24 20:22:16 +00:00
parent 4e602e1783
commit 566557f8b0
11 changed files with 1685 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
# 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/implied consent | $10M 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 (preferred)
- **Implied consent:** Existing business relationship (2 years) or inquiry (6 months)
**Requirements:**
- Clear sender identification
- Unsubscribe functional for 60 days after send
- Process unsubscribe within 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.
## 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](./email-capture.md) - Implement consent forms and double opt-in
- [Marketing Emails](./marketing-emails.md) - Consent and unsubscribe requirements
- [List Management](./list-management.md) - Handle unsubscribes and deletion requests

View File

@@ -0,0 +1,120 @@
# Email Deliverability
Ensuring emails reach inboxes through proper authentication and sender reputation.
## Email Authentication
**Required by Gmail/Yahoo** - unauthenticated emails will be rejected or spam-filtered.
### SPF (Sender Policy Framework)
Specifies which servers can send email for your domain.
```
v=spf1 include:_spf.resend.com ~all
```
- Add TXT record to DNS
- Use `~all` (soft fail) for testing, `-all` (hard fail) for production
- Keep under 10 DNS lookups
### DKIM (DomainKeys Identified Mail)
Cryptographic signature proving email authenticity.
- Generate keys (provided by email service)
- Add public key as TXT record in DNS
- Use 2048-bit keys, rotate every 6-12 months
### DMARC
Policy for handling SPF/DKIM failures + reporting.
```
v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com
```
**Rollout:** `p=none` (monitor) → `p=quarantine; pct=25``p=reject`
### BIMI (Optional)
Display brand logo in email clients. Requires DMARC `p=quarantine` or `p=reject`.
### Verify Your Setup
Check DNS records directly:
```bash
# SPF record
dig TXT yourdomain.com +short
# DKIM record (replace 'resend' with your selector)
dig TXT resend._domainkey.yourdomain.com +short
# DMARC record
dig TXT _dmarc.yourdomain.com +short
```
**Expected 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.
### Maintaining Reputation
**Do:** Send to engaged users, keep bounce <2%, 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 | Invalid email, domain doesn't exist | Remove immediately |
| Soft bounce | Mailbox full, server down | Retry: 1h → 4h → 24h, remove after 3-5 failures |
**Targets:** <2% good, 2-5% acceptable, >5% concerning, >10% critical
## Complaint Handling
**Targets:** <0.05% excellent, 0.05-0.1% good, >0.2% 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, AOL. Remove complainers immediately.
## Infrastructure
**Dedicated sending domain:** Use subdomain (e.g., `mail.yourdomain.com`) to protect main domain reputation.
**DNS TTL:** Low (300s) during setup, high (3600s+) after stable.
## Troubleshooting
**Emails going to spam?** Check in order:
1. Authentication (SPF, DKIM, DMARC)
2. Sender reputation (blacklists, complaint rates)
3. Content (spammy words, HTML issues)
4. Sending patterns (sudden volume spikes)
**Diagnostic tools:** [mail-tester.com](https://mail-tester.com), [mxtoolbox.com](https://mxtoolbox.com), [Google Postmaster Tools](https://postmaster.google.com)
## Related
- [List Management](./list-management.md) - Handle bounces and complaints to protect reputation
- [Sending Reliability](./sending-reliability.md) - Retry logic and error handling

View File

@@ -0,0 +1,126 @@
# Email Capture Best Practices
Collecting email addresses responsibly with validation, verification, and proper consent.
## Email Validation
### Client-Side
**HTML5:**
```html
<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 (Required)
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
## Email Verification
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 deals
```
**Don'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
**Design:**
- Mobile-friendly
- Large, tappable button
- Clear call-to-action
See [Transactional Emails](./transactional-emails.md) for detailed email design guidance.
## Related
- [Compliance](./compliance.md) - Legal requirements for consent (GDPR, CASL)
- [Marketing Emails](./marketing-emails.md) - What happens after capture
- [Deliverability](./deliverability.md) - How validation improves sender reputation

View File

@@ -0,0 +1,177 @@
# 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.
**Characteristics:**
- User-initiated or expected
- Time-sensitive and actionable
- Required for the user to complete an action
- Not promotional in nature
- Can be sent without explicit opt-in (with limitations)
**Examples:**
- Password reset links
- Order confirmations
- Account verification
- OTP/2FA codes
- Shipping notifications
### Marketing Emails
**Definition:** Emails sent for promotional, advertising, or informational purposes that are not directly related to a specific transaction.
**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
- 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
### 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. Be careful:
**Best practice:** Keep transactional and marketing separate. If you must include marketing in a transactional email:
- Make transactional content primary
- Keep marketing content minimal and clearly separated
- Ensure transactional purpose is clear
- Check local regulations (some regions prohibit this)
**Example of acceptable hybrid:**
- Order confirmation (transactional) with a small "You might also like" section (marketing)
**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](./transactional-email-catalog.md).
**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 Infrastructure
**Best practice:** Use separate sending infrastructure for transactional and marketing emails.
**Benefits:**
- Protect transactional deliverability
- Different authentication domains
- Independent reputation
- Easier compliance management
**Implementation:**
- Use different subdomains (e.g., `mail.app.com` for transactional, `news.app.com` for marketing)
- Separate email service accounts or API keys
- Different monitoring and alerting
### 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.
## Related Topics
- [Transactional Emails](./transactional-emails.md) - Best practices for sending transactional emails
- [Marketing Emails](./marketing-emails.md) - Best practices for marketing emails
- [Compliance](./compliance.md) - Legal requirements for each email type
- [Deliverability](./deliverability.md) - Ensuring transactional emails are delivered

View File

@@ -0,0 +1,157 @@
# 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) |
| Unsubscribe | Add immediately | Only if user re-subscribes |
| Soft bounce (3x) | Add after threshold | Yes, after 30-90 days |
| Manual removal | Add on request | Only if user requests |
### Implementation
```typescript
// 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:**
```typescript
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 |
### Re-engagement Campaigns
Before removing inactive subscribers:
1. **Identify inactive:** No opens/clicks in 90-180 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
```typescript
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
```typescript
// 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% | >5% |
| Complaint rate | <0.1% | >0.2% |
| Suppression list growth | Stable | Sudden spike |
| List churn | <2%/month | >5%/month |
## 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](./webhooks-events.md) - Receive bounce/complaint notifications
- [Deliverability](./deliverability.md) - How list hygiene affects sender reputation
- [Compliance](./compliance.md) - Legal requirements for data retention

View File

@@ -0,0 +1,115 @@
# 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) or simple process
- Immediate (GDPR) or within 10 days (CAN-SPAM)
- 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.1%
## Required Elements (All Marketing Emails)
- Clear sender identification
- Physical mailing address (CAN-SPAM)
- Unsubscribe mechanism
- Indication it's marketing (GDPR)
## Related
- [Compliance](./compliance.md) - Detailed legal requirements by region
- [Email Capture](./email-capture.md) - Collecting consent properly
- [List Management](./list-management.md) - Maintaining list hygiene

View File

@@ -0,0 +1,155 @@
# 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.
```typescript
// 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
```typescript
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
```typescript
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.
```typescript
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](./webhooks-events.md) - Process delivery confirmations and failures
- [List Management](./list-management.md) - Handle bounces and suppress invalid addresses

View File

@@ -0,0 +1,418 @@
# 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
- Account deletion confirmation
### Newsletter / Content Platform
Apps focused on content delivery and subscriptions.
**Essential:**
- Email verification
- Password reset
- Welcome email
- 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
- 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
- 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
- Compliance notices
### Social / Community Platform
Apps focused on user interaction and community features.
**Essential:**
- Email verification
- Password reset
- Welcome email
- 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
- 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
- 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.
**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](./email-types.md) - Understanding transactional vs marketing
- [Transactional Emails](./transactional-emails.md) - Best practices for sending transactional emails
- [Compliance](./compliance.md) - Legal requirements for each email type

View File

@@ -0,0 +1,92 @@
# 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: 123456 | Security code |
| 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-100 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 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.yourdomain.com |
| Reply-To | Monitored inbox | support@yourdomain.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

View File

@@ -0,0 +1,163 @@
# 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)
```typescript
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.
```typescript
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
```typescript
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
```typescript
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
```typescript
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.
```typescript
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.
```bash
ngrok http 3000
# Use the ngrok URL as your webhook endpoint
```
**Verify handling:** Send test events through Resend dashboard or manually trigger each event type.
## Related
- [List Management](./list-management.md) - What to do with bounce/complaint data
- [Sending Reliability](./sending-reliability.md) - Retry logic when sends fail