# Email Systems - Detailed Guide
> This file contains the detailed procedure and reference material extracted from `SKILL.md` for focused loading. The root skill defines activation, examples, safety constraints, and limitations.
## Scope and evidence
Do not send, provision paid providers or change DNS merely because this skill is
loaded: require the user's campaign/recipient or infrastructure mandate. SDK/API
fragments need exact package versions and private credentials. IP warm-up numbers,
bounce thresholds and named-provider endorsements below are illustrative; follow
current provider guidance and observed volume/reputation, not a fixed four-week
promise. Shared pools can suit low-volume transactional mail better than an idle
dedicated IP. Keep suppression durable across imports and queued sends; minimize
tracking data and apply applicable consent/retention rules.
[FTC CAN-SPAM guide](https://www.ftc.gov/business-guidance/resources/can-spam-act-compliance-guide-business).
## Principles
- Transactional vs Marketing separation | Description: Transactional emails (password reset, receipts) need measured reliability and an alternate recovery path.
Marketing emails (newsletters, promos) have lower priority. Separate sending streams/subdomains and suppression policies; consider distinct
IPs/providers at sufficient volume to protect transactional deliverability. | Examples: Good: Password resets via Postmark, marketing via ConvertKit | Bad: All emails through one SendGrid account
- Permission is everything | Description: Only email people who asked to hear from you. Double opt-in for marketing.
Easy unsubscribe. Clean your list ruthlessly. Bad lists destroy deliverability. | Examples: Good: Confirmed subscription + one-click unsubscribe | Bad: Scraped email list, hidden unsubscribe, bought contacts
- Deliverability is infrastructure | Description: SPF, DKIM, DMARC are not optional. Warm up new IPs. Monitor bounce rates.
Deliverability is earned through technical setup and good behavior. | Examples: Good: All DNS records configured, dedicated IP warmed for 4 weeks | Bad: Using free tier shared IP, no authentication records
- One email, one goal | Description: Each email should have exactly one purpose and one CTA. Multiple asks
means nothing gets clicked. Clear single action. | Examples: Good: "Click here to verify your email" (one button) | Bad: "Verify email, check out our blog, follow us on Twitter, refer a friend..."
- Timing and frequency matter | Description: Wrong time = low open rates. Too frequent = unsubscribes. Let users
set preferences. Test send times. Respect inbox fatigue. | Examples: Good: Weekly digest on Tuesday 10am user's timezone, preference center | Bad: Daily emails at random times, no way to reduce frequency
## Patterns
### Transactional Email Queue
Queue all transactional emails with retry logic and monitoring
**When to use**: Sending any critical email (password reset, receipts, confirmations)
// Don't block request on email send
await queue.add('email', {
template: 'password-reset',
to: user.email,
data: { resetToken, expiresAt }
}, {
attempts: 3,
backoff: { type: 'exponential', delay: 2000 }
});
### Email Event Tracking
Track delivery, opens, clicks, bounces, and complaints
**When to use**: Any email campaign or transactional flow
# Track lifecycle:
- Queued: Email entered system
- Sent: Handed to provider
- Delivered: Receiving mail server accepted it; inbox placement is not established
- Opened: Tracking pixel fetched; proxies/privacy features make this unreliable
- Clicked: Link fetched; security scanners can generate clicks
- Bounced: Failed delivery; distinguish temporary and permanent categories
- Complained: Marked as spam
### Template Versioning
Version email templates for rollback and A/B testing
**When to use**: Changing production email templates
templates/
password-reset/
v1.tsx (current)
v2.tsx (testing 10%)
v1-deprecated.tsx (archived)
# Deploy new version gradually
# Monitor metrics before full rollout
### Bounce Handling State Machine
Automatically handle bounces to protect sender reputation
**When to use**: Processing bounce and complaint webhooks
switch (bounceType) {
case 'hard':
await markEmailInvalid(email);
break;
case 'soft':
const count = await incrementBounceCount(email);
if (count >= configuredSoftBounceThreshold) await markEmailInvalid(email);
break;
case 'complaint':
await unsubscribeImmediately(email);
break;
}
### React Email Components
Build emails with reusable React components
**When to use**: Creating email templates
import { Button, Html } from '@react-email/components';
export default function WelcomeEmail({ userName }) {
return (
Welcome {userName}!
);
}
### Preference Center
Let users control email frequency and topics
**When to use**: Building marketing or notification systems
Preferences:
☑ Product updates (weekly)
☑ New features (monthly)
☐ Marketing promotions
☑ Account notifications (always)
# Respect preferences in all sends
# A preference center alone does not establish GDPR/ePrivacy compliance
## Sharp Edges
### Missing SPF, DKIM, or DMARC records
Severity: CRITICAL
Situation: Sending emails without authentication. Emails going to spam folder.
Low open rates. No idea why. Turns out DNS records were never set up.
Symptoms:
- Emails going to spam
- Low deliverability rates
- mail-tester.com score below 8
- No DMARC reports received
Why this breaks:
Email authentication (SPF, DKIM, DMARC) tells receiving servers you're
legit. Without them, you look like a spammer. Modern email providers
increasingly require all three.
Recommended fix:
# Required DNS records:
## SPF (Sender Policy Framework)
Illustrative only: authorize the actual sending providers in one SPF record and
respect DNS lookup limits; do not copy unrelated Google/SendGrid includes.
## DKIM (DomainKeys Identified Mail)
TXT record provided by your email provider
Adds cryptographic signature to emails
## DMARC (Domain-based Message Authentication)
Review aligned SPF/DKIM for every legitimate stream; begin with monitored policy
and approved report handling before moving to quarantine/reject. Do not blindly
replace existing DNS or send reports to an unapproved third-party mailbox.
# Verify setup:
- Send test email to mail-tester.com
- Check MXToolbox for record validation
- Monitor DMARC reports
### Using shared IP for transactional email
Severity: HIGH
Situation: Password resets going to spam. Using free tier of email provider.
Some other customer on your shared IP got flagged for spam.
Your reputation is ruined by association.
Symptoms:
- Transactional emails in spam
- Inconsistent delivery
- Using same provider for marketing and transactional
Why this breaks:
Shared IPs share reputation. One bad actor affects everyone. For
critical transactional email, you need your own IP or a provider
with strict shared IP policies.
Recommended fix:
# Transactional email strategy:
## Option 1: Dedicated IP (high volume)
- Get dedicated IP from your provider
- Warm it up slowly (start with 100/day)
- Maintain consistent volume
## Option 2: Transactional-only provider
- Postmark (very strict, great reputation)
- Includes shared pool with high standards
### Separate concerns:
- Transactional: Postmark or Resend
- Marketing: ConvertKit or Customer.io
- Never mix marketing and transactional
### Not processing bounce notifications
Severity: HIGH
Situation: Emailing same dead addresses over and over. Bounce rate climbing.
Email provider threatening to suspend account. List is 40% dead.
Symptoms:
- Bounce rate above 2%
- No webhook handlers for bounces
- Same emails failing repeatedly
Why this breaks:
Bounces damage sender reputation. Email providers track bounce rates.
Above 2% and you start looking like a spammer. Dead addresses must
be removed immediately.
Recommended fix:
# Bounce handling requirements:
### Hard bounces:
Remove immediately on first occurrence
Invalid address, domain doesn't exist
### Soft bounces:
Retry 3 times over 72 hours
After 3 failures, treat as hard bounce
### Implementation:
```typescript
// Framework-independent adapter pseudocode: use the provider's exact signature scheme.
async function handleEmailWebhook(rawBody: Uint8Array, headers: Headers) {
const event = verifyAndParseProviderWebhook(rawBody, headers); // Reject invalid signatures/replay.
if (await wasProcessed(event.id)) return;
await applySuppressionAndMarkProcessedAtomically(event);
// Hard bounce/complaint suppresses; transient failures follow provider semantics.
}
```
### Monitor:
Track bounce rate by campaign
Alert if bounce rate exceeds 1%
### Missing or hidden unsubscribe link
Severity: CRITICAL
Situation: Users marking as spam because they cannot unsubscribe. Spam complaints
rising. CAN-SPAM violation. Email provider suspends account.
Symptoms:
- Hidden unsubscribe links
- Multi-step unsubscribe process
- No List-Unsubscribe header
- High spam complaint rate
Why this breaks:
Users who cannot unsubscribe will mark as spam. Spam complaints hurt
reputation more than unsubscribes. Also it is literally illegal.
Commercial-message opt-out and privacy obligations depend on jurisdiction and purpose.
Recommended fix:
# Unsubscribe requirements:
### Visible:
- Above the fold in email footer
- Clear text, not hidden
- Not styled to be invisible
### One-click:
- RFC 8058 one-click uses an authenticated-by-opaque-token HTTPS POST, not a GET that scanners can trigger
- No login required
- No "are you sure" hoops
### List-Unsubscribe header:
Implement the POST endpoint, opaque list/recipient token validation and DKIM coverage
of both unsubscribe headers; do not redirect the POST or require login/cookies.
Keep a separate visible human unsubscribe flow. See [RFC 8058](https://www.rfc-editor.org/rfc/rfc8058.html).
```
List-Unsubscribe: ,
List-Unsubscribe-Post: List-Unsubscribe=One-Click
```
### Preference center:
Option to reduce frequency instead of full unsubscribe
### Sending HTML without plain text alternative
Severity: MEDIUM
Situation: Some users see blank emails. Spam filters flagging emails. Accessibility
issues for screen readers. Email clients that strip HTML show nothing.
Symptoms:
- No text/plain part in emails
- Blank emails for some users
- Lower engagement in some segments
Why this breaks:
Not everyone can render HTML. Screen readers work better with plain text.
Spam filters are suspicious of HTML-only. Multipart is the standard.
Recommended fix:
# Always send multipart:
```typescript
await resend.emails.send({
from: 'you@example.com',
to: 'user@example.com',
subject: 'Welcome!',
html: '
Welcome!
Thanks for signing up.
',
text: 'Welcome!\n\nThanks for signing up.',
});
```
# Auto-generate text from HTML:
Use html-to-text library as fallback
But hand-crafted plain text is better
# Plain text should be readable:
Not just HTML stripped of tags
Actual formatted text content
### Sending high volume from new IP immediately
Severity: HIGH
Situation: Just switched providers. Started sending 50,000 emails/day immediately.
Massive deliverability issues. New IP has no reputation. Looks like spam.
Symptoms:
- New IP/provider
- Sending high volume immediately
- Sudden deliverability drop
Why this breaks:
New IPs have no reputation. Sending high volume immediately looks
like a spammer who just spun up. You need to gradually build trust.
Recommended fix:
# IP warm-up schedule:
Week 1: 50-100 emails/day
Week 2: 200-500 emails/day
Week 3: 500-1000 emails/day
Week 4: 1000-5000 emails/day
Continue doubling until at volume
# Best practices:
- Start with most engaged users
- Send to Gmail/Microsoft first (they set reputation)
- Maintain consistent volume
- Don't spike and drop
# During warm-up:
- Monitor deliverability closely
- Check feedback loops
- Adjust pace if issues arise
### Emailing people who did not opt in
Severity: CRITICAL
Situation: Bought an email list. Scraped emails from LinkedIn. Added conference
contacts. Spam complaints through the roof. Provider suspends account.
Maybe a lawsuit.
Symptoms:
- Purchased email lists
- Scraped contacts
- High unsubscribe rate on first send
- Spam complaints above 0.1%
Why this breaks:
Permission-based sending is this workflow's policy. Do not claim CAN-SPAM universally requires opt-in: it sets commercial-message identification/opt-out duties; EU privacy/ePrivacy rules and other local laws need separate applicability review.
It is also effective - unwilling recipients hurt your metrics and
reputation more than they help.
Recommended fix:
# Permission requirements:
### Explicit opt-in:
- User actively chooses to receive email
- Not pre-checked boxes
- Clear what they are signing up for
### Double opt-in:
- Confirmation email with link
- Only add to list after confirmation
- Best practice for marketing lists
### What you cannot do:
- Buy email lists
- Scrape emails from websites
- Add conference contacts without consent
- Use partner/customer lists without consent
### Transactional exception:
Password resets, receipts, account alerts
do not need marketing opt-in
### Emails that are mostly or entirely images
Severity: MEDIUM
Situation: Beautiful designed email that is one big image. Users with images
blocked see nothing. Spam filters flag it. Mobile loading is slow.
No one can copy text.
Symptoms:
- Single image emails
- No text content visible
- Missing or generic alt text
- Low engagement when images blocked
Why this breaks:
Images are blocked by default in many clients. Spam filters are
suspicious of image-only emails. Accessibility suffers. Load times
increase.
Recommended fix:
# Balance images and text:
## Text/image accessibility heuristic (not a spam-filter threshold):
- At least 60% text content
- Images for enhancement, not content
### Always include:
- Alt text on every image
- Key message in text, not just image
- Fallback for images-off view
### Test:
- Preview with images disabled
- Should still be usable
# Example:
```html
Use code SAVE50 to save 50% this week.
```
### Missing or default preview text
Severity: MEDIUM
Situation: Inbox shows "View this email in browser" or random HTML as preview.
Lower open rates. First impression wasted on boilerplate.
Symptoms:
- View in browser as preview
- HTML code visible in preview
- No preview component in template
Why this breaks:
Preview text is prime real estate - appears right after subject line.
Default or missing preview text wastes this space. Measure preview text against actual conversion outcomes; no fixed uplift is established.
Recommended fix:
# Add explicit preview text:
### In HTML:
```html
Your preview text here. This appears in inbox preview.
```
### With React Email:
```tsx
Your preview text here. This appears in inbox preview.
```
### Best practices:
- Complement the subject line
- 40-100 characters optimal
- Create curiosity or value
- Different from first line of email
### Not handling partial send failures
Severity: HIGH
Situation: Sending to 10,000 users. API fails at 3,000. No tracking of what sent.
Either double-send or lose 7,000. No way to know who got the email.
Symptoms:
- No per-recipient send logging
- Cannot tell who received email
- Double-sending issues
- No retry mechanism
Why this breaks:
Bulk sends fail partially. APIs timeout. Rate limits hit. Without
tracking individual send status, you cannot recover gracefully.
Recommended fix:
# Track each send individually:
```text
Non-executable campaign worker design:
1. Load only consented, unsuppressed recipients for an explicitly authorized campaign.
2. Reserve a stable recipient+campaign idempotency key in a durable outbox.
3. Send through a bounded worker pool within provider limits; handle API error fields
as well as thrown errors and retain provider message IDs privately.
4. On timeout, mark outcome unknown and reconcile provider state before retrying.
5. Persist acknowledgement/suppression atomically; redact recipient and raw error
payloads in shared logs. Do not Promise.all 10,000 remote sends at once.
```
# Best practices:
- Log every send attempt
- Include message ID for tracking
- Build retry queue for failures
- Monitor success rate per campaign
## Validation Checks
### Missing plain text email part
Severity: WARNING
Emails should always include a plain text alternative
Message: Email being sent with HTML but no plain text part. Add 'text:' property for accessibility and deliverability.
### Hardcoded from email address
Severity: WARNING
From addresses should come from environment variables
Message: From email appears hardcoded. Use environment variable for flexibility.
### Missing bounce webhook handler
Severity: WARNING
Email bounces should be handled to maintain list hygiene
Message: Email provider used but no bounce handling detected. Implement webhook handler for bounces.
### Missing List-Unsubscribe header
Severity: INFO
Marketing emails should include List-Unsubscribe header
Message: Marketing email detected without List-Unsubscribe header. Add header for better deliverability.
### Synchronous email send in request handler
Severity: WARNING
Email sends should be queued, not blocking
Message: Email sent synchronously in request handler. Consider queuing for better reliability.
### Email send without retry logic
Severity: INFO
Email sends should have retry mechanism for failures
Message: Email send without apparent retry logic. Add retry for transient failures.
### Email API key in code
Severity: ERROR
API keys should come from environment variables
Message: Email API key appears hardcoded in source code. Use environment variable.
### Bulk email without rate limiting
Severity: WARNING
Bulk sends should respect provider rate limits
Message: Bulk email sending without apparent rate limiting. Add throttling to avoid hitting limits.
### Email without preview text
Severity: INFO
Emails should include preview/preheader text
Message: Email template without preview text. Add hidden preheader for inbox preview.
### Email send without logging
Severity: WARNING
Email sends should be logged for debugging and auditing
Message: Email being sent without apparent logging. Log sends for debugging and compliance.
## Collaboration
### Delegation Triggers
- copy|subject|messaging|content -> copywriting (Email needs copy)
- design|template|visual|layout -> ui-design (Email needs design)
- track|analytics|measure|metrics -> analytics-architecture (Email needs tracking)
- infrastructure|deploy|server|queue -> devops (Email needs infrastructure)
### Email Marketing Stack
Skills: email-systems, copywriting, marketing, analytics-architecture
Workflow:
```
1. Infrastructure setup (email-systems)
2. Template creation (email-systems)
3. Copy writing (copywriting)
4. Campaign launch (marketing)
5. Performance tracking (analytics-architecture)
```
### Transactional Email
Skills: email-systems, backend, devops
Workflow:
```
1. Provider setup (email-systems)
2. Template coding (email-systems)
3. Queue integration (backend)
4. Monitoring (devops)
```