Email Templates Pattern

Build beautiful, reusable email templates with React Email that render consistently across all email clients.

Overview#

React Email provides a modern way to build email templates using React components. Templates are rendered to HTML that works across all major email clients, including Gmail, Outlook, and Apple Mail.

When to use:

  • Brand-consistent transactional emails
  • Complex email layouts
  • Reusable email components
  • Type-safe email props
  • Team collaboration on email design

Key features:

  • React component syntax
  • Email client compatibility
  • Hot-reload development
  • Reusable components
  • TypeScript support

Code Example#

Basic Template Structure#

1// emails/welcome.tsx 2import { 3 Body, 4 Button, 5 Container, 6 Head, 7 Heading, 8 Html, 9 Preview, 10 Section, 11 Text 12} from '@react-email/components' 13 14interface WelcomeEmailProps { 15 name: string 16 actionUrl: string 17} 18 19export function WelcomeEmail({ name, actionUrl }: WelcomeEmailProps) { 20 return ( 21 <Html> 22 <Head /> 23 <Preview>Welcome to Our App</Preview> 24 <Body style={main}> 25 <Container style={container}> 26 <Heading style={h1}>Welcome, {name}!</Heading> 27 <Text style={text}> 28 Thanks for signing up. We are excited to have you on board. 29 </Text> 30 <Section style={buttonContainer}> 31 <Button style={button} href={actionUrl}> 32 Get Started 33 </Button> 34 </Section> 35 <Text style={footer}> 36 If you did not create an account, you can ignore this email. 37 </Text> 38 </Container> 39 </Body> 40 </Html> 41 ) 42} 43 44const main = { 45 backgroundColor: '#f6f9fc', 46 fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' 47} 48 49const container = { 50 backgroundColor: '#ffffff', 51 margin: '0 auto', 52 padding: '40px 20px', 53 maxWidth: '560px' 54} 55 56const h1 = { 57 color: '#1a1a1a', 58 fontSize: '24px', 59 fontWeight: '600', 60 margin: '0 0 20px' 61} 62 63const text = { 64 color: '#4a4a4a', 65 fontSize: '16px', 66 lineHeight: '1.5', 67 margin: '0 0 20px' 68} 69 70const buttonContainer = { 71 textAlign: 'center' as const, 72 margin: '30px 0' 73} 74 75const button = { 76 backgroundColor: '#000000', 77 borderRadius: '6px', 78 color: '#ffffff', 79 fontSize: '16px', 80 fontWeight: '600', 81 padding: '12px 24px', 82 textDecoration: 'none' 83} 84 85const footer = { 86 color: '#898989', 87 fontSize: '14px', 88 margin: '30px 0 0' 89} 90 91export default WelcomeEmail

Shared Email Layout#

1// emails/components/Layout.tsx 2import { 3 Body, 4 Container, 5 Head, 6 Html, 7 Img, 8 Preview, 9 Section, 10 Text 11} from '@react-email/components' 12 13interface LayoutProps { 14 preview: string 15 children: React.ReactNode 16} 17 18export function EmailLayout({ preview, children }: LayoutProps) { 19 return ( 20 <Html> 21 <Head /> 22 <Preview>{preview}</Preview> 23 <Body style={main}> 24 <Container style={container}> 25 {/* Header */} 26 <Section style={header}> 27 <Img 28 src={`${process.env.NEXT_PUBLIC_APP_URL}/logo.png`} 29 width="120" 30 height="40" 31 alt="Company Logo" 32 /> 33 </Section> 34 35 {/* Content */} 36 <Section style={content}> 37 {children} 38 </Section> 39 40 {/* Footer */} 41 <Section style={footerSection}> 42 <Text style={footerText}> 43 Company Inc, 123 Main St, City, State 12345 44 </Text> 45 <Text style={footerLinks}> 46 <a href="#" style={link}>Unsubscribe</a> 47 {' | '} 48 <a href="#" style={link}>Privacy Policy</a> 49 </Text> 50 </Section> 51 </Container> 52 </Body> 53 </Html> 54 ) 55} 56 57const main = { 58 backgroundColor: '#f4f4f5', 59 fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' 60} 61 62const container = { 63 margin: '0 auto', 64 maxWidth: '600px' 65} 66 67const header = { 68 backgroundColor: '#ffffff', 69 padding: '20px', 70 textAlign: 'center' as const, 71 borderBottom: '1px solid #e5e7eb' 72} 73 74const content = { 75 backgroundColor: '#ffffff', 76 padding: '40px 20px' 77} 78 79const footerSection = { 80 padding: '20px', 81 textAlign: 'center' as const 82} 83 84const footerText = { 85 color: '#71717a', 86 fontSize: '12px', 87 margin: '0 0 10px' 88} 89 90const footerLinks = { 91 color: '#71717a', 92 fontSize: '12px' 93} 94 95const link = { 96 color: '#3b82f6', 97 textDecoration: 'underline' 98}

Invoice Email Template#

1// emails/invoice.tsx 2import { 3 Column, 4 Heading, 5 Hr, 6 Row, 7 Section, 8 Text 9} from '@react-email/components' 10import { EmailLayout } from './components/Layout' 11 12interface InvoiceItem { 13 name: string 14 quantity: number 15 price: number 16} 17 18interface InvoiceEmailProps { 19 invoiceNumber: string 20 date: string 21 items: InvoiceItem[] 22 total: number 23 customerName: string 24} 25 26export function InvoiceEmail({ 27 invoiceNumber, 28 date, 29 items, 30 total, 31 customerName 32}: InvoiceEmailProps) { 33 const formatCurrency = (amount: number) => 34 new Intl.NumberFormat('en-US', { 35 style: 'currency', 36 currency: 'USD' 37 }).format(amount) 38 39 return ( 40 <EmailLayout preview={`Invoice #${invoiceNumber}`}> 41 <Heading style={h1}>Invoice #{invoiceNumber}</Heading> 42 <Text style={text}>Hi {customerName},</Text> 43 <Text style={text}> 44 Thank you for your purchase. Here is your invoice: 45 </Text> 46 47 <Section style={invoiceInfo}> 48 <Text style={infoText}> 49 <strong>Invoice Date:</strong> {date} 50 </Text> 51 <Text style={infoText}> 52 <strong>Invoice Number:</strong> {invoiceNumber} 53 </Text> 54 </Section> 55 56 <Hr style={hr} /> 57 58 {/* Table Header */} 59 <Row style={tableHeader}> 60 <Column style={colItem}>Item</Column> 61 <Column style={colQty}>Qty</Column> 62 <Column style={colPrice}>Price</Column> 63 </Row> 64 65 {/* Table Rows */} 66 {items.map((item, index) => ( 67 <Row key={index} style={tableRow}> 68 <Column style={colItem}>{item.name}</Column> 69 <Column style={colQty}>{item.quantity}</Column> 70 <Column style={colPrice}> 71 {formatCurrency(item.price * item.quantity)} 72 </Column> 73 </Row> 74 ))} 75 76 <Hr style={hr} /> 77 78 {/* Total */} 79 <Row style={totalRow}> 80 <Column style={colItem}> 81 <strong>Total</strong> 82 </Column> 83 <Column style={colQty}></Column> 84 <Column style={colPrice}> 85 <strong>{formatCurrency(total)}</strong> 86 </Column> 87 </Row> 88 </EmailLayout> 89 ) 90} 91 92const h1 = { 93 fontSize: '24px', 94 fontWeight: '600', 95 color: '#1a1a1a', 96 margin: '0 0 20px' 97} 98 99const text = { 100 fontSize: '16px', 101 color: '#4a4a4a', 102 lineHeight: '1.5', 103 margin: '0 0 20px' 104} 105 106const invoiceInfo = { 107 backgroundColor: '#f9fafb', 108 padding: '16px', 109 borderRadius: '8px', 110 marginBottom: '20px' 111} 112 113const infoText = { 114 fontSize: '14px', 115 color: '#4a4a4a', 116 margin: '0 0 8px' 117} 118 119const hr = { 120 borderColor: '#e5e7eb', 121 margin: '20px 0' 122} 123 124const tableHeader = { 125 backgroundColor: '#f3f4f6', 126 padding: '12px' 127} 128 129const tableRow = { 130 padding: '12px 0', 131 borderBottom: '1px solid #e5e7eb' 132} 133 134const totalRow = { 135 padding: '12px 0' 136} 137 138const colItem = { 139 width: '60%', 140 textAlign: 'left' as const 141} 142 143const colQty = { 144 width: '15%', 145 textAlign: 'center' as const 146} 147 148const colPrice = { 149 width: '25%', 150 textAlign: 'right' as const 151} 152 153export default InvoiceEmail

Notification Email with Action#

1// emails/notification.tsx 2import { Button, Heading, Text } from '@react-email/components' 3import { EmailLayout } from './components/Layout' 4 5interface NotificationEmailProps { 6 title: string 7 message: string 8 actionLabel?: string 9 actionUrl?: string 10} 11 12export function NotificationEmail({ 13 title, 14 message, 15 actionLabel, 16 actionUrl 17}: NotificationEmailProps) { 18 return ( 19 <EmailLayout preview={title}> 20 <Heading style={h1}>{title}</Heading> 21 <Text style={text}>{message}</Text> 22 23 {actionLabel && actionUrl && ( 24 <Button style={button} href={actionUrl}> 25 {actionLabel} 26 </Button> 27 )} 28 </EmailLayout> 29 ) 30} 31 32const h1 = { 33 fontSize: '20px', 34 fontWeight: '600', 35 color: '#1a1a1a', 36 margin: '0 0 16px' 37} 38 39const text = { 40 fontSize: '16px', 41 color: '#4a4a4a', 42 lineHeight: '1.5', 43 margin: '0 0 24px' 44} 45 46const button = { 47 backgroundColor: '#2563eb', 48 borderRadius: '6px', 49 color: '#ffffff', 50 fontSize: '14px', 51 fontWeight: '600', 52 padding: '10px 20px', 53 textDecoration: 'none' 54} 55 56export default NotificationEmail

Development Server#

1# Install React Email CLI 2npm install -D react-email 3 4# Add script to package.json 5{ 6 "scripts": { 7 "email:dev": "email dev" 8 } 9} 10 11# Run development server 12npm run email:dev

Rendering Templates#

1// lib/email/render.ts 2import { render } from '@react-email/render' 3import WelcomeEmail from '@/emails/welcome' 4import InvoiceEmail from '@/emails/invoice' 5import NotificationEmail from '@/emails/notification' 6 7type EmailTemplates = { 8 welcome: typeof WelcomeEmail 9 invoice: typeof InvoiceEmail 10 notification: typeof NotificationEmail 11} 12 13const templates: EmailTemplates = { 14 welcome: WelcomeEmail, 15 invoice: InvoiceEmail, 16 notification: NotificationEmail 17} 18 19export async function renderEmail<T extends keyof EmailTemplates>( 20 template: T, 21 props: Parameters<EmailTemplates[T]>[0] 22): Promise<string> { 23 const Template = templates[template] 24 return render(Template(props as any)) 25} 26 27// Usage 28const html = await renderEmail('welcome', { 29 name: 'John', 30 actionUrl: 'https://example.com' 31})

Usage Instructions#

  1. Install React Email: npm install @react-email/components react-email
  2. Create email directory: Add templates to emails/ folder
  3. Build components: Create reusable layout and style components
  4. Run dev server: Use npx email dev for live preview
  5. Render and send: Use render() to convert to HTML for sending

Best Practices#

  1. Use inline styles - Email clients require inline CSS
  2. Create shared layouts - Reuse header/footer across templates
  3. Define style constants - Keep styles consistent and maintainable
  4. Test extensively - Preview in multiple email clients
  5. Use TypeScript - Define props interfaces for type safety
  6. Keep templates simple - Complex layouts break in some clients
  7. Use tables for layout - More reliable than flexbox/grid
  8. Provide fallback fonts - Use web-safe font stacks