Launch Special: Use code LAUNCH20 for $20 off — Just $59 $79
Logo
Back

Send React Email with Nodemailer in Next.js (2026 Guide)

Step-by-step guide to sending React Email templates with Nodemailer in Next.js. Covers install, env setup, App Router & Pages Router examples, error handling, and tips.

Guides
Soufiane E.
Soufiane E.
8 min read
Send React Email with Nodemailer in Next.js (2026 Guide)

Nodemailer is the most flexible email option for Node.js — it works with any SMTP server: Gmail, Outlook, your own mail server, or any transactional provider that supports SMTP (Mailgun, SendGrid, Postmark all do). No vendor lock-in. This guide shows you exactly how to integrate React Email with Nodemailer in a Next.js project — covering environment setup, rendering templates to HTML, sending with both App Router and Pages Router, and handling common errors.


Prerequisites

Before starting, you need:

  • A Nodemailer account with API credentials
  • A Next.js project (App Router or Pages Router — both covered below)
  • Node.js 18+

Step 1: Install Dependencies

npm install nodemailer @types/nodemailer @react-email/render

@react-email/render converts your React Email component to an HTML string, which you then pass to Nodemailer's API. Unlike Resend (which accepts React elements directly), most providers require rendered HTML.


Step 2: Configure Environment Variables

Add the following to your .env.local file:

SMTP_HOST=smtp.yourprovider.com
SMTP_PORT=587
SMTP_USER=hello@yourdomain.com
SMTP_PASS=your-smtp-password
  • SMTP_HOSTSMTP server hostname from your email provider
  • SMTP_PORT587 for STARTTLS (recommended), 465 for SSL, 25 for unencrypted
  • SMTP_USERSMTP authentication username
  • SMTP_PASSSMTP authentication password or API key (varies by provider)

When deploying, add these variables in your hosting provider's environment settings (Vercel dashboard, Railway, Fly.io, etc.). Variables in .env.local do not deploy automatically.


Step 3: Create a React Email Template

Create an emails/ directory and add your first template. All providers use the same React Email template format — the difference is only in how you send it.

// emails/welcome-email.tsx
import * as React from 'react';
import {
Body, Button, Container, Head,
Heading, Html, Preview, Section, Text,
} from '@react-email/components';
interface WelcomeEmailProps {
username: string;
dashboardUrl: string;
}
export function WelcomeEmail({ username, dashboardUrl }: WelcomeEmailProps) {
return (
<Html>
<Head />
<Preview>Your account is ready — let's get started</Preview>
<Body style={{ fontFamily: 'sans-serif', backgroundColor: '#f4f4f5' }}>
<Container style={{ margin: '0 auto', padding: '40px 20px', maxWidth: '560px' }}>
<Heading>Welcome, {username}!</Heading>
<Text>Your account is set up and ready to use.</Text>
<Section>
<Button
href={dashboardUrl}
style={{ backgroundColor: '#000', color: '#fff', padding: '12px 24px', borderRadius: '6px' }}
>
Go to Dashboard
</Button>
</Section>
</Container>
</Body>
</Html>
);
}
WelcomeEmail.PreviewProps = {
username: 'Alex',
dashboardUrl: 'https://example.com/dashboard',
} as WelcomeEmailProps;
export default WelcomeEmail;

Preview locally

Before sending real emails, use the React Email dev server to preview your templates:

npx react-email dev --dir emails

Open http://localhost:3000 to see your templates rendered exactly as they will appear in email clients.



Step 4: Initialize the Nodemailer Client

Create the client once at module level so it's reused across requests:

import nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
secure: Number(process.env.SMTP_PORT) === 465,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});

Step 5: Create the API Route

App Router (app/api/send/route.ts)

import { render } from '@react-email/render';
import nodemailer from 'nodemailer';
import { WelcomeEmail } from '../../../emails/welcome-email';
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
secure: Number(process.env.SMTP_PORT) === 465,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});
export async function POST(request: Request) {
const { username, email } = await request.json();
const html = await render(
<WelcomeEmail username={username} dashboardUrl="https://yourdomain.com/dashboard" />
);
const info = await transporter.sendMail({
from: '"Your App" <hello@yourdomain.com>',
to: email,
subject: `Welcome, ${username}!`,
html,
});
return Response.json({ messageId: info.messageId });
}

Pages Router (pages/api/send.ts)

import type { NextApiRequest, NextApiResponse } from 'next';
import { render } from '@react-email/render';
import nodemailer from 'nodemailer';
import { WelcomeEmail } from '../../emails/welcome-email';
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
secure: Number(process.env.SMTP_PORT) === 465,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') return res.status(405).end();
const { username, email } = req.body;
const html = await render(
<WelcomeEmail username={username} dashboardUrl="https://yourdomain.com/dashboard" />
);
const info = await transporter.sendMail({
from: '"Your App" <hello@yourdomain.com>',
to: email,
subject: `Welcome, ${username}!`,
html,
});
return res.status(200).json({ messageId: info.messageId });
}

Nodemailer-Specific Tips

  • For local development, use [Ethereal](https://ethereal.email/) — a fake SMTP service. Call `nodemailer.createTestAccount()` to get test credentials and `nodemailer.getTestMessageUrl(info)` to view sent emails.
  • Create the transporter once at module level (outside the handler) so it is reused across requests and connections are pooled.
  • For Gmail SMTP, use an App Password instead of your regular password — enable 2FA on your Google account, then generate an App Password under Security settings.
  • Most transactional providers (Postmark, SendGrid, Mailgun) expose SMTP endpoints as an alternative to their API clients — useful when you want provider flexibility without switching packages.

Common Errors and Fixes

ErrorCauseFix
ECONNREFUSED / ETIMEDOUTWrong SMTP host or port, or firewall blocking the connectionVerify SMTP_HOST and SMTP_PORT match your provider docs. Port 587 with STARTTLS is the most widely supported.
Invalid login (535)Wrong username or passwordFor Gmail, use an App Password not your account password. For other providers, double-check the SMTP credentials in their dashboard.
Self signed certificate in certificate chainTLS verification failing for custom SMTP serversAdd `tls: { rejectUnauthorized: false }` to the transport config (development only — do not disable in production)

Organizing Multiple Email Templates

A SaaS app typically needs 8–12 email templates. Keep them organized in a dedicated directory:

emails/
├── welcome-email.tsx
├── otp-email.tsx
├── magic-link.tsx
├── password-reset.tsx
├── payment-receipt.tsx
├── trial-started.tsx
├── trial-ending.tsx
└── security-alert.tsx

Each template uses the same @react-email/components primitives and is rendered to HTML with @react-email/render before being passed to Nodemailer.

If you don't want to write and cross-client test all these templates from scratch, the React Email Templates bundle includes 12 production-ready templates for the most common SaaS email flows — tested on Gmail, Outlook, Apple Mail, and mobile clients in both light and dark mode.


Related Guides