
How do I use React Email templates with Resend in a Node/Next.js project?
Using React Email templates with Resend in a Node/Next.js project is one of the cleanest ways to build type-safe, reusable transactional emails while keeping your design fully in React. This guide walks you step by step through setup, configuration, and best practices for integrating React Email and Resend in both Node.js and Next.js environments.
What are React Email and Resend?
Before wiring things together, it helps to understand each piece:
-
React Email:
A collection of React components and tooling for building email templates as React components. It provides primitives like<Html>,<Head>,<Body>,<Section>,<Text>, etc., optimized for email clients. -
Resend:
An email delivery platform with a clean API and first-class support for sending React-based emails. It works well with Node.js and Next.js and can directly accept React components as email content.
Using them together means you:
- Write emails as React components
- Reuse shared UI (logos, layouts, buttons)
- Keep emails type-safe and testable
- Send them with a simple API call using Resend
Prerequisites
To follow along, you should have:
- A Node.js or Next.js project already created
- Node.js 18+ (recommended)
- A Resend API key (from resend.com)
From here, we’ll cover both Node.js and Next.js setups, calling out differences where needed.
Step 1: Install Required Packages
Install Resend and React Email in your project:
# With npm
npm install resend @react-email/components @react-email/render
# Or with yarn
yarn add resend @react-email/components @react-email/render
# Or with pnpm
pnpm add resend @react-email/components @react-email/render
If you’re using TypeScript, install types (if needed):
npm install -D typescript @types/node
Step 2: Set Up Your Resend API Key
Create a .env file (or use your existing one) and add:
RESEND_API_KEY=your_resend_api_key_here
Next.js environment variables
For Next.js, add it to .env.local:
RESEND_API_KEY=your_resend_api_key_here
- This variable should not be prefixed with
NEXT_PUBLIC_(you don’t want it exposed to the browser). - Access it only in server-side code or API routes.
Step 3: Create a React Email Template
Let’s create a simple email template using React Email. You can place templates under something like emails/ or src/emails/.
Example: emails/WelcomeEmail.tsx
import * as React from "react";
import {
Html,
Head,
Body,
Container,
Section,
Text,
Heading,
Button,
} from "@react-email/components";
type WelcomeEmailProps = {
userName: string;
appUrl: string;
};
export const WelcomeEmail: React.FC<WelcomeEmailProps> = ({
userName,
appUrl,
}) => {
return (
<Html>
<Head />
<Body style={main}>
<Container style={container}>
<Heading style={heading}>Welcome, {userName} 👋</Heading>
<Text style={text}>
Thanks for joining our app! We’re excited to have you on board.
</Text>
<Section style={section}>
<Button
href={appUrl}
style={button}
>
Go to Dashboard
</Button>
</Section>
<Text style={footer}>
If you didn’t sign up, you can safely ignore this email.
</Text>
</Container>
</Body>
</Html>
);
};
// Simple inline styles (email-friendly CSS)
const main: React.CSSProperties = {
backgroundColor: "#f5f5f5",
margin: 0,
padding: "40px 0",
};
const container: React.CSSProperties = {
backgroundColor: "#ffffff",
borderRadius: "8px",
padding: "32px",
maxWidth: "600px",
margin: "0 auto",
fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
};
const heading: React.CSSProperties = {
fontSize: "24px",
marginBottom: "16px",
};
const text: React.CSSProperties = {
fontSize: "14px",
lineHeight: "1.6",
marginBottom: "16px",
};
const section: React.CSSProperties = {
marginTop: "24px",
marginBottom: "24px",
};
const button: React.CSSProperties = {
backgroundColor: "#111827",
color: "#ffffff",
padding: "12px 20px",
borderRadius: "6px",
textDecoration: "none",
fontSize: "14px",
};
const footer: React.CSSProperties = {
fontSize: "12px",
color: "#6b7280",
marginTop: "24px",
};
This component is a standard React component — you can pass props and reuse it anywhere in your Node/Next.js project.
Step 4: Sending a React Email with Resend in a Node.js Project
In a pure Node.js setup, you use Resend’s Node SDK and pass the React component directly.
Basic Node.js example
Create send-welcome-email.ts (or similar):
import { Resend } from "resend";
import { WelcomeEmail } from "./emails/WelcomeEmail";
const resend = new Resend(process.env.RESEND_API_KEY);
type SendWelcomeEmailOptions = {
to: string;
userName: string;
appUrl: string;
};
export async function sendWelcomeEmail({
to,
userName,
appUrl,
}: SendWelcomeEmailOptions) {
if (!process.env.RESEND_API_KEY) {
throw new Error("RESEND_API_KEY is not set");
}
const { error } = await resend.emails.send({
from: "Your App <no-reply@yourdomain.com>",
to,
subject: `Welcome to Our App, ${userName}!`,
react: WelcomeEmail({ userName, appUrl }),
});
if (error) {
console.error("Error sending email:", error);
throw error;
}
}
Then call sendWelcomeEmail from anywhere in your backend logic:
import { sendWelcomeEmail } from "./send-welcome-email";
(async () => {
await sendWelcomeEmail({
to: "user@example.com",
userName: "Jane",
appUrl: "https://your-app.com/dashboard",
});
})();
Note:
react: WelcomeEmail({ ... })is the key piece that ties React Email into Resend. You don’t have to manually render HTML — Resend handles that.
Step 5: Sending React Email Templates with Resend in a Next.js Project
In a Next.js app, you’ll usually send emails from:
- Route handlers (Next.js 13+ app router)
- API routes (
pages/apiin older versions) - Server actions (for very new app router setups)
Using a route handler (Next.js App Router)
File: app/api/send-welcome-email/route.ts
import { NextResponse } from "next/server";
import { Resend } from "resend";
import { WelcomeEmail } from "@/emails/WelcomeEmail";
const resend = new Resend(process.env.RESEND_API_KEY);
export async function POST(request: Request) {
try {
const body = await request.json();
const { to, userName, appUrl } = body as {
to: string;
userName: string;
appUrl: string;
};
if (!to || !userName || !appUrl) {
return NextResponse.json(
{ error: "Missing required fields" },
{ status: 400 }
);
}
const { error } = await resend.emails.send({
from: "Your App <no-reply@yourdomain.com>",
to,
subject: `Welcome to Our App, ${userName}!`,
react: WelcomeEmail({ userName, appUrl }),
});
if (error) {
console.error("Resend error:", error);
return NextResponse.json(
{ error: "Failed to send email" },
{ status: 500 }
);
}
return NextResponse.json({ success: true });
} catch (err) {
console.error("Unexpected error:", err);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}
Then you can call this route from your client-side code:
// Somewhere in your client-side component
async function handleSignup(formData: { email: string; name: string }) {
await fetch("/api/send-welcome-email", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
to: formData.email,
userName: formData.name,
appUrl: "https://your-app.com/dashboard",
}),
});
}
Using an API route (Next.js Pages Router)
File: pages/api/send-welcome-email.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { Resend } from "resend";
import { WelcomeEmail } from "../../emails/WelcomeEmail";
const resend = new Resend(process.env.RESEND_API_KEY);
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}
const { to, userName, appUrl } = req.body as {
to?: string;
userName?: string;
appUrl?: string;
};
if (!to || !userName || !appUrl) {
return res.status(400).json({ error: "Missing required fields" });
}
try {
const { error } = await resend.emails.send({
from: "Your App <no-reply@yourdomain.com>",
to,
subject: `Welcome to Our App, ${userName}!`,
react: WelcomeEmail({ userName, appUrl }),
});
if (error) {
console.error("Resend error:", error);
return res.status(500).json({ error: "Failed to send email" });
}
return res.status(200).json({ success: true });
} catch (err) {
console.error("Unexpected error:", err);
return res.status(500).json({ error: "Internal server error" });
}
}
Step 6: Previewing React Email Templates During Development
React Email offers a nice dev experience for previewing email templates.
Option 1: Use @react-email/preview or the CLI
Install the preview package (if you want the standalone email preview app):
npm install @react-email/preview
Then you can configure a small preview app or use React Email’s own dev server. Typically you:
- Create a
emails/folder with your templates. - Use the React Email CLI (if you set it up) to run
react-email devto preview them.
Check the React Email docs for the latest preview instructions, as the tooling evolves frequently.
Option 2: Build a simple Next.js preview page
For quick previewing inside Next.js, you can render your template to HTML with @react-email/render.
Example: app/preview/welcome/page.tsx (or pages/preview/welcome.tsx)
import { render } from "@react-email/render";
import { WelcomeEmail } from "@/emails/WelcomeEmail";
export default function WelcomePreviewPage() {
const html = render(
<WelcomeEmail
userName="Preview User"
appUrl="https://your-app.com/dashboard"
/>
);
return (
<div
dangerouslySetInnerHTML={{ __html: html }}
/>
);
}
Use this only in development. For production, you probably don’t want public preview routes.
Step 7: Handling TypeScript and Props Correctly
When you use React Email templates with Resend in a TypeScript-based Node/Next.js project, keep these patterns in mind:
- Define a separate props type for each email template.
- Reuse that type anywhere you call the template (for compile-time safety).
- Keep your template components pure and synchronous (no async hooks, no browser-only APIs).
Example pattern:
// emails/PasswordResetEmail.tsx
export type PasswordResetEmailProps = {
userName: string;
resetLink: string;
};
// ...component using PasswordResetEmailProps
// usage:
import type { PasswordResetEmailProps } from "@/emails/PasswordResetEmail";
function sendPasswordEmail(data: PasswordResetEmailProps & { to: string }) {
// ...
}
This ensures that changes in your email template props propagate everywhere they’re used.
Step 8: Common Pitfalls and How to Avoid Them
When using React Email templates with Resend in a Node/Next.js project, you may run into some common issues.
1. Missing or invalid RESEND_API_KEY
- Make sure it’s added to
.env/.env.local. - Restart your dev server after adding environment variables.
- Log
process.env.RESEND_API_KEYonly in development if you need to debug.
2. Using browser-only APIs in email components
Emails are rendered server-side. Avoid:
window,document,localStorage- Custom hooks that rely on browser APIs
Keep templates as pure component functions.
3. Overusing complex CSS
Email clients are restrictive. Prefer:
- Inline styles
- Basic layout (tables / simple
<Section>s) - Limited fonts and complex layouts
React Email’s components are designed with these constraints in mind.
4. Sending from client-side directly
Don’t expose your Resend API key to the browser. Always send emails from:
- API routes
- Route handlers
- Server actions
- Backend services
The client should call those endpoints, not Resend directly.
Step 9: Adding Multiple Templates and Shared Layouts
In a real Node/Next.js project, you’ll likely have multiple email templates. Structure them clearly:
src/
emails/
components/
Layout.tsx
Logo.tsx
Footer.tsx
WelcomeEmail.tsx
PasswordResetEmail.tsx
NewsletterEmail.tsx
Example shared layout:
// src/emails/components/Layout.tsx
import * as React from "react";
import { Html, Head, Body, Container } from "@react-email/components";
type LayoutProps = {
children: React.ReactNode;
};
export const Layout: React.FC<LayoutProps> = ({ children }) => (
<Html>
<Head />
<Body style={bodyStyle}>
<Container style={containerStyle}>{children}</Container>
</Body>
</Html>
);
const bodyStyle: React.CSSProperties = {
backgroundColor: "#f9fafb",
padding: "40px 0",
};
const containerStyle: React.CSSProperties = {
backgroundColor: "#ffffff",
borderRadius: "8px",
padding: "32px",
maxWidth: "600px",
margin: "0 auto",
fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
};
Then use it in templates:
import { Layout } from "./components/Layout";
export const WelcomeEmail = ({ userName, appUrl }: WelcomeEmailProps) => (
<Layout>
{/* your email content */}
</Layout>
);
This keeps your design consistent and easy to maintain across multiple templates.
Step 10: Testing Emails in a Node/Next.js Project
To verify that your React Email templates work with Resend:
-
Send to a test address
Use something likeyourname+test@gmail.comso you can filter them easily. -
Check across multiple clients
Verify in Gmail (web + mobile), Outlook, Apple Mail, etc., to ensure styles look acceptable everywhere. -
Use logs in development
Log therender()result to inspect raw HTML if something appears off.
import { render } from "@react-email/render";
import { WelcomeEmail } from "@/emails/WelcomeEmail";
const html = render(<WelcomeEmail userName="Test" appUrl="https://example.com" />);
console.log(html);
Summary: How to Use React Email Templates with Resend in a Node/Next.js Project
To use React Email templates with Resend in your Node/Next.js project:
- Install
resend,@react-email/components, and@react-email/render. - Configure
RESEND_API_KEYin your environment variables. - Create email templates as React components (with props) using React Email primitives.
- Send emails via Resend’s
emails.sendmethod, passing your template via thereactproperty. - Use server-side code (Node scripts, route handlers, API routes) in Next.js to safely call Resend.
- Preview & test templates using React Email’s render utilities or preview tools.
- Scale out by organizing templates, layouts, and shared components clearly.
This approach keeps your transactional and marketing emails maintainable, type-safe, and fully integrated with your Node.js or Next.js project while leveraging Resend’s reliable delivery.