Email Template Implementation: React Email, MJML, Plain HTML, and the Patterns That Don't Embarrass You in Outlook
If you're running a SaaS in 2026, you send a lot of email — welcome emails, password resets, receipts, notifications, digests, lifecycle drips. Most founders default to a string-template ("Hi ${name}, your...") with inline HTML, then six months in have 47 templates scattered across the codebase, each rendering subtly differently in Outlook 2016, with the brand colors slightly off in three of them. Email rendering is one of the most-frustrating frontends — it's like building UIs for a browser frozen in 2008.
A working email-template system answers: how do we author templates (React Email / MJML / plain HTML?), how do we maintain consistency, how do we handle dynamic data, how do we test rendering across clients, and how do we send through providers (per VibeReference: Email Providers). Done well, emails feel native to your brand and render reliably; done badly, every email is a one-off design + engineering project that breaks for 30% of recipients.
This guide is the implementation playbook for email templates in 2026 — picking the authoring approach, the design discipline, dynamic-data patterns, multi-client testing, and shipping templates without the Outlook nightmares.
Pick the Authoring Approach
The biggest decision is HOW you author email templates. Get this right.
Help me pick an email-template authoring tool.
The five options:
**Option A: React Email (recommended for most teams in 2026)**
```tsx
import { Html, Body, Container, Text, Button } from '@react-email/components';
export function WelcomeEmail({ name }: { name: string }) {
return (
<Html>
<Body>
<Container>
<Text>Welcome, {name}!</Text>
<Button href="https://app.example.com">Get started</Button>
</Container>
</Body>
</Html>
);
}
Pros:
- React component model
- Type-safe props
- Live preview during development (
react-email dev) - Compiles to clean HTML
- Tailwind support
- Reuse components across templates
- Same code = render to HTML or PDF (per pdf-document-generation-tools)
Cons:
- React-specific (Next.js / Node mostly)
- Compiled HTML still has email-specific quirks
Option B: MJML (markup-driven)
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>Welcome, {{name}}!</mj-text>
<mj-button href="https://app.example.com">Get started</mj-button>
</mj-column>
</mj-section>
</mj-body>
</mjml>
Pros:
- Industry-standard markup for emails
- Compiles to bulletproof email HTML (table-based)
- Strong Outlook compatibility
- Language-agnostic (Node, Ruby, Python, Go)
- Good for designers / non-engineers
Cons:
- XML-y; verbose
- Less component reuse than React Email
- Templating engine separate (Handlebars + MJML, etc.)
Option C: Plain HTML email (the legacy way)
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
<tr>
<td>Welcome, ${name}!</td>
</tr>
</table>
Pros:
- No build step
- Direct control
- Works anywhere
Cons:
- Tables and inline styles everywhere
- Hard to maintain
- Bug-prone
Option D: SaaS template builders (Postmark / Mailmodo / Stripo)
GUI-based template editors hosted by your email provider.
Pros:
- Non-engineer can edit
- WYSIWYG preview
- Provider-tested rendering
Cons:
- Vendor lock-in
- Limited dynamic logic
- Templates not in your code repo
Option E: Marketing-tool-specific (Customer.io / Klaviyo / Iterable templates)
If using a marketing automation platform (per VibeReference: Email Marketing Providers), use their template editors.
Pros:
- Tightly integrated with audience / triggers
- Marketing team can edit
Cons:
- Marketing emails only (not transactional)
- Vendor lock-in
- Two template systems (transactional + marketing)
The 90% answer for indie SaaS in 2026:
- Transactional + lifecycle: React Email (or MJML if not React-shop)
- Marketing broadcasts: marketing tool''s editor (Customer.io / Loops / Resend Broadcasts)
Don''t mix authoring approaches within a category.
For my system:
- Stack (React / Vue / Go / Python?)
- Sender mix (transactional / marketing / both)
- Team (engineers / designers / marketers)
Output:
- The authoring choice
- The setup plan
- The migration plan if changing
The biggest unforced error: **plain HTML email-templating with string interpolation.** Six months in, every template has bespoke code; nothing reuses; testing each is manual. The fix: pick a real authoring system on day one. React Email or MJML. Either works; both compound.
## Design Discipline: Brand Consistency Across Templates
Templates drift in look-and-feel over time. Build the discipline.
Help me set up email design consistency.
The structure:
1. Email design tokens
Define ONCE; use everywhere:
// emails/theme.ts
export const colors = {
primary: '#0066CC',
text: '#1A1A1A',
textMuted: '#6B7280',
background: '#FFFFFF',
backgroundAlt: '#F5F5F5',
border: '#E5E5E5',
};
export const fonts = {
body: 'system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif',
heading: 'system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif',
};
export const spacing = {
xs: '4px', sm: '8px', md: '16px', lg: '24px', xl: '40px',
};
Each template imports from theme.ts.
2. Reusable layout components
// emails/components/EmailLayout.tsx
import { Html, Body, Container, Section, Img, Text, Hr } from '@react-email/components';
export function EmailLayout({ children, preheader }: { children: React.ReactNode; preheader: string }) {
return (
<Html>
<head>
<title>{preheader}</title>
</head>
<Body style={{ background: colors.backgroundAlt, fontFamily: fonts.body }}>
<span style={{ display: 'none' }}>{preheader}</span>
<Container style={{ background: colors.background, padding: spacing.lg }}>
<Img src={`${BASE_URL}/logo.png`} width={120} alt="Acme" />
<Section>{children}</Section>
<Hr style={{ borderColor: colors.border, margin: `${spacing.xl} 0` }} />
<Text style={{ color: colors.textMuted, fontSize: 12 }}>
You''re receiving this because... <Link href="...">Unsubscribe</Link>
</Text>
</Container>
</Body>
</Html>
);
}
Every email uses EmailLayout. Brand consistency for free.
3. Standard typography components
export function H1({ children }: { children: React.ReactNode }) {
return <Text style={{ fontSize: 24, fontWeight: 600, color: colors.text }}>{children}</Text>;
}
export function P({ children }: { children: React.ReactNode }) {
return <Text style={{ fontSize: 16, lineHeight: '24px', color: colors.text }}>{children}</Text>;
}
export function Button({ href, children }: { href: string; children: React.ReactNode }) {
return (
<table role="presentation"><tr><td>
<a href={href} style={{
background: colors.primary, color: 'white',
padding: '12px 24px', borderRadius: 6,
textDecoration: 'none', fontWeight: 500,
display: 'inline-block',
}}>
{children}
</a>
</td></tr></table>
);
}
Templates use <H1>, <P>, <Button> — never raw HTML.
4. The "preheader" rule
The preheader (preview text in inbox) is critical:
- First 100 characters under subject line in inbox
- Most templates skip → "View in browser" leaks
- Always set explicitly
5. Light + dark mode
In 2026, most email clients support dark mode:
- Mac Mail / Gmail / Outlook adjust colors
- Test light + dark separately
- Use
prefers-color-schememedia query (limited support but improving)
6. The "no images of text" rule
Never put text in images — accessibility + dark-mode breaks.
Exception: logo.
7. The "alt text on every image" rule
Email clients block images by default for many users. Alt text saves the experience.
For my system:
- Theme tokens
- Layout component
- Typography components
Output:
- The theme.ts file
- The EmailLayout component
- The component library
The biggest design-consistency mistake: **each email is its own snowflake.** Bob writes the welcome email; Alice writes the password reset; Carol writes the receipt. Three different button styles; three different paddings; three different footers. The fix: shared layout + components. Every template uses them. Custom is forbidden without team sign-off.
## Handle Dynamic Data Safely
Email templates take user data. Get the data-handling right.
Help me handle template data.
The principles:
1. Type-safe props
type WelcomeEmailProps = {
user: {
firstName: string;
email: string;
};
workspace: {
name: string;
inviteLink: string;
};
};
export function WelcomeEmail({ user, workspace }: WelcomeEmailProps) {
return <EmailLayout preheader={`Welcome to ${workspace.name}`}>
{/* ... */}
</EmailLayout>;
}
TypeScript ensures you pass the right data.
2. Escape user-supplied content
User-generated content (names, message bodies) must be escaped:
- React Email auto-escapes (safe by default)
- Plain HTML / template-string approach: explicit escape
Don''t:
// XSS risk
const html = `<p>Welcome ${user.name}</p>`;
Do:
// React Email escapes for you
<P>Welcome {user.name}</P>
// Or in plain HTML, escape:
const html = `<p>Welcome ${escapeHtml(user.name)}</p>`;
3. Default values for missing data
Some users won''t have all fields:
const greeting = user.firstName ? `Hi ${user.firstName}` : 'Hi there';
Don''t render "Hi {{user.first_name}}" because of templating-error.
4. Localization (if applicable)
If you serve multiple languages (per internationalization-chat):
import { useTranslation } from 'react-i18next';
export function WelcomeEmail({ user, locale }: WelcomeEmailProps) {
const { t } = useTranslation('emails', { lng: locale });
return <P>{t('welcome.greeting', { name: user.firstName })}</P>;
}
5. URLs and tracking
Outbound links should:
- Be HTTPS
- Include UTM parameters for analytics
- Use a redirect-tracker (Postmark / Customer.io provide; Resend handles)
const url = `https://app.example.com/dashboard?utm_source=email&utm_campaign=welcome`;
6. Personalization that won''t embarrass you
Don''t over-personalize:
- "We noticed you''re from Cleveland" — feels stalker-y
- "Hi [Name], here are 3 things you''ll love" — fine
Test with edge cases:
- User with no first name (just email)
- User with apostrophe in name (O''Brien)
- User from another country
- User with non-Latin name
- User who blocked images
For my templates:
- Type definitions
- Escape strategy
- Default values
- Edge cases
Output:
- The prop typing
- The escape audit
- The edge-case testing
The biggest data-handling mistake: **failing to handle missing values.** Email goes out: "Welcome, {firstname}!" because the user signed up via SSO without first name. Looks broken. The fix: every dynamic value has a fallback; test missing-data cases explicitly.
## Multi-Client Rendering: The Outlook Tax
Email clients render differently. Test before shipping.
Help me test email rendering.
The pain:
Email clients vary wildly:
- Gmail: modern; CSS support good
- Apple Mail: modern; CSS support good
- Outlook 2016+ (Windows): TERRIBLE; uses Word renderer; tables only
- Outlook for Mac: better than Windows
- Outlook.com: like Gmail
- Yahoo: decent
- iOS Mail: like Apple Mail
- Android Gmail: depends on app
The compatibility realities:
Things that work everywhere:
- Tables (
<table>for layout) - Inline styles (
<p style="color: red">) - Basic font choices
- Simple links + buttons
- Background colors (with table cells)
Things that work on most BUT NOT Outlook:
- CSS Grid / Flexbox
<div>for layout<style>in head (some Outlook versions strip)- CSS animations
- Custom fonts (Outlook strips)
border-radius(mostly works; some clients ignore)
Things that NEVER work:
- JavaScript
- Forms (mostly; AMP for Email partial exception)
- Video (use animated GIF instead, sparingly)
- HTTP (must be HTTPS)
The "table-based layout" reality:
In 2026, even with React Email, you still use tables for layout in emails. React Email components compile to table HTML.
Don''t fight it. Author with React; let it compile to tables.
Testing tools:
| Tool | Cost | Purpose |
|---|---|---|
| Litmus | $99/mo | Render preview across 90+ clients |
| Email on Acid | $99/mo | Similar to Litmus |
| Mailtrap | $14/mo+ | Catch test emails; preview rendering |
| Postmark dev preview | Bundled | Postmark accounts |
| Resend preview | Bundled | Resend accounts |
| react-email dev | Free | Live preview during dev |
| Browser preview | Free | Open the compiled HTML in browser |
The "browser test" minimum:
Even without paid tools:
- Browser: open compiled HTML
- Send test to Gmail (browser + iOS app + Android app)
- Send test to Outlook (web + desktop)
- Send test to Apple Mail
- Cover ~85% of recipients
The Outlook 2016+ specific advice:
If you support Outlook (and many B2B do):
- Test in actual Outlook (not just Litmus screenshot)
- Outlook uses Word for rendering — bizarre quirks
- VML for round corners on buttons
- Hide elements with conditional comments:
<!--[if !mso]><!-->...<!--<![endif]--> - Use bulletproof button HTML (not just
<a>styled)
For my testing:
- Tools used
- Client coverage
- Outlook-specific testing
Output:
- The testing setup
- The client priority list
- The Outlook-specific patches
The biggest testing mistake: **not testing in Outlook.** Render looks great in Gmail; Outlook breaks the layout; 30% of B2B users see broken email. The fix: test in Outlook explicitly. Litmus / Email on Acid pay for themselves the first time you catch an Outlook bug.
## The Template Catalog: Manage Without Chaos
Templates accumulate. Without inventory, they sprawl.
Help me catalog email templates.
The structure:
Per template:
| Field | Example |
|---|---|
| Name | welcome-email |
| Trigger | User signs up |
| Audience | All new users |
| Subject | "Welcome to Acme!" |
| Owner | Product / Marketing / Eng |
| Last reviewed | 2026-04-30 |
| Replaceable | Yes / No |
Categorize:
| Category | Examples |
|---|---|
| Transactional | Receipt, invoice, password reset |
| Lifecycle | Welcome, day-3, week-1 |
| Notification | Mention, comment reply, share |
| Digest | Weekly summary, activity report |
| Marketing | Newsletter, product launch |
| Account | Email change, password change, billing failed |
Storage convention:
emails/
templates/
transactional/
receipt.tsx
password-reset.tsx
lifecycle/
welcome.tsx
day-3-tips.tsx
notification/
mention.tsx
...
components/
EmailLayout.tsx
H1.tsx
Button.tsx
...
theme.ts
send.ts
The single-source-of-truth rule:
Every email has ONE place where it''s defined.
Anti-pattern: same email defined in two places (transactional system + marketing automation tool). They drift. Pick one home.
Quarterly review:
- Templates in catalog
- Templates actually sent (last 90 days)
- Templates with issues
- Templates to deprecate
Some templates haven''t been sent in a year — kill them.
The "template owner" rule:
Every template has an owner:
- Marketing / lifecycle: marketing
- Transactional: product / engineering
- Account: product / engineering
When something needs update: clear owner.
For my templates:
- Catalog inventory
- Categorization
- Owners
Output:
- The catalog
- The categorization
- The owner assignments
The biggest catalog mistake: **no catalog.** A year in, you have 47 templates somewhere; nobody can list them; nobody owns them. The fix: catalog them all in one doc; categorize; assign owners. 30 minutes one time; clarity forever.
## Render Engine: Where Templates Compile and Send
How do templates actually get sent? Architecture matters.
Help me design the render-and-send pipeline.
The pipeline:
Trigger event → Pick template → Render with data → Send via provider → Track delivery
Step 1: Trigger event
- User action (signup, purchase, comment)
- Cron job (digest)
- Manual send (admin tool)
Step 2: Pick template
async function sendEmail(name: string, data: any, recipient: string) {
const template = await getTemplate(name); // welcome | receipt | ...
// ...
}
Step 3: Render
// Server-side render
import { render } from '@react-email/render';
import { WelcomeEmail } from './emails/welcome';
const html = await render(<WelcomeEmail user={user} />);
const text = await render(<WelcomeEmail user={user} />, { plainText: true });
Provide BOTH HTML and plaintext (some clients still use text version).
Step 4: Send via provider
Per email-providers:
// Resend example
import { Resend } from 'resend';
await resend.emails.send({
from: 'Acme <noreply@acme.com>',
to: recipient,
subject: 'Welcome!',
html,
text,
reply_to: 'support@acme.com',
headers: {
'X-Entity-Ref-ID': eventId, // For tracking
},
});
Step 5: Track
- Provider webhooks (delivered / opened / clicked / bounced)
- Per outbound-webhooks-chat
- Update DB (sent_at, opened_at, etc.)
The async-vs-sync rule:
Sending is slow (200-1000ms). Don''t block user requests.
// Bad: blocks API
async function POST(req) {
await db.users.create(...);
await sendEmail('welcome', user); // 500ms; blocks
return Response.json({ ok: true });
}
// Good: async
async function POST(req) {
await db.users.create(...);
await queueEmail('welcome', user); // queue for background send
return Response.json({ ok: true });
}
Per cron-scheduled-tasks-chat: use queue (Inngest / Vercel Queues / etc.).
The retry pattern:
Email sends can fail (provider down, rate limit). Retry:
await sendEmailWithRetry({
template: 'welcome',
data: { user },
retries: 3,
backoff: 'exponential',
});
Provider SDKs often have built-in retry. Otherwise: queue → retry.
The deduplication pattern:
Avoid double-sending:
// Idempotency key
await sendEmail('receipt', {
data: { invoice },
idempotencyKey: `receipt-${invoice.id}`,
});
Some providers support; otherwise track sent in DB.
The dev / staging behavior:
In dev / staging, NEVER send to real users:
const recipient =
process.env.NODE_ENV === 'production'
? user.email
: process.env.DEV_TEST_EMAIL; // your team email
Or use Mailtrap (catches test emails; doesn''t deliver).
For my pipeline:
- Render approach
- Send approach (sync vs async)
- Retry / dedup
- Dev safety
Output:
- The pipeline architecture
- The async / queue setup
- The dev-safety rules
The biggest pipeline mistake: **synchronous send in API request.** API takes 800ms because email send is included. User experience suffers. Or: dev environment accidentally sends to real users. The fix: queue all sends; never send sync; safety-check dev.
## Internationalization & Time Zones
If you serve multiple markets, plan for it.
Help me handle i18n + timezones.
The patterns:
1. Locale per user
Store user.locale (e.g., "en-US", "fr-FR", "ja-JP").
When sending, render in user''s locale:
<EmailLayout locale={user.locale}>
<P>{t('welcome.greeting', { name: user.firstName }, user.locale)}</P>
</EmailLayout>
Per internationalization-chat.
2. Translations storage
JSON files:
locales/
en-US/
emails/welcome.json
fr-FR/
emails/welcome.json
Each language gets its own translation file. Translation keys consistent.
3. Time zone for timestamps
If email contains times:
- Show in user''s timezone
- Or show UTC with timezone label
<P>Your meeting is at {formatInTimezone(date, user.timezone)} ({user.timezone})</P>
4. Right-to-left (RTL) support
For Arabic / Hebrew users:
<html dir="rtl" lang="ar">
Layouts mostly mirror; some elements need explicit RTL handling.
5. Date formats
Different locales use different date formats:
- US: 04/30/2026
- EU: 30/04/2026
- ISO: 2026-04-30
Use locale-aware formatting:
new Intl.DateTimeFormat(user.locale, { dateStyle: 'long' }).format(date);
6. Currency
Per currency-fx-handling-chat:
new Intl.NumberFormat(user.locale, {
style: 'currency',
currency: 'USD',
}).format(amount);
For my system:
- Languages supported
- Date / time / currency localization
- Translation workflow
Output:
- The i18n setup
- The localized templates
- The translation workflow
The biggest i18n mistake: **English-only emails to non-English users.** International customer signs up; gets English welcome email; feels generic. The fix: at minimum, use Intl APIs for dates / numbers / currency. Full translation when you have international scale.
## Testing and Quality
Templates ship with bugs without testing. Build the discipline.
Help me test email templates.
The test types:
1. Snapshot tests
import { render } from '@react-email/render';
import { WelcomeEmail } from './emails/welcome';
test('welcome email renders', async () => {
const html = await render(<WelcomeEmail user={{ firstName: 'Test' }} />);
expect(html).toMatchSnapshot();
});
Catches unintended changes.
2. Accessibility tests
- Alt text on images
- Semantic HTML where possible
- Color contrast OK
- Readable in screen readers
Tools: Pa11y (CLI accessibility checker)
3. Visual regression
Tools: Litmus / Email on Acid render preview; or Percy for HTML pages
Compare current render to last-known-good.
4. Edge case tests
- Long names ("This is a very long first name that overflows")
- Empty / null fields
- Names with apostrophes (O''Brien)
- Unicode names (李明)
- Long emails
- Long URLs
Catch edge-case rendering bugs.
5. Subject line tests
Subject lines:
- < 50 characters preferred (mobile)
- No emoji-overload
- No spam-trigger words
- Personalization where appropriate
A/B test subject lines (per ab-testing-chat).
6. Spam-score check
Tools: Mail-Tester (email a unique address; get spam score)
Most providers (Postmark / Resend) auto-check.
Common triggers:
- All caps
- Excessive punctuation ("FREE!!!")
- "Click here" without context
- Image-heavy / text-light
- Unsubscribe missing
7. Render-and-screenshot pre-deploy
CI step:
- Render template to HTML
- Screenshot in headless browser
- Compare to baseline
- Fail if different
For my system:
- Test types
- CI integration
- Edge-case coverage
Output:
- The test plan
- The CI integration
- The edge-case fixtures
The biggest testing mistake: **shipping templates untested in production.** Customer reports broken email; you find a missing `</td>` tag; embarrassing. The fix: snapshot tests + visual regression in CI; edge-case fixtures; can''t merge if tests fail.
## Avoid Common Pitfalls
Recognizable failure patterns.
The email-template mistake checklist.
Mistake 1: Plain HTML strings with concat
- XSS risk; unmaintainable
- Fix: React Email or MJML
Mistake 2: No design tokens
- Brand drift across templates
- Fix: theme.ts + shared components
Mistake 3: Outlook untested
- 30% of B2B users see broken
- Fix: explicit Outlook testing
Mistake 4: Missing alt text / preheader
- Image-blocked users see broken
- Fix: discipline on every template
Mistake 5: Synchronous send in API
- Slow API responses
- Fix: queue / async
Mistake 6: No dev-safety
- Test emails reach real users
- Fix: env-based recipient routing
Mistake 7: No retry on send failure
- Lost emails when provider blips
- Fix: retry with backoff
Mistake 8: Templates not in version control
- Stored in marketing tool only
- Fix: code-repo as source of truth
Mistake 9: No catalog
- 47 templates; nobody knows what''s sent
- Fix: catalog with owners
Mistake 10: Image-of-text design
- Dark mode breaks; accessibility fails
- Fix: real text, never image-of-text
The quality checklist:
- Authoring tool picked (React Email / MJML)
- Theme tokens + shared layout component
- Type-safe props per template
- Preheader on every template
- Alt text on every image
- Tested in 5+ clients including Outlook
- Async send via queue
- Retry + dedup
- Dev-safety (no real emails in dev)
- Catalog with owners
- CI: snapshot + edge-case tests
For my templates:
- Audit
- Top 3 fixes
Output:
- Audit results
- Top 3 fixes
- The "v2 email infrastructure" plan
The single most-common mistake: **treating emails as one-off projects.** Every template is its own snowflake; brand drifts; testing skipped; bugs ship. The fix: email is infrastructure. Theme + components + tests + catalog. The discipline pays back constantly because emails are seen by every user, every day.
---
## What "Done" Looks Like
A working email-template system in 2026 has:
- Authoring tool picked (React Email default for React shops; MJML for non-React)
- Design tokens + shared layout components
- Type-safe template props
- Preheader + alt-text on every template
- Multi-client tested (Gmail / Outlook / Apple Mail / iOS / Android)
- Async send via queue with retry + dedup
- Dev-safety preventing real-user emails in dev
- Catalog of all templates with owners
- CI tests (snapshot, edge-case, visual regression)
- Quarterly review removing unused templates
The hidden cost of weak email-template infrastructure: **broken trust at the most personal touchpoint.** Email is one-on-one; broken email feels personal; customers notice. A welcome email that looks broken in Outlook tells the recipient "this company doesn''t care about details." Templates compound — invest once; benefit on every send. The opposite — bespoke per-template work — is permanent tax with constant brand drift.
## See Also
- [Email Deliverability](email-deliverability-chat.md) — getting emails through
- [Outbound Webhooks](outbound-webhooks-chat.md) — provider event tracking
- [Cron Jobs & Scheduled Tasks](cron-scheduled-tasks-chat.md) — scheduled sends
- [Internationalization](internationalization-chat.md) — localized templates
- [Currency & FX Handling](currency-fx-handling-chat.md) — money in receipts
- [In-App Notifications](in-app-notifications-chat.md) — adjacent / alternative channel
- [Mobile Push Notifications](mobile-push-notifications-chat.md) — adjacent channel
- [Onboarding Email Sequence](onboarding-email-sequence-chat.md) — lifecycle emails
- [Caching Strategies](caching-strategies-chat.md) — template caching
- [Performance Optimization](performance-optimization-chat.md) — async send
- [VibeReference: Email Providers](https://www.vibereference.com/backend-and-data/email-providers) — sending layer
- [VibeReference: Email Marketing Providers](https://www.vibereference.com/marketing-and-seo/email-marketing-providers) — marketing tools
- [VibeReference: Resend](https://www.vibereference.com/backend-and-data/resend) — Resend specifics
- [VibeReference: AWS SES](https://www.vibereference.com/backend-and-data/aws-ses) — SES specifics
- [LaunchWeek: Email Sequences](https://www.launchweek.com/2-content/email-sequences) — strategy / playbook
[⬅️ Day 6: Grow Overview](README.md)