Skip to main content

How to Build Email Templates with React Email + Resend

·APIScout Team
react emailresendemail templatestutorialapi integration

How to Build Email Templates with React Email + Resend

Email HTML is notoriously awful. React Email fixes that by letting you build email templates with React components — version-controlled, type-safe, and testable. Pair it with Resend for delivery and you get a modern email stack.

What You'll Build

  • Reusable email component library
  • Welcome, receipt, and notification templates
  • Live preview during development
  • Responsive email layout
  • Send via Resend API

Prerequisites: React/Next.js, Resend account (free: 3,000 emails/month).

1. Setup

npm install @react-email/components resend
npm install -D react-email

Add to package.json:

{
  "scripts": {
    "email:dev": "email dev --dir emails",
    "email:export": "email export --dir emails --outDir out"
  }
}

2. Base Layout

// emails/components/Layout.tsx
import {
  Body, Container, Head, Html, Preview,
  Section, Text, Link, Hr,
} from '@react-email/components';

interface LayoutProps {
  preview: string;
  children: React.ReactNode;
}

export function Layout({ preview, children }: LayoutProps) {
  return (
    <Html>
      <Head />
      <Preview>{preview}</Preview>
      <Body style={body}>
        <Container style={container}>
          {children}
          <Hr style={hr} />
          <Text style={footer}>
            Your App Inc. · 123 Main St · San Francisco, CA 94105
            {'\n'}
            <Link href="https://yourapp.com/unsubscribe" style={link}>
              Unsubscribe
            </Link>
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

const body = {
  backgroundColor: '#f6f9fc',
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
};
const container = {
  margin: '0 auto',
  padding: '40px 20px',
  maxWidth: '560px',
  backgroundColor: '#ffffff',
  borderRadius: '8px',
};
const hr = { borderColor: '#e6ebf1', margin: '32px 0' };
const footer = { color: '#8898aa', fontSize: '12px', lineHeight: '20px', textAlign: 'center' as const };
const link = { color: '#556cd6' };

3. Email Templates

Welcome Email

// emails/WelcomeEmail.tsx
import { Heading, Text, Button, Section, Img } from '@react-email/components';
import { Layout } from './components/Layout';

interface WelcomeEmailProps {
  name: string;
  dashboardUrl: string;
}

export default function WelcomeEmail({
  name = 'there',
  dashboardUrl = 'https://yourapp.com/dashboard',
}: WelcomeEmailProps) {
  return (
    <Layout preview={`Welcome to Your App, ${name}!`}>
      <Img
        src="https://yourapp.com/logo.png"
        width={120}
        height={36}
        alt="Your App"
        style={{ margin: '0 auto 24px' }}
      />
      <Heading style={heading}>Welcome, {name}! 🎉</Heading>
      <Text style={text}>
        Your account is ready. Here are 3 things to do first:
      </Text>
      <Text style={text}>
        1. <strong>Complete your profile</strong> — Add your company and role{'\n'}
        2. <strong>Connect your first API</strong> — Import your API keys{'\n'}
        3. <strong>Invite your team</strong> — Collaborate with colleagues
      </Text>
      <Section style={buttonSection}>
        <Button style={button} href={dashboardUrl}>
          Go to Dashboard →
        </Button>
      </Section>
      <Text style={subtle}>
        Need help? Reply to this email or visit our{' '}
        <a href="https://yourapp.com/help" style={{ color: '#556cd6' }}>help center</a>.
      </Text>
    </Layout>
  );
}

const heading = { fontSize: '24px', fontWeight: 'bold', color: '#1a1a1a', margin: '0 0 16px' };
const text = { fontSize: '16px', color: '#4a4a4a', lineHeight: '26px', margin: '0 0 16px' };
const subtle = { fontSize: '14px', color: '#8a8a8a', lineHeight: '22px' };
const buttonSection = { textAlign: 'center' as const, margin: '32px 0' };
const button = {
  backgroundColor: '#2563eb',
  color: '#ffffff',
  padding: '12px 32px',
  borderRadius: '6px',
  fontSize: '16px',
  fontWeight: 'bold',
  textDecoration: 'none',
};

Receipt Email

// emails/ReceiptEmail.tsx
import { Heading, Text, Section, Row, Column, Hr } from '@react-email/components';
import { Layout } from './components/Layout';

interface LineItem {
  name: string;
  quantity: number;
  price: number;
}

interface ReceiptEmailProps {
  customerName: string;
  orderId: string;
  items: LineItem[];
  total: number;
  date: string;
}

export default function ReceiptEmail({
  customerName = 'Customer',
  orderId = '12345',
  items = [{ name: 'Pro Plan (Monthly)', quantity: 1, price: 29.00 }],
  total = 29.00,
  date = '2026-03-08',
}: ReceiptEmailProps) {
  return (
    <Layout preview={`Receipt for order #${orderId}`}>
      <Heading style={heading}>Receipt</Heading>
      <Text style={text}>Hi {customerName}, thanks for your purchase!</Text>

      <Section style={orderInfo}>
        <Row>
          <Column><Text style={label}>Order</Text><Text style={value}>#{orderId}</Text></Column>
          <Column><Text style={label}>Date</Text><Text style={value}>{date}</Text></Column>
        </Row>
      </Section>

      <Hr style={hr} />

      {items.map((item, i) => (
        <Row key={i} style={lineItem}>
          <Column style={{ width: '70%' }}>
            <Text style={itemName}>{item.name}</Text>
            <Text style={itemQty}>Qty: {item.quantity}</Text>
          </Column>
          <Column style={{ width: '30%', textAlign: 'right' }}>
            <Text style={itemPrice}>${item.price.toFixed(2)}</Text>
          </Column>
        </Row>
      ))}

      <Hr style={hr} />

      <Row>
        <Column style={{ width: '70%' }}>
          <Text style={totalLabel}>Total</Text>
        </Column>
        <Column style={{ width: '30%', textAlign: 'right' }}>
          <Text style={totalValue}>${total.toFixed(2)}</Text>
        </Column>
      </Row>
    </Layout>
  );
}

const heading = { fontSize: '24px', fontWeight: 'bold', color: '#1a1a1a' };
const text = { fontSize: '16px', color: '#4a4a4a' };
const orderInfo = { backgroundColor: '#f6f9fc', padding: '16px', borderRadius: '6px', margin: '16px 0' };
const label = { fontSize: '12px', color: '#8a8a8a', margin: '0' };
const value = { fontSize: '14px', color: '#1a1a1a', fontWeight: 'bold', margin: '4px 0 0' };
const hr = { borderColor: '#e6ebf1', margin: '16px 0' };
const lineItem = { margin: '8px 0' };
const itemName = { fontSize: '14px', color: '#1a1a1a', margin: '0' };
const itemQty = { fontSize: '12px', color: '#8a8a8a', margin: '2px 0 0' };
const itemPrice = { fontSize: '14px', color: '#1a1a1a', margin: '0' };
const totalLabel = { fontSize: '16px', fontWeight: 'bold', color: '#1a1a1a' };
const totalValue = { fontSize: '16px', fontWeight: 'bold', color: '#1a1a1a' };

4. Preview

Run the development server to preview templates:

npm run email:dev

Opens at http://localhost:3000 with live reload, device preview (mobile/desktop), and source view.

5. Send with Resend

// lib/email.ts
import { Resend } from 'resend';
import WelcomeEmail from '@/emails/WelcomeEmail';
import ReceiptEmail from '@/emails/ReceiptEmail';

const resend = new Resend(process.env.RESEND_API_KEY);

export async function sendWelcomeEmail(to: string, name: string) {
  return resend.emails.send({
    from: 'Your App <hello@yourdomain.com>',
    to,
    subject: `Welcome to Your App, ${name}!`,
    react: WelcomeEmail({ name, dashboardUrl: 'https://yourapp.com/dashboard' }),
  });
}

export async function sendReceipt(to: string, order: any) {
  return resend.emails.send({
    from: 'Your App <billing@yourdomain.com>',
    to,
    subject: `Receipt for order #${order.id}`,
    react: ReceiptEmail({
      customerName: order.customerName,
      orderId: order.id,
      items: order.items,
      total: order.total,
      date: new Date().toLocaleDateString(),
    }),
  });
}

Email Client Compatibility

FeatureGmailOutlookApple MailYahoo
CSS inline styles
Flexbox
Grid
Media queries
Web fonts

React Email components handle these compatibility issues — they use tables and inline styles under the hood.

Common Mistakes

MistakeImpactFix
Using CSS classesStyles stripped by email clientsUse inline styles (React Email handles this)
Images without alt textBroken experience when images blockedAlways include alt text
No plain text fallbackSome clients can't render HTMLProvide text prop alongside react
Too wide layoutBreaks on mobileMax-width 600px for container
Dark mode not consideredUnreadable on dark backgroundsTest with color-scheme: light dark

Building email templates? Compare Resend vs SendGrid vs Postmark on APIScout — which email API pairs best with React Email.

Comments