Mailgun is a developer-focused email API with strong deliverability tools, detailed logging, and a generous free tier. It's a popular choice for startups that want more control over deliverability than larger platforms offer. This guide shows you exactly how to integrate React Email with Mailgun 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 Mailgun 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 mailgun.js form-data @react-email/render@react-email/render converts your React Email component to an HTML string, which you then pass to Mailgun'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:
MAILGUN_API_KEY=key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
MAILGUN_DOMAIN=mg.yourdomain.comMAILGUN_API_KEY— Found in the Mailgun dashboard under Settings → API KeysMAILGUN_DOMAIN— Your verified Mailgun sending domain
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.tsximport * 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 emailsOpen http://localhost:3000 to see your templates rendered exactly as they will appear in email clients.
Step 4: Initialize the Mailgun Client
Create the client once at module level so it's reused across requests:
import Mailgun from 'mailgun.js';import FormData from 'form-data';
const mailgun = new Mailgun(FormData);const mg = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY!,});Step 5: Create the API Route
App Router (app/api/send/route.ts)
import { render } from '@react-email/render';import Mailgun from 'mailgun.js';import FormData from 'form-data';import { WelcomeEmail } from '../../../emails/welcome-email';
const mailgun = new Mailgun(FormData);const mg = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY!,});
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 result = await mg.messages.create(process.env.MAILGUN_DOMAIN!, { from: 'Your App <hello@yourdomain.com>', to: [email], subject: `Welcome, ${username}!`, html, });
return Response.json({ id: result.id });}Pages Router (pages/api/send.ts)
import type { NextApiRequest, NextApiResponse } from 'next';import { render } from '@react-email/render';import Mailgun from 'mailgun.js';import FormData from 'form-data';import { WelcomeEmail } from '../../emails/welcome-email';
const mailgun = new Mailgun(FormData);const mg = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY!,});
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 result = await mg.messages.create(process.env.MAILGUN_DOMAIN!, { from: 'Your App <hello@yourdomain.com>', to: [email], subject: `Welcome, ${username}!`, html, });
return res.status(200).json({ id: result.id });}Mailgun-Specific Tips
- Mailgun uses EU and US regions. If your account is on the EU region, initialize the client with `url: 'https://api.eu.mailgun.net'`.
- New Mailgun accounts are in sandbox mode and can only send to authorized recipients. Add authorized recipients in the dashboard or upgrade to send freely.
- The `form-data` package is a required peer dependency of `mailgun.js` — it must be installed separately.
- Mailgun supports email tracking (opens, clicks) and webhooks for bounce/complaint events — configure these in the dashboard for better deliverability management.
Common Errors and Fixes
| Error | Cause | Fix |
|---|---|---|
Unauthorized (401) | Invalid API key | Check MAILGUN_API_KEY starts with "key-" and is the Private API Key, not the Public Validation Key |
Domain not found (404) | Wrong MAILGUN_DOMAIN value | Use your exact sending domain from the Mailgun Sending → Domains list, e.g. mg.yourdomain.com |
Cannot read property 'create' of undefined | form-data not installed | Run npm install form-data — it is required by mailgun.js |
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.tsxEach template uses the same @react-email/components primitives and is rendered to HTML with @react-email/render before being passed to Mailgun.
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.
