| name | email-designer |
| description | Email template designer for Sunrise. Builds React Email templates
that render reliably across Gmail, Outlook, Apple Mail, and Yahoo:
inline styles or `pixelBasedPreset` Tailwind, mobile-responsive
600px max-width, clear single-CTA structure, dark-mode safe. Use
when creating new templates under `emails/` or updating existing
ones (welcome, verify-email, reset-password, invitation,
contact-notification, workflow-notification, escalation-notification).
|
Email Designer Skill
Production-ready email templates in Sunrise use React Email rendered server-side and dispatched via Resend. The seven templates already in emails/ (welcome, verify-email, reset-password, invitation, contact-notification, workflow-notification, escalation-notification) are the canonical references — copy their structure when adding a new one.
Technology Stack
- Framework: React Email (
@react-email/components)
- Rendering:
@react-email/render (server-side HTML generation)
- Delivery: Resend
- Styling: inline styles, or Tailwind via
pixelBasedPreset for email-safe utility classes
- Testing: Vitest (template rendering tests in
tests/unit/emails/)
Core Design Principles
- Mobile-First: Design for mobile, enhance for desktop
- Clear CTAs: Single primary action per email, obvious button
- Accessibility: Proper alt text, semantic HTML, readable fonts
- Cross-Client: Test in Gmail, Outlook, Apple Mail, Yahoo
- Brand Consistency: Use consistent colors, fonts, spacing
- Graceful Degradation: Work without images, CSS support
4-Step Workflow
Step 1: Understand Requirements
Gather information:
- Email purpose (welcome, verification, password reset, invitation, etc.)
- Required data (user name, links, expiry dates, etc.)
- Primary CTA (button text and action)
- Tone (formal, friendly, urgent, informative)
Categorize template type:
- Transactional: Verification, password reset, receipts
- Notification: Welcome, invitation, status updates
- Marketing: Announcements, newsletters (not currently in the template set)
Step 2: Query React Email Documentation
ALWAYS use Context7 for latest React Email patterns:
mcp__context7__query_docs({
libraryId: '/react.email/llmstxt',
query: 'button component link responsive layout container section',
});
mcp__context7__query_docs({
libraryId: '/react.email/llmstxt',
query: 'tailwind pixelBasedPreset email client compatibility',
});
Key components from @react-email/components:
- Structure:
Html, Head, Body, Preview
- Layout:
Container, Section, Row, Column
- Typography:
Text, Heading, Hr
- CTAs:
Button, Link
- Media:
Img
- Styling:
Tailwind (with pixelBasedPreset for email compatibility)
Step 3: Build Email Template
File naming: emails/[purpose].tsx
- Examples:
welcome.tsx, verify-email.tsx, reset-password.tsx, invitation.tsx
Template structure (React Email latest patterns):
import * as React from 'react';
import {
Html,
Head,
Body,
Container,
Section,
Text,
Heading,
Button,
Hr,
Link,
Img,
Preview,
} from '@react-email/components';
interface EmailNameProps {
userName: string;
actionUrl: string;
expiresAt?: Date;
}
export default function EmailName({ userName, actionUrl, expiresAt }: EmailNameProps) {
const previewText = 'Brief preview text shown in inbox';
return (
<Html lang="en">
<Head />
<Preview>{previewText}</Preview>
<Body style={main}>
<Container style={container}>
{/* Header */}
<Section style={header}>
<Img
src={`${process.env.NEXT_PUBLIC_APP_URL}/logo.png`}
width="120"
height="40"
alt="Sunrise Logo"
/>
</Section>
{/* Content */}
<Section style={content}>
<Heading style={h1}>Email Heading</Heading>
<Text style={text}>Hi {userName},</Text>
<Text style={text}>Email body content explaining the purpose and action.</Text>
{/* Primary CTA */}
<Section style={buttonContainer}>
<Button href={actionUrl} style={button}>
Action Button Text
</Button>
</Section>
{/* Alternative text link */}
<Text style={text}>
Or copy and paste this link:{' '}
<Link href={actionUrl} style={link}>
{actionUrl}
</Link>
</Text>
{/* Expiry notice if applicable */}
{expiresAt && (
<Text style={notice}>
This link expires on {expiresAt.toLocaleDateString()} at{' '}
{expiresAt.toLocaleTimeString()}.
</Text>
)}
</Section>
{/* Footer */}
<Hr style={hr} />
<Section style={footer}>
<Text style={footerText}>
© {new Date().getFullYear()} Sunrise. All rights reserved.
</Text>
<Text style={footerText}>
<Link href={`${process.env.NEXT_PUBLIC_APP_URL}/privacy`} style={link}>
Privacy Policy
</Link>
{' | '}
<Link href={`${process.env.NEXT_PUBLIC_APP_URL}/terms`} style={link}>
Terms of Service
</Link>
</Text>
</Section>
</Container>
</Body>
</Html>
);
}
const main = {
backgroundColor: '#f6f9fc',
fontFamily:
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
};
const container = {
backgroundColor: '#ffffff',
margin: '0 auto',
padding: '20px 0 48px',
marginBottom: '64px',
maxWidth: '600px',
};
const header = {
padding: '24px',
textAlign: 'center' as const,
};
const content = {
padding: '0 48px',
};
const h1 = {
color: '#1f2937',
fontSize: '24px',
fontWeight: '600',
lineHeight: '32px',
margin: '16px 0',
};
const text = {
color: '#374151',
fontSize: '16px',
lineHeight: '24px',
margin: '16px 0',
};
const buttonContainer = {
padding: '27px 0',
textAlign: 'center' as const,
};
const button = {
backgroundColor: '#2563eb',
borderRadius: '8px',
color: '#ffffff',
fontSize: '16px',
fontWeight: '600',
textDecoration: 'none',
textAlign: 'center' as const,
display: 'block',
padding: '12px 20px',
};
const link = {
color: '#2563eb',
textDecoration: 'underline',
};
const notice = {
color: '#6b7280',
fontSize: '14px',
lineHeight: '20px',
margin: '16px 0',
textAlign: 'center' as const,
};
const hr = {
borderColor: '#e5e7eb',
margin: '20px 0',
};
const footer = {
padding: '0 48px',
};
const footerText = {
color: '#6b7280',
fontSize: '12px',
lineHeight: '16px',
margin: '8px 0',
textAlign: 'center' as const,
};
Key Pattern Updates:
- ✅ Export
default function (not named export)
- ✅
Html lang="en" attribute for accessibility
- ✅
import * as React from 'react' for React Email compatibility
- ✅ Use
process.env.NEXT_PUBLIC_APP_URL for dynamic URLs
- ✅ Inline styles with proper TypeScript types (
as const)
- ✅ Button
href as first prop (React Email pattern)
Step 4: Create Tests
File: __tests__/unit/emails/[email-name].test.tsx
Pattern (React Email latest):
import * as React from 'react';
import { describe, it, expect } from 'vitest';
import { render } from '@react-email/render';
import WelcomeEmail from '@/emails/welcome';
describe('WelcomeEmail', () => {
it('should render with user name', async () => {
const html = await render(<WelcomeEmail userName="John Doe" userEmail="john@example.com" />);
expect(html).toContain('John Doe');
expect(html).toContain('Welcome to Sunrise');
});
it('should include preview text', async () => {
const html = await render(<WelcomeEmail userName="John Doe" userEmail="john@example.com" />);
expect(html).toContain('Welcome to Sunrise');
});
it('should have proper HTML structure', async () => {
const html = await render(<WelcomeEmail userName="John Doe" userEmail="john@example.com" />);
expect(html).toContain('<!DOCTYPE html');
expect(html).toContain('<html');
expect(html).toContain('lang="en"');
expect(html).toContain('</html>');
});
it('should include CTA button with href', async () => {
const html = await render(<WelcomeEmail userName="John Doe" userEmail="john@example.com" />);
expect(html).toContain('href=');
expect(html).toContain('Get Started');
});
it('should render without errors', () => {
expect(() => {
render(<WelcomeEmail userName="John Doe" userEmail="john@example.com" />);
}).not.toThrow();
});
});
Key Test Patterns:
- ✅ Import default export (not named export)
- ✅ Use
render() from @react-email/render
- ✅ Test returns string HTML (use
.toContain())
- ✅ Verify HTML structure, preview text, dynamic content
- ✅ Test that rendering doesn't throw errors
Email Template Types
1. Welcome Email
Purpose: Greet new users after signup
Props: userName, userEmail
CTA: "Get Started" → Dashboard
Tone: Friendly, enthusiastic
2. Email Verification
Purpose: Verify email address
Props: userName, verificationUrl, expiresAt
CTA: "Verify Email" → Verification handler
Tone: Clear, helpful
Critical: Include expiry notice
3. Password Reset
Purpose: Allow users to reset forgotten password
Props: userName, resetUrl, expiresAt
CTA: "Reset Password" → Reset handler
Tone: Helpful, secure
Critical: Security notice, expiry warning, "didn't request this?" text
4. Invitation
Purpose: Invite new user to join
Props: inviterName, inviteeName, inviteeEmail, invitationUrl, expiresAt
CTA: "Accept Invitation" → Invitation handler
Tone: Personal, welcoming
Critical: Who invited them, what happens next, expiry notice
Design Best Practices
Colors
const primary = '#2563eb';
const textPrimary = '#1f2937';
const textSecondary = '#374151';
const textMuted = '#6b7280';
const bgPrimary = '#ffffff';
const bgSecondary = '#f6f9fc';
const borderColor = '#e5e7eb';
Typography
const fontFamily =
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif';
const h1 = { fontSize: '24px', lineHeight: '32px', fontWeight: '600' };
const h2 = { fontSize: '20px', lineHeight: '28px', fontWeight: '600' };
const body = { fontSize: '16px', lineHeight: '24px' };
const small = { fontSize: '14px', lineHeight: '20px' };
const tiny = { fontSize: '12px', lineHeight: '16px' };
Spacing
const containerPadding = '0 48px';
const sectionMargin = '32px 0';
const textMargin = '16px 0';
const buttonPadding = '12px 20px';
Buttons
const buttonPrimary = {
backgroundColor: '#2563eb',
color: '#ffffff',
padding: '12px 20px',
borderRadius: '8px',
fontSize: '16px',
fontWeight: '600',
textDecoration: 'none',
display: 'block',
textAlign: 'center' as const,
};
const buttonSecondary = {
backgroundColor: '#ffffff',
color: '#2563eb',
border: '1px solid #2563eb',
padding: '12px 20px',
borderRadius: '8px',
fontSize: '16px',
fontWeight: '600',
textDecoration: 'none',
display: 'block',
textAlign: 'center' as const,
};
Cross-Client Compatibility
Email Client Support Matrix
- Gmail (Web, iOS, Android) - Good CSS support
- Outlook (Desktop, Web) - Limited CSS, use tables for layout
- Apple Mail (iOS, macOS) - Excellent CSS support
- Yahoo Mail - Good CSS support
- Thunderbird - Good CSS support
Compatibility Rules
DO:
- Use inline styles (not
<style> tags or external CSS)
- Use tables for complex layouts (fallback for Outlook)
- Use web-safe fonts
- Provide alt text for all images
- Include plain text fallbacks for links
- Test with images disabled
DON'T:
- Don't rely on
<style> tags (Outlook strips them)
- Don't use
float or position (inconsistent support)
- Don't use background images (Outlook doesn't support)
- Don't use custom fonts (may not load)
- Don't assume JavaScript works (it doesn't in email)
Mobile Responsiveness
const container = {
maxWidth: '600px',
width: '100%',
};
const heading = {
fontSize: '24px',
'@media only screen and (max-width: 480px)': {
fontSize: '20px !important',
},
};
const content = {
padding: '0 48px',
'@media only screen and (max-width: 480px)': {
padding: '0 24px !important',
},
};
Testing Checklist
Rendering Tests:
Content Tests:
Visual Tests (Manual):
Accessibility:
Development Workflow
Preview in Browser:
npm run email:dev
Export Static HTML:
npm run email:export
Test Rendering:
npm run test -- emails/
Common Patterns
Conditional Content
{expiresAt && (
<Text style={notice}>
This link expires on {expiresAt.toLocaleDateString()}.
</Text>
)}
{userName ? `Hi ${userName}` : 'Hi there'}
Multiple CTAs
{}
<Button style={buttonPrimary} href={primaryUrl}>
Primary Action
</Button>
{}
<Button style={buttonSecondary} href={secondaryUrl}>
Secondary Action
</Button>
Lists
<ul style={{ margin: '16px 0', paddingLeft: '20px' }}>
<li style={{ margin: '8px 0' }}>List item 1</li>
<li style={{ margin: '8px 0' }}>List item 2</li>
<li style={{ margin: '8px 0' }}>List item 3</li>
</ul>
Social Links (Footer)
<Section style={{ textAlign: 'center', margin: '24px 0' }}>
<Link href="https://twitter.com/yourapp" style={socialLink}>
<Img src="https://yourdomain.com/icons/twitter.png" width="24" height="24" alt="Twitter" />
</Link>
<Link href="https://facebook.com/yourapp" style={socialLink}>
<Img src="https://yourdomain.com/icons/facebook.png" width="24" height="24" alt="Facebook" />
</Link>
</Section>
Related Documentation
.context/email/overview.md — email system architecture and sending pipeline
emails/ — production templates (welcome, verify-email, reset-password, invitation, contact-notification, workflow-notification, escalation-notification)
lib/email/send.ts — sendEmail helper that renders a template to HTML and dispatches via Resend
tests/unit/emails/ — rendering tests for the existing templates; mirror this layout for new ones
- React Email documentation via Context7 (
/react.email/llmstxt)
- Resend documentation via Context7 (
/resend/react-email)
Usage Examples
Create welcome email:
User: "Create a welcome email template for new users"
Assistant: [Creates emails/welcome.tsx with friendly greeting, dashboard CTA, tests]
Create verification email:
User: "Build an email verification template with expiry warning"
Assistant: [Creates emails/verify-email.tsx with verification button, expiry notice, security text, tests]
Update existing template:
User: "Add social links to the invitation email footer"
Assistant: [Updates emails/invitation.tsx footer with Twitter/Facebook links, updates tests]