design-email · diff

git:20260923.938ff43 to git:20261006.39320f8

7 added, 122 removed. Audit F to F.

---
name: design-email
description: >
Design and build transactional and marketing email templates. Use when
"build an email template", "welcome email", "password reset email", "React
Email", "MJML", "dark mode email", or "email copy review". Spam/SPF →
enhance-email-deliverability.
license: MIT
---
# design-email — Email Templates That Actually Get Read
**Degree of freedom: MIXED.** Copy and layout judgment `[HIGH freedom]`;
stack detect, markup, send, and preview `[LOW freedom — run exactly]`.
**Email is the one channel users check before they check your app.** A
well-crafted transactional email — clear, warm, fast-loading, and readable on
any device — builds trust. A corporate, jargon-heavy, broken email erodes it.
This skill handles design and copy. Inbox placement / SPF DKIM DMARC is `enhance-email-deliverability`.
## How to reason
1. **Detect** — React Email vs MJML vs none
2. **Audit** — copy voice, subject, one action; client-safe layout
3. **Write** — specific subject, human "you", plain-text twin
4. **Preview** — 375px + dark mode; hand DNS off
## Worked example
> **Detect:** `@react-email/components` already in `package.json`; `emails/` has a receipt only.
> **Audit:** password-reset subject is "Account notification"; two CTAs; no preview text.
> **Write:** subject "Reset your App password"; one "Reset my password" button; expiry in the first sentence.
> **Preview:** `npx email dev` at 375px; tap target ≥44px; DNS/spam → `enhance-email-deliverability`.
## Self-critique before reporting
- **One action** — exactly one primary CTA; subject is specific and honest
- **Client-safe** — 600px single column, inline styles, alt text, dark fallback
- **Human copy** — "you"/"we"; no jargon or ALL-CAPS subject
- **Right owner** — SPF / DKIM / DMARC / bounces / List-Unsubscribe → `enhance-email-deliverability`
---
## Phase 0: Detect the stack [LOW freedom — run exactly]
```
package.json → @react-email/*, mjml, nodemailer, @sendgrid/mail,
resend, @aws-sdk/client-ses, postmark
emails/ → existing React Email templates
src/emails/ → email template directory
.env.* → RESEND_API_KEY, SENDGRID_API_KEY, etc. (reference by name only)
```
| Detected framework | Approach |
|--------------------|----------|
| React Email (`@react-email/components`) | JSX components, preview server, multi-client export |
| MJML | XML-like syntax, battle-tested cross-client output |
| None | Implement React Email (current best practice) |
---
## Phase 1: Audit existing templates [HIGH freedom]
If templates already exist, walk through each one and check:
**Copy quality**:
- Does it sound like a person sent it, or a system?
- Is the subject line specific? ("Your order is on its way" not "Order notification")
- Is the first sentence the most important one (preview text matters)?
- Does it tell the reader exactly what to do and what happens next?
- Is it free of business jargon, technical terms, and passive voice?
**Design**:
- Single-column layout? (Most reliable across clients)
- Max-width 600px? (Standard email width)
- All styles inline? (Gmail strips `<style>` tags)
- Images have `alt` text? (Many clients block images by default)
- Dark mode support? (`@media (prefers-color-scheme: dark)` + inline fallbacks)
- Mobile: tap targets ≥ 44px, font size ≥ 14px
**Sender identity (template-side):**
- "From" address uses the app domain, not a personal Gmail?
- SPF / DKIM / DMARC / bounces → `enhance-email-deliverability`
---
## Phase 2: Research best practices [HIGH freedom]
```json
firecrawl:firecrawl_search
{
"query": "transactional email design best practices [current year] React Email",
"limit": 3,
"sources": [{ "type": "web" }]
}
```
Fetch React Email docs if using it:
```json
context7:resolve-library-id
{
"libraryName": "react-email"
}
```
---
## Phase 3: Copy principles [HIGH freedom]
Apply these to every email before looking at design:
| Principle | How |
|-----------|-----|
| Lead with the most important thing | First line = the key action or news. Not "Hi, we wanted to let you know that…" |
| Subject line = specific, honest | "Your password reset link" not "Important account information" |
| One email, one action | Every email should have exactly one thing it wants the reader to do |
| Write to a person, not an account | "You" not "the user". "We" not "the system". |
| Tell them what happens next | "Click the button below to confirm. The link expires in 24 hours." |
| No jargon | "Your account" not "your profile entity". "Sign in" not "authenticate". |
| Active voice | "We've sent you a link" not "A link has been sent to you" |
| Short paragraphs | 1–2 sentences max. White space is your friend in email. |
| Plain text fallback | Every HTML email should have a plain text version |
### Common email types — copy templates
- **Welcome email:**
- ```
- Subject: Welcome to [App Name], [First Name] 👋
- Preview: Here's how to get started in 2 minutes.
-
- Hi [First Name],
-
- You're in. [App Name] helps you [core value in 1 sentence].
-
- Here's the first thing to do: [single CTA — most valuable first action].
-
- [Primary Button: Get started]
-
- If you have any questions, just reply to this email.
-
- — [Name], [App Name]
- ```
-
**Password reset:**
```
Subject: Reset your [App Name] password
Preview: Your link expires in 1 hour.
Hi [First Name],
Someone requested a password reset for your [App Name] account.
If that was you, click the button below. If not, you can ignore this email.
[Primary Button: Reset my password]
This link expires in 1 hour. After that, you'll need to request a new one.
```
- **Billing confirmation:**
- ```
- Subject: Your payment of [amount] was successful
- Preview: Next billing date: [date].
-
- Hi [First Name],
-
- Your payment of [amount] for [Plan Name] went through.
-
- Amount: [amount]
- Date: [date]
- Next billing date: [date]
-
- [Button: View your invoice]
-
- Questions about your bill? Reply here and we'll sort it out.
- ```
+ **Welcome** (subject with first name, preview "get started in 2 minutes", one Get started button, "just reply" close) and **Billing confirmation** (subject "Your payment of [amount] was successful", amount/date/next billing date lines, View your invoice button): [references/copy-templates.md](references/copy-templates.md).
---
## Phase 4: Build the template (React Email) [LOW freedom — run exactly]
Install if not present:
```bash
npm install react-email @react-email/components
```
- Base template structure (illustrative — swap the indigo button and off-white ground for the brand):
- ```tsx
- // emails/welcome.tsx
- import {
- Body, Button, Container, Head, Heading, Hr, Html,
- Link, Preview, Section, Text, Img,
- } from '@react-email/components';
-
- interface WelcomeEmailProps {
- firstName: string;
- ctaUrl: string;
- }
-
- export function WelcomeEmail({ firstName, ctaUrl }: WelcomeEmailProps) {
- return (
- <Html lang="en">
- <Head />
- <Preview>Welcome to AppName — here's how to get started.</Preview>
- <Body style={body}>
- <Container style={container}>
- <Img src="https://yourdomain.com/logo.png" width="48" height="48" alt="AppName" />
- <Heading style={h1}>Welcome, {firstName}</Heading>
- <Text style={text}>
- You're in. AppName helps you [core value in one sentence].
- </Text>
- <Text style={text}>
- Here's the first thing to do:
- </Text>
- <Section style={btnContainer}>
- <Button style={button} href={ctaUrl}>
- Get started
- </Button>
- </Section>
- <Text style={text}>
- If you have any questions, just reply to this email.
- </Text>
- <Hr style={hr} />
- <Text style={footer}>
- AppName · 123 Street · City · {' '}
- <Link href="{{{UNSUBSCRIBE_URL}}}">Unsubscribe</Link>
- </Text>
- </Container>
- </Body>
- </Html>
- );
- }
-
- const body = { backgroundColor: '#f6f9fc', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' };
- const container = { backgroundColor: '#ffffff', margin: '40px auto', padding: '40px', maxWidth: '600px', borderRadius: '8px' };
- const h1 = { fontSize: '24px', fontWeight: '700', color: '#1a1a1a', marginBottom: '16px' };
- const text = { fontSize: '16px', lineHeight: '1.6', color: '#444', marginBottom: '16px' };
- const btnContainer = { textAlign: 'center' as const, marginBottom: '24px' };
- const button = { backgroundColor: '#6366f1', borderRadius: '6px', color: '#fff', fontSize: '16px', fontWeight: '600', padding: '12px 28px', textDecoration: 'none', display: 'inline-block' };
- const hr = { borderColor: '#e5e7eb', marginTop: '32px', marginBottom: '32px' };
- const footer = { fontSize: '12px', color: '#9ca3af', textAlign: 'center' as const };
- ```
+ Base template structure: `Html lang` → `Head` → `Preview` → `Body` → `Container` (600px max, white on off-white) with `Img` logo (alt text), `Heading`, `Text`, one `Button` in a centered `Section`, `Hr`, and a footer `Text` with address + `{{{UNSUBSCRIBE_URL}}}` link. Styles are inline objects; swap the indigo button and off-white ground for the brand.
+ Full `emails/welcome.tsx`: [references/react-email-and-send.md](references/react-email-and-send.md) §Base template.
### Dark mode support
```tsx
// In <Head>:
<style>{`
@media (prefers-color-scheme: dark) {
.email-body { background-color: #1a1a1a !important; }
.email-container { background-color: #2d2d2d !important; }
.email-text { color: #e5e7eb !important; }
.email-heading { color: #f9fafb !important; }
}
`}</style>
// Add className to Body, Container, Text, Heading components to match
```
---
## Phase 5: Send via provider [LOW freedom — run exactly]
### Resend (recommended for new projects)
- ```typescript
- import { Resend } from 'resend';
- import { WelcomeEmail } from '../emails/welcome';
- import { render } from '@react-email/render';
-
- const resend = new Resend(process.env.RESEND_API_KEY);
-
- await resend.emails.send({
- from: 'AppName <hello@yourdomain.com>',
- to: user.email,
- subject: `Welcome to AppName, ${user.firstName}`,
- react: <WelcomeEmail firstName={user.firstName} ctaUrl={ctaUrl} />,
- });
- ```
+ `new Resend(process.env.RESEND_API_KEY)` → `resend.emails.send({ from: 'AppName <hello@yourdomain.com>', to, subject, react: <WelcomeEmail … /> })`.
+ Code: [references/react-email-and-send.md](references/react-email-and-send.md) §Send with Resend.
### Add a Supabase Edge Function trigger (if using Supabase)
- ```typescript
- // supabase/functions/send-welcome-email/index.ts
- import { serve } from 'https://deno.land/std/http/server.ts';
- import { Resend } from 'npm:resend';
-
- serve(async (req) => {
- const { record } = await req.json(); // from a database webhook
- const resend = new Resend(Deno.env.get('RESEND_API_KEY'));
- await resend.emails.send({
- from: 'AppName <hello@yourdomain.com>',
- to: record.email,
- subject: `Welcome to AppName!`,
- html: '...', // rendered HTML
- });
- return new Response('OK');
- });
- ```
+ `supabase/functions/send-welcome-email/index.ts`: Deno `serve`, read `record` from the database webhook body, `npm:resend` with `Deno.env.get('RESEND_API_KEY')`, send rendered HTML, return `OK`.
+ Code: [references/react-email-and-send.md](references/react-email-and-send.md) §Supabase Edge Function trigger.
Deploy:
```bash
supabase functions deploy send-welcome-email --no-verify-jwt
```
---
## Phase 6: Content flags that hurt placement [LOW freedom — do not skip]
DNS auth, bounces, and List-Unsubscribe headers are `enhance-email-deliverability`.
This phase only owns copy/markup that templates can fix:
- [ ] From address uses your domain (not `noreply@gmail.com`)
- [ ] Reply-To set if replies should go somewhere useful
- [ ] No ALL-CAPS words in subject line
- [ ] No spam trigger words: "FREE", "URGENT", "ACT NOW", "GUARANTEED"
- [ ] Plain text version matches the HTML version
- [ ] Hand remaining inbox-placement work to `enhance-email-deliverability`
---
## Phase 7: Preview and test [LOW freedom — run exactly]
Start the React Email preview server:
```bash
npx email dev --dir emails --port 3001
```
Then check with Playwright:
```bash
PW="npx --yes @playwright/cli@latest"
$PW -s=email-preview open --headed http://localhost:3001
$PW -s=email-preview snapshot # verify each template renders
$PW -s=email-preview resize 375 812 # mobile — font size, tap targets
$PW -s=email-preview screenshot --filename ".playwright-mcp/email-mobile.png"
```
For cross-client testing, use Litmus or Email on Acid. Key clients to test:
- Gmail (web + Android + iOS)
- Apple Mail (macOS + iOS)
- Outlook (Windows — most restrictive CSS support)
- Samsung Mail
---
## Email templates checklist
```
Design:
- [ ] Max-width 600px
- [ ] Single column
- [ ] All styles inline (no <style> blocks except dark mode)
- [ ] Images have alt text
- [ ] Buttons ≥ 44px tall, large enough to tap
- [ ] Font size ≥ 14px on body text
- [ ] Dark mode tested
Copy:
- [ ] Subject line is specific and honest
- [ ] Preview text set and meaningful
- [ ] First sentence is the most important thing
- [ ] Exactly one primary action
- [ ] Copy sounds like a person wrote it
- [ ] No jargon or passive voice
- [ ] Plain text version exists
Deliverability (handoff — do not own here):
- [ ] From address on app domain
- [ ] Plain-text alternative present
- [ ] SPF / DKIM / DMARC / bounces / unsubscribe headers → `enhance-email-deliverability`
```