React Email lets you build email templates with React components. Here is how to create reusable, responsive emails.
Step 1: Install React Email
pnpm add @react-email/components react-email
Step 2: Email Layout Component
// emails/components/Layout.tsx
import {
Html,
Head,
Body,
Container,
Section,
Img,
Text,
Link,
Hr,
Preview,
} from "@react-email/components";
interface LayoutProps {
preview: string;
children: React.ReactNode;
}
export function EmailLayout({ preview, children }: LayoutProps) {
return (
<Html>
<Head />
<Preview>{preview}</Preview>
<Body style={body}>
<Container style={container}>
{/* Header */}
<Section style={header}>
<Img
src="https://rcbsoftware.com/logos/logo.png"
width={120}
height={32}
alt="RCB Software"
/>
</Section>
{/* Content */}
<Section style={content}>{children}</Section>
{/* Footer */}
<Hr style={hr} />
<Section style={footer}>
<Text style={footerText}>
RCB Software — Custom web and software solutions
</Text>
<Text style={footerLinks}>
<Link href="https://rcbsoftware.com" style={link}>
Website
</Link>
{" · "}
<Link href="https://rcbsoftware.com/contact" style={link}>
Contact
</Link>
{" · "}
<Link href="%unsubscribe_url%" style={link}>
Unsubscribe
</Link>
</Text>
</Section>
</Container>
</Body>
</Html>
);
}
const body = {
backgroundColor: "#f6f9fc",
fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
};
const container = {
backgroundColor: "#ffffff",
margin: "0 auto",
padding: "0",
maxWidth: "600px",
borderRadius: "8px",
overflow: "hidden" as const,
};
const header = {
backgroundColor: "#0f172a",
padding: "24px 32px",
};
const content = {
padding: "32px",
};
const hr = {
borderColor: "#e5e7eb",
margin: "0",
};
const footer = {
padding: "24px 32px",
};
const footerText = {
color: "#6b7280",
fontSize: "12px",
lineHeight: "20px",
};
const footerLinks = {
color: "#6b7280",
fontSize: "12px",
};
const link = {
color: "#3b82f6",
textDecoration: "none" as const,
};
Step 3: Welcome Email
// emails/WelcomeEmail.tsx
import { Text, Button, Section, Heading } from "@react-email/components";
import { EmailLayout } from "./components/Layout";
interface WelcomeEmailProps {
name: string;
loginUrl: string;
}
export default function WelcomeEmail({ name, loginUrl }: WelcomeEmailProps) {
return (
<EmailLayout preview={`Welcome to RCB Software, ${name}!`}>
<Heading as="h1" style={heading}>
Welcome aboard, {name}!
</Heading>
<Text style={text}>
We are excited to have you. Your account is ready, and you can start
exploring everything we have to offer right away.
</Text>
<Section style={{ textAlign: "center" as const, marginTop: "32px" }}>
<Button style={button} href={loginUrl}>
Get Started
</Button>
</Section>
<Text style={text}>
Here is what you can do next:
</Text>
<ul>
<li style={listItem}>Complete your profile setup</li>
<li style={listItem}>Explore the dashboard</li>
<li style={listItem}>Create your first project</li>
</ul>
<Text style={text}>
If you have any questions, just reply to this email — we are always happy to help.
</Text>
<Text style={signoff}>
Best,
<br />
The RCB Software Team
</Text>
</EmailLayout>
);
}
const heading = {
fontSize: "24px",
fontWeight: "bold" as const,
color: "#0f172a",
lineHeight: "32px",
};
const text = {
fontSize: "14px",
lineHeight: "24px",
color: "#374151",
};
const button = {
backgroundColor: "#3b82f6",
borderRadius: "6px",
color: "#ffffff",
fontSize: "14px",
fontWeight: "600" as const,
padding: "12px 24px",
textDecoration: "none" as const,
};
const listItem = {
fontSize: "14px",
lineHeight: "28px",
color: "#374151",
};
const signoff = {
fontSize: "14px",
color: "#374151",
marginTop: "32px",
};
Step 4: Invoice Email
// emails/InvoiceEmail.tsx
import { Text, Section, Row, Column, Heading, Hr, Button } from "@react-email/components";
import { EmailLayout } from "./components/Layout";
interface LineItem {
description: string;
quantity: number;
unitPrice: number;
}
interface InvoiceEmailProps {
customerName: string;
invoiceNumber: string;
dueDate: string;
items: LineItem[];
invoiceUrl: string;
}
export default function InvoiceEmail({
customerName,
invoiceNumber,
dueDate,
items,
invoiceUrl,
}: InvoiceEmailProps) {
const subtotal = items.reduce((sum, item) => sum + item.quantity * item.unitPrice, 0);
const tax = subtotal * 0.1;
const total = subtotal + tax;
return (
<EmailLayout preview={`Invoice ${invoiceNumber} — $${total.toFixed(2)} due ${dueDate}`}>
<Heading as="h1" style={heading}>
Invoice #{invoiceNumber}
</Heading>
<Text style={text}>Hi {customerName},</Text>
<Text style={text}>
Here is your invoice. Payment is due by {dueDate}.
</Text>
{/* Line items */}
<Section style={table}>
<Row style={tableHeader}>
<Column style={{ ...cell, width: "50%" }}>Description</Column>
<Column style={{ ...cell, width: "15%", textAlign: "right" as const }}>Qty</Column>
<Column style={{ ...cell, width: "17%", textAlign: "right" as const }}>Price</Column>
<Column style={{ ...cell, width: "18%", textAlign: "right" as const }}>Total</Column>
</Row>
{items.map((item, i) => (
<Row key={i} style={tableRow}>
<Column style={cell}>{item.description}</Column>
<Column style={{ ...cell, textAlign: "right" as const }}>{item.quantity}</Column>
<Column style={{ ...cell, textAlign: "right" as const }}>
${item.unitPrice.toFixed(2)}
</Column>
<Column style={{ ...cell, textAlign: "right" as const }}>
${(item.quantity * item.unitPrice).toFixed(2)}
</Column>
</Row>
))}
</Section>
<Hr style={hr} />
{/* Totals */}
<Section>
<Row>
<Column style={{ width: "65%" }} />
<Column style={totalLabel}>Subtotal</Column>
<Column style={totalValue}>${subtotal.toFixed(2)}</Column>
</Row>
<Row>
<Column style={{ width: "65%" }} />
<Column style={totalLabel}>Tax (10%)</Column>
<Column style={totalValue}>${tax.toFixed(2)}</Column>
</Row>
<Row>
<Column style={{ width: "65%" }} />
<Column style={{ ...totalLabel, fontWeight: "bold" as const }}>Total</Column>
<Column style={{ ...totalValue, fontWeight: "bold" as const, fontSize: "16px" }}>
${total.toFixed(2)}
</Column>
</Row>
</Section>
<Section style={{ textAlign: "center" as const, marginTop: "32px" }}>
<Button style={button} href={invoiceUrl}>
Pay Invoice
</Button>
</Section>
</EmailLayout>
);
}
const heading = { fontSize: "24px", fontWeight: "bold" as const, color: "#0f172a" };
const text = { fontSize: "14px", lineHeight: "24px", color: "#374151" };
const table = { marginTop: "24px" };
const tableHeader = { backgroundColor: "#f9fafb", borderBottom: "1px solid #e5e7eb" };
const tableRow = { borderBottom: "1px solid #f3f4f6" };
const cell = { padding: "10px 8px", fontSize: "13px", color: "#374151" };
const hr = { borderColor: "#e5e7eb", margin: "16px 0" };
const totalLabel = { textAlign: "right" as const, fontSize: "13px", color: "#6b7280", padding: "4px 8px" };
const totalValue = { textAlign: "right" as const, fontSize: "13px", color: "#0f172a", padding: "4px 8px" };
const button = {
backgroundColor: "#3b82f6",
borderRadius: "6px",
color: "#ffffff",
fontSize: "14px",
fontWeight: "600" as const,
padding: "12px 24px",
textDecoration: "none" as const,
};
Step 5: Send with Resend
// lib/email.ts
import { Resend } from "resend";
import { render } from "@react-email/render";
import WelcomeEmail from "@/emails/WelcomeEmail";
import InvoiceEmail from "@/emails/InvoiceEmail";
const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendWelcomeEmail(to: string, name: string) {
const html = await render(
WelcomeEmail({
name,
loginUrl: `${process.env.NEXT_PUBLIC_APP_URL}/login`,
})
);
return resend.emails.send({
from: "RCB Software <hello@rcbsoftware.com>",
to,
subject: `Welcome to RCB Software, ${name}!`,
html,
});
}
export async function sendInvoiceEmail(
to: string,
data: {
customerName: string;
invoiceNumber: string;
dueDate: string;
items: { description: string; quantity: number; unitPrice: number }[];
invoiceUrl: string;
}
) {
const html = await render(InvoiceEmail(data));
return resend.emails.send({
from: "RCB Software <billing@rcbsoftware.com>",
to,
subject: `Invoice #${data.invoiceNumber}`,
html,
});
}
Step 6: Preview Emails in Development
Add the dev script to package.json:
{
"scripts": {
"email": "email dev --dir emails --port 3001"
}
}
Run pnpm email to preview and test your emails at localhost:3001.
Summary
- React Email components render to HTML email-compatible output
- Shared layout ensures consistent branding across all emails
- Inline styles work reliably across email clients
- React Email dev server provides instant preview during development
Need Professional Email Templates?
We design and develop email systems that engage users and drive conversions. Contact us to get started.