Custom Email Template
Create beautiful transactional email templates with React Email and send them through Resend.
KitRocket uses React Email for templates and Resend for delivery. This guide shows you how to create a custom email template from scratch.
Step 1: Create the template component
Create a new file in the templates directory:
import {
Html,
Head,
Body,
Container,
Section,
Text,
Button,
Heading,
Hr,
Img,
} from "@react-email/components";
interface InviteEmailProps {
inviterName: string;
teamName: string;
inviteUrl: string;
}
export function InviteEmail({ inviterName, teamName, inviteUrl }: InviteEmailProps) {
return (
<Html>
<Head />
<Body style={body}>
<Container style={container}>
<Img
src={`${process.env.BETTER_AUTH_URL}/logo.png`}
width="40"
height="40"
alt="Logo"
/>
<Heading style={heading}>You're invited</Heading>
<Text style={text}>
{inviterName} has invited you to join <strong>{teamName}</strong>.
</Text>
<Section style={buttonContainer}>
<Button style={button} href={inviteUrl}>
Accept Invitation
</Button>
</Section>
<Hr style={hr} />
<Text style={footer}>
If you didn't expect this invitation, you can safely ignore this email.
</Text>
</Container>
</Body>
</Html>
);
}
// Styles
const body = {
backgroundColor: "#f6f9fc",
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
};
const container = {
backgroundColor: "#ffffff",
margin: "0 auto",
padding: "40px 20px",
maxWidth: "560px",
borderRadius: "8px",
};
const heading = {
fontSize: "24px",
fontWeight: "bold" as const,
color: "#1a1a1a",
marginBottom: "16px",
};
const text = {
fontSize: "16px",
lineHeight: "24px",
color: "#4a4a4a",
};
const buttonContainer = {
textAlign: "center" as const,
marginTop: "24px",
marginBottom: "24px",
};
const button = {
backgroundColor: "#000000",
color: "#ffffff",
fontSize: "16px",
fontWeight: "bold" as const,
padding: "12px 24px",
borderRadius: "6px",
textDecoration: "none",
};
const hr = {
borderColor: "#e6e6e6",
marginTop: "24px",
marginBottom: "24px",
};
const footer = {
fontSize: "14px",
color: "#999999",
};
Step 2: Add a send function
Add a function to send the new email:
import { Resend } from "resend";
import { InviteEmail } from "./templates/invite";
const resend = new Resend(process.env.RESEND_API_KEY);
// ... existing send functions
export async function sendInviteEmail(
to: string,
props: { inviterName: string; teamName: string; inviteUrl: string }
) {
const { data, error } = await resend.emails.send({
from: process.env.EMAIL_FROM!,
to,
subject: `${props.inviterName} invited you to join ${props.teamName}`,
react: InviteEmail(props),
});
if (error) {
console.error("Failed to send invite email:", error);
throw new Error("Email delivery failed");
}
return data;
}
Step 3: Use in an API route
Call the send function from any API route:
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth/server";
import { sendInviteEmail } from "@/lib/email/send";
export async function POST(request: NextRequest) {
const session = await auth.api.getSession({
headers: request.headers,
});
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { email, teamName } = await request.json();
const inviteUrl = `${process.env.BETTER_AUTH_URL}/invite/accept?token=...`;
await sendInviteEmail(email, {
inviterName: session.user.name,
teamName,
inviteUrl,
});
return NextResponse.json({ sent: true });
}
Step 4: Preview locally
React Email includes a dev server for previewing templates:
pnpm email:dev
This opens a browser at localhost:3001 where you can see all your templates with hot reload. Change styles and content, and see updates instantly.
If the script isn't set up yet, add it:
{
"scripts": {
"email:dev": "email dev --dir src/lib/email/templates"
}
}
Template tips
Use inline styles
Email clients don't support CSS classes reliably. Always use inline styles or the style prop.
Test across clients
Email rendering varies wildly. Test in:
- Gmail (web and mobile)
- Apple Mail
- Outlook (the hardest)
- Yahoo Mail
Use Litmus or Email on Acid for cross-client testing.
Keep it simple
Stick to single-column layouts. Avoid complex CSS (no flexbox, no grid). Tables are still the most reliable layout method for emails, and React Email handles this under the hood.
Add a plain text fallback
await resend.emails.send({
from: process.env.EMAIL_FROM!,
to,
subject: "You're invited",
react: InviteEmail(props),
text: `${props.inviterName} invited you to join ${props.teamName}. Accept here: ${props.inviteUrl}`,
});
Available React Email components
| Component | Purpose |
|---|---|
Html, Head, Body | Document structure |
Container | Centered content wrapper |
Section, Row, Column | Layout |
Text | Paragraphs |
Heading | Headers (h1-h6) |
Button | CTA buttons |
Link | Hyperlinks |
Img | Images |
Hr | Horizontal rule |
Preview | Email preview text (shows in inbox) |
Next steps
- Email Module — full module documentation
- React Email docs — component reference
- Resend docs — delivery API reference