Best Practices
Production communication - deliverability, unsubscribe, observability, testing, compliance (CAN-SPAM, GDPR, CASL)
Best Practices
Email is the only kind of infrastructure your competitors can mess up to your detriment — their bad emails get your IP range banned, their spam complaints affect your shared sender pools. These habits keep your messages landing in inboxes.
Deliverability Fundamentals
Deliverability is the % of your sent emails that reach the inbox (not spam, not bounced). The factors:
| Factor | What helps |
|---|---|
| Domain authentication | SPF + DKIM + DMARC all set correctly |
| IP reputation | Dedicated IP for high volume; warm up gradually |
| Sender reputation | Low bounce + low complaint + high engagement |
| List hygiene | Don't send to bounces, unsubscribers, complaints |
| Content | Avoid spammy words, balanced text/HTML ratio, no broken images |
| Frequency | Sudden bursts look like spam attacks |
| Engagement signals | Opens, clicks, replies all help; ignores / deletes hurt |
The hierarchy: transactional emails for users who explicitly created accounts > marketing to engaged users > marketing to cold lists. Each step down is harder.
SPF / DKIM / DMARC
Mandatory in 2025+. Gmail and Yahoo now enforce them — emails without all three risk going straight to spam.
example.com TXT "v=spf1 include:_spf.resend.com -all"
resend._domainkey.example.com TXT "v=DKIM1; k=rsa; p=..."
_dmarc.example.com TXT "v=DMARC1; p=reject; rua=mailto:dmarc@example.com; pct=100"-all (hard fail) > ~all (soft fail). p=reject > p=quarantine > p=none. Aim for hard SPF and DMARC reject once you're confident your sending sources are all authorized.
Dedicated IP
For >100K emails/month, consider a dedicated IP on your transactional provider. Pros: your reputation, your control. Cons: must warm up, smaller volumes can be a disadvantage (no other senders to share warming signal).
Below 100K/month: stay on the shared IP pool. The provider warmed it for everyone; you benefit.
Unsubscribe (Required, Honor Always)
For any non-transactional email:
- One-click unsubscribe in every send (Gmail/Yahoo requirement:
List-Unsubscribeheader + visible footer link). - Honor immediately, not "within 10 days." (CAN-SPAM says 10 days; in practice make it instant.)
- No requirement to log in to unsubscribe. That's hostile and illegal in some jurisdictions.
- Confirm unsubscribe with a brief landing page, no further sign-in required.
- Suppress globally, not just per-list. A user who unsubscribed from your marketing emails should not be added to a different marketing list later without re-consent.
// One-click unsubscribe header
'List-Unsubscribe': '<https://example.com/u/abc123>, <mailto:unsubscribe+abc123@example.com>'
'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click'The HTTP POST to your unsubscribe URL doesn't need authentication — the link itself is the token. Verify the token, suppress, done.
For transactional emails (password reset, receipts), unsubscribe is generally not required, but always include "account preferences" links so users can manage what they receive.
Suppression Lists
Two lists every system needs:
| List | Source | Effect |
|---|---|---|
| Hard bounces | Webhook: invalid address | Never email this address again |
| Complaints | Webhook: marked as spam | Never email this address again |
| Unsubscribes | Webhook or user action | Don't send marketing; transactional OK |
| Manual blocks | Support / abuse team | Per their request |
Before every send: check the suppression list. Don't trust your providers to enforce it — they sometimes have gaps.
async function sendEmail(to, ...) {
if (await suppressions.includes(to)) {
metrics.increment('email.suppressed');
return;
}
await provider.send({ to, ... });
}Observability
| Metric | Why |
|---|---|
| Send rate | Volume; alert on spikes (could be bug or abuse) |
| Delivery rate | (delivered / sent); should be >99% |
| Bounce rate | Hard bounces; >2% is bad |
| Complaint rate | Spam reports; >0.1% triggers Gmail's spam filter on you |
| Open rate | Soft signal of engagement (limited by Apple Mail Privacy Protection) |
| Click-through rate | Better engagement signal |
| Unsubscribe rate per campaign | High = bad targeting |
| Time to delivery | Slow = provider queue issues |
Most providers ship a dashboard. Mirror critical metrics to your own observability (Prometheus & Grafana) so you can alert on them like any other system.
Set up a DMARC aggregate report parser — _dmarc reports tell you who's sending email claiming to be you. Useful for spotting unauthorized senders.
Testing
Three layers:
Local Development
Use Mailtrap or Mailpit — a fake SMTP server that catches outgoing emails without sending them:
# docker-compose.yml
services:
mailpit:
image: axllent/mailpit
ports:
- "1025:1025" # SMTP
- "8025:8025" # web UIPoint your dev environment at port 1025; emails land in the web UI at port 8025. Devs see exactly what users see, no real send.
CI
Render templates, snapshot the HTML, compare. Catches accidental layout breakage.
test('welcome email renders correctly', () => {
const html = render(<WelcomeEmail name="Alice" />);
expect(html).toMatchSnapshot();
});Pre-Production
Send to a real mailbox you control via the provider's test API key or sandbox mode. Validates the actual send pipeline without spamming real users.
Email Client Compatibility
The bad news: email clients vary wildly. Gmail's renderer ≠ Outlook's ≠ Apple Mail's.
- Outlook desktop uses Word's renderer — supports almost nothing modern. Tables, inline styles, ancient HTML.
- Gmail strips
<style>tags in some contexts — use inline styles. - Apple Mail is the most modern; dark mode aware.
- Mobile clients are most varied; many users now use Gmail mobile.
Use Litmus or Email on Acid to preview across 50+ clients before launch.
For most teams, React Email + a careful template is fine — it abstracts the worst quirks. Test the critical templates in 3-5 representative clients.
Compliance
| Law | Applies to | Key requirements |
|---|---|---|
| CAN-SPAM (US) | Commercial email | Honest from, accurate subject, physical address, unsubscribe within 10 days |
| GDPR (EU) | Personal data of EU residents | Lawful basis (consent for marketing), data access, deletion |
| CASL (Canada) | Commercial email to Canadians | Express consent (opt-in), identification, unsubscribe |
| PECR (UK) | Marketing communications | Consent, identification |
In practice:
- Marketing: require explicit opt-in. No pre-checked boxes. Track when and how they opted in.
- Transactional: implicit consent from the account relationship is generally fine.
- Cold outreach: B2B may be OK in CAN-SPAM; usually requires consent in EU.
- Cookie-banners aren't enough for email consent (separate consent).
Keep audit trails: who opted in, when, from which form. Required for GDPR Article 7.
Operational Habits
| Habit | Why |
|---|---|
| Send via a queue, not synchronously | Provider hiccups don't break user flows |
| Different sending domains for transactional vs marketing | Insulate reputation |
| Idempotency keys on send | Crashing mid-loop doesn't double-send |
| Rate limiting your own send rate | Avoid sudden bursts |
Reply-To to a monitored address | Users will reply; respond or auto-respond |
| Test unsubscribe end-to-end monthly | Bugs here are reputation-damaging |
| Monitor your own DMARC reports | Spot impersonators and bad config |
Common Pitfalls
| Pitfall | Symptom | Fix |
|---|---|---|
From: noreply@example.com | Replies go nowhere | Use a monitored mailbox or Reply-To |
Sending from @gmail.com / @yahoo.com | Strict DMARC at those providers rejects you | Use your own verified domain |
| Embedding tracking pixels in transactional email | Privacy concerns; some providers warn users | Don't track transactional sends |
| Showing user-supplied content (subject, body) unsanitized | Phishing-by-proxy through your service | Validate and constrain user-supplied fields |
Sending to typo addresses (gmial.com) | Hard bounces accumulate | Validate the email format; use email validation services |
| All emails from the same template variable | Personalization opportunities missed | Use templates with conditional sections |
| No unsubscribe link on "newsletters" | Legal risk + complaints | Always include |
| Same content as last week | Frequency fatigue | Vary content; segment lists |
Big images, no alt text | Blocked images render as nothing | Always provide alt |
Checklist
Production communication checklist
- SPF, DKIM, DMARC configured; DMARC at
p=reject - Separate sending domains for transactional vs marketing
- Transactional emails sent via a job queue, not synchronously
- React Email (or equivalent) for templates; versioned in code
- Localization for non-English audiences
- One-click
List-Unsubscribeheader on all non-transactional emails - Suppression list checked before every send
- Webhook handler for bounces, complaints, unsubscribes
- Webhook signature verified
- Mailtrap / Mailpit in development environments
- Snapshot tests for critical email templates
- Pre-production sends to test mailbox before launch
- Metrics: delivery rate, bounce rate, complaint rate alerting
- DMARC aggregate reports monitored
- Unsubscribe flow tested end-to-end monthly
- Compliance documentation (consent capture, retention)
- Reply-To monitored or auto-responded to
- Rate limiting on send to prevent bursts
- Backup provider configured (failover when primary is down)