diff --git a/profiles/opencode/skill/email-best-practices/SKILL.md b/profiles/opencode/skill/email-best-practices/SKILL.md
new file mode 100644
index 0000000..4314e9a
--- /dev/null
+++ b/profiles/opencode/skill/email-best-practices/SKILL.md
@@ -0,0 +1,59 @@
+---
+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, or deciding transactional vs marketing.
+---
+
+# 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](./resources/deliverability.md) |
+| Build password reset, OTP, confirmations | [Transactional Emails](./resources/transactional-emails.md) |
+| Plan which emails your app needs | [Transactional Email Catalog](./resources/transactional-email-catalog.md) |
+| Build newsletter signup, validate emails | [Email Capture](./resources/email-capture.md) |
+| Send newsletters, promotions | [Marketing Emails](./resources/marketing-emails.md) |
+| Ensure CAN-SPAM/GDPR/CASL compliance | [Compliance](./resources/compliance.md) |
+| Decide transactional vs marketing | [Email Types](./resources/email-types.md) |
+| Handle retries, idempotency, errors | [Sending Reliability](./resources/sending-reliability.md) |
+| Process delivery events, set up webhooks | [Webhooks & Events](./resources/webhooks-events.md) |
+| Manage bounces, complaints, suppression | [List Management](./resources/list-management.md) |
+
+## Start Here
+
+**New app?**
+Start with the [Catalog](./resources/transactional-email-catalog.md) to plan which emails your app needs (password reset, verification, etc.), then set up [Deliverability](./resources/deliverability.md) (DNS authentication) before sending your first email.
+
+**Spam issues?**
+Check [Deliverability](./resources/deliverability.md) first—authentication problems are the most common cause. Gmail/Yahoo reject unauthenticated emails.
+
+**Marketing emails?**
+Follow this path: [Email Capture](./resources/email-capture.md) (collect consent) → [Compliance](./resources/compliance.md) (legal requirements) → [Marketing Emails](./resources/marketing-emails.md) (best practices).
+
+**Production-ready sending?**
+Add reliability: [Sending Reliability](./resources/sending-reliability.md) (retry + idempotency) → [Webhooks & Events](./resources/webhooks-events.md) (track delivery) → [List Management](./resources/list-management.md) (handle bounces).
diff --git a/profiles/opencode/skill/email-best-practices/resources/compliance.md b/profiles/opencode/skill/email-best-practices/resources/compliance.md
new file mode 100644
index 0000000..01460a0
--- /dev/null
+++ b/profiles/opencode/skill/email-best-practices/resources/compliance.md
@@ -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
diff --git a/profiles/opencode/skill/email-best-practices/resources/deliverability.md b/profiles/opencode/skill/email-best-practices/resources/deliverability.md
new file mode 100644
index 0000000..dd03157
--- /dev/null
+++ b/profiles/opencode/skill/email-best-practices/resources/deliverability.md
@@ -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
diff --git a/profiles/opencode/skill/email-best-practices/resources/email-capture.md b/profiles/opencode/skill/email-best-practices/resources/email-capture.md
new file mode 100644
index 0000000..311fb2d
--- /dev/null
+++ b/profiles/opencode/skill/email-best-practices/resources/email-capture.md
@@ -0,0 +1,126 @@
+# Email Capture Best Practices
+
+Collecting email addresses responsibly with validation, verification, and proper consent.
+
+## Email Validation
+
+### Client-Side
+
+**HTML5:**
+```html
+
+```
+
+**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
diff --git a/profiles/opencode/skill/email-best-practices/resources/email-types.md b/profiles/opencode/skill/email-best-practices/resources/email-types.md
new file mode 100644
index 0000000..235c56c
--- /dev/null
+++ b/profiles/opencode/skill/email-best-practices/resources/email-types.md
@@ -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
diff --git a/profiles/opencode/skill/email-best-practices/resources/list-management.md b/profiles/opencode/skill/email-best-practices/resources/list-management.md
new file mode 100644
index 0000000..f39ee27
--- /dev/null
+++ b/profiles/opencode/skill/email-best-practices/resources/list-management.md
@@ -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 {
+ 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
diff --git a/profiles/opencode/skill/email-best-practices/resources/marketing-emails.md b/profiles/opencode/skill/email-best-practices/resources/marketing-emails.md
new file mode 100644
index 0000000..b8c3748
--- /dev/null
+++ b/profiles/opencode/skill/email-best-practices/resources/marketing-emails.md
@@ -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
diff --git a/profiles/opencode/skill/email-best-practices/resources/sending-reliability.md b/profiles/opencode/skill/email-best-practices/resources/sending-reliability.md
new file mode 100644
index 0000000..2143b36
--- /dev/null
+++ b/profiles/opencode/skill/email-best-practices/resources/sending-reliability.md
@@ -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
diff --git a/profiles/opencode/skill/email-best-practices/resources/transactional-email-catalog.md b/profiles/opencode/skill/email-best-practices/resources/transactional-email-catalog.md
new file mode 100644
index 0000000..9990ac7
--- /dev/null
+++ b/profiles/opencode/skill/email-best-practices/resources/transactional-email-catalog.md
@@ -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
diff --git a/profiles/opencode/skill/email-best-practices/resources/transactional-emails.md b/profiles/opencode/skill/email-best-practices/resources/transactional-emails.md
new file mode 100644
index 0000000..57a2eeb
--- /dev/null
+++ b/profiles/opencode/skill/email-best-practices/resources/transactional-emails.md
@@ -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
diff --git a/profiles/opencode/skill/email-best-practices/resources/webhooks-events.md b/profiles/opencode/skill/email-best-practices/resources/webhooks-events.md
new file mode 100644
index 0000000..75454e8
--- /dev/null
+++ b/profiles/opencode/skill/email-best-practices/resources/webhooks-events.md
@@ -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