Amazon Simple Email Service (SES) is the lowest-cost option for high-volume email and integrates natively with the rest of your AWS infrastructure. It requires more setup than developer-focused providers but scales to billions of emails. This guide shows you exactly how to integrate React Email with AWS SES 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 AWS SES 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 @aws-sdk/client-ses @react-email/render@react-email/render converts your React Email component to an HTML string, which you then pass to AWS SES'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:
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
AWS_REGION=us-east-1AWS_ACCESS_KEY_ID— IAM user access key with SES send permissionsAWS_SECRET_ACCESS_KEY— IAM user secret keyAWS_REGION— The AWS region where your SES is configured
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 AWS SES Client
Create the client once at module level so it's reused across requests:
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';
const ses = new SESClient({ region: process.env.AWS_REGION!, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID!, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, },});Step 5: Create the API Route
App Router (app/api/send/route.ts)
import { render } from '@react-email/render';import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';import { WelcomeEmail } from '../../../emails/welcome-email';
const ses = new SESClient({ region: process.env.AWS_REGION!, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID!, secretAccessKey: process.env.AWS_SECRET_ACCESS_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 command = new SendEmailCommand({ Source: 'hello@yourdomain.com', Destination: { ToAddresses: [email] }, Message: { Subject: { Data: `Welcome, ${username}!` }, Body: { Html: { Data: html, Charset: 'UTF-8' } }, }, });
const result = await ses.send(command); return Response.json({ messageId: result.MessageId });}Pages Router (pages/api/send.ts)
import type { NextApiRequest, NextApiResponse } from 'next';import { render } from '@react-email/render';import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses';import { WelcomeEmail } from '../../emails/welcome-email';
const ses = new SESClient({ region: process.env.AWS_REGION!, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID!, secretAccessKey: process.env.AWS_SECRET_ACCESS_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 command = new SendEmailCommand({ Source: 'hello@yourdomain.com', Destination: { ToAddresses: [email] }, Message: { Subject: { Data: `Welcome, ${username}!` }, Body: { Html: { Data: html, Charset: 'UTF-8' } }, }, });
const result = await ses.send(command); return res.status(200).json({ messageId: result.MessageId });}AWS SES-Specific Tips
- New AWS SES accounts start in sandbox mode — you can only send to verified email addresses. Request production access in the SES console to lift this restriction.
- On AWS infrastructure (EC2, Lambda, ECS), you can use IAM roles instead of access keys — omit the `credentials` config and the SDK picks up the role automatically.
- Use `SESv2Client` and `SendEmailCommand` from `@aws-sdk/client-sesv2` if you need features like templates, configuration sets, or contact lists.
- SES is significantly cheaper than other providers at scale ($0.10 per 1,000 emails), but requires more ops work — you manage your own bounce and complaint handling.
Common Errors and Fixes
| Error | Cause | Fix |
|---|---|---|
Email address not verified | In sandbox mode, both From and To must be verified | Verify your email addresses in the SES console, or request production access to remove this restriction |
SignatureDoesNotMatch | Incorrect AWS credentials | Double-check AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are correct and not rotated |
AccessDenied | IAM user lacks SES send permissions | Attach the `AmazonSESFullAccess` policy or a custom policy with `ses:SendEmail` to your IAM user |
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 AWS SES.
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.
