SendOps

Guide

React Email + Amazon SES: the complete guide

React Email lets you write email templates as typed React components; Amazon SES sends them for a fraction of what dedicated email platforms charge. They pair beautifully — with one catch around templates and bulk sending that this guide covers in full. What React Email is, how to wire it to SES with working code, where the render-at-send approach breaks down, and how to compile React components into SES-native templates.

What Is React Email

Email templates as typed React components

React Email is an open-source framework — created and maintained by Resend, credit where it's due — for building email templates as React components instead of hand-written HTML table soup. You write a .tsx file, and the framework renders it into email-safe HTML that works across Gmail, Outlook, Apple Mail, and the rest.

The pitch, for anyone who has hand-maintained email HTML: your templates become typed, composable, and version-controlled. Props are TypeScript interfaces, shared headers and footers are components you import, and template changes go through code review like everything else in your repo.

Components

Building blocks like Html, Body, Container, Section, Text, and Button that compile down to markup email clients can actually render — tables and inline styles under the hood, components in your editor.

Tailwind support

A Tailwind wrapper component lets you style with utility classes; React Email converts them to the inline styles email clients require, so you keep the workflow you already use in your app.

Preview server

Run npx react-email dev and get a local preview UI with hot reload — edit the component, see the rendered email instantly, no test sends required.

One thing worth being clear about: although Resend builds React Email, it is provider-agnostic. The output is plain HTML, and nothing in the framework ties you to Resend's API. That's exactly why it pairs so well with Amazon SES — you get Resend-quality authoring ergonomics on top of SES's pricing and your own AWS infrastructure.

Render at Send

The DIY path: render to HTML, send via SESv2

The straightforward integration takes three packages: @react-email/components for authoring, @react-email/render for turning a component into an HTML string, and @aws-sdk/client-sesv2 for the send. You render at the moment of sending, with the recipient's data as props.

First, the template — an ordinary React component with a typed props interface:

emails/welcome.tsx
import { Html, Body, Container, Heading, Text, Button } from "@react-email/components"

interface WelcomeEmailProps {
  name: string
  dashboardUrl: string
}

export default function WelcomeEmail({ name, dashboardUrl }: WelcomeEmailProps) {
  return (
    <Html>
      <Body style={{ fontFamily: "Helvetica, Arial, sans-serif" }}>
        <Container>
          <Heading>Welcome, {name}</Heading>
          <Text>Your account is ready to go.</Text>
          <Button href={dashboardUrl}>Open your dashboard</Button>
        </Container>
      </Body>
    </Html>
  )
}

Then the send path: render the component with real props, and hand the resulting HTML to the SESv2 SendEmail API. As of July 2026, render() is async and returns a promise:

send.ts
import { render } from "@react-email/render"
import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2"
import WelcomeEmail from "./emails/welcome"

const ses = new SESv2Client({ region: "us-east-1" })

const email = WelcomeEmail({
  name: "Ada",
  dashboardUrl: "https://app.example.com/dashboard",
})

const html = await render(email)
const text = await render(email, { plainText: true })

await ses.send(
  new SendEmailCommand({
    FromEmailAddress: "Example <hello@example.com>",
    Destination: { ToAddresses: ["ada@example.com"] },
    Content: {
      Simple: {
        Subject: { Data: "Welcome to Example" },
        Body: {
          Html: { Data: html },
          Text: { Data: text },
        },
      },
    },
  })
)

Two details worth copying from that snippet: always generate the plain-text version alongside the HTML (the plainText option does it from the same component — good for deliverability and required by some clients), and send from a verified identity. If your SES account isn't set up yet — domain verification, DKIM, sandbox exit — our Amazon SES setup guide covers the whole path.

For transactional email — welcome messages, receipts, password resets — this is a genuinely good architecture. One render per send is cheap, the data is per-recipient anyway, and the template lives in your repo next to the code that triggers it.

Where It Breaks Down

Render-at-send at scale, and the Handlebars gap

Render-at-send has three structural costs, and they all get heavier as volume grows:

Node in the send path

Rendering React requires a JavaScript runtime at send time. Every service that sends email now needs your template code, its dependencies, and a Node-compatible environment — even if the rest of your stack is Go, Python, or a Lambda you'd rather keep tiny.

No SES-native templates

SES never sees a template — only finished HTML, one message at a time. You give up everything SES's template machinery offers: stored templates, template-level rendering failure events, and the bulk API that depends on them.

Re-render per recipient

Personalization means calling render() once per recipient. Fine for transactional volume; for a 100,000-recipient newsletter it's 100,000 renders and 100,000 individual API calls your infrastructure has to execute, pace, and retry.

The third point is where teams hit the wall, because SES has a purpose-built answer for bulk — and React Email can't feed it. SendBulkEmail takes up to 50 recipients per call and personalizes server-side, but it only works with SES-native templates, and SES templates use Handlebars syntax:

What SES bulk sending actually needs
<h1>Welcome, {{name}}</h1>
<p>Your account is ready to go.</p>
<a href="{{dashboardUrl}}">Open your dashboard</a>

React Email doesn't output that. Its renderer resolves props into final values — it produces Welcome, Ada, not Welcome, {{name}}. There's no native "render to Handlebars template" mode as of July 2026. So teams that want both React authoring and SES bulk sending end up in one of two uncomfortable places: hand-converting rendered HTML into a Handlebars template (and re-converting after every design change), or templating around it with sentinel props — passing literal {{name}} strings as prop values and hoping nothing escapes or mangles them.

Both approaches work until they don't. The failure mode is always the same: the React source and the deployed SES template drift apart, and nobody notices until a broadcast goes out with last quarter's footer. For the full picture on SES's bulk machinery — templates, SendBulkEmail, list management — see our bulk email with Amazon SES guide.

Compile to Handlebars

The third path: compile React Email to SES-native templates

There's a way to keep React Email's authoring experience and SES's native template machinery: treat the React component as source code and the SES template as a build artifact. That's how SendOps templates work — the full authoring reference lives in the React Email docs: write templates as React components, and SendOps renders them to Handlebars HTML in CI with built-in escaping and strict-render rules before syncing to SES.

Author in your repo

Templates stay as React Email TSX in your repository — typed props, shared components, the preview server, code review. Nothing about how you write templates changes.

Compile at deploy

SendOps' converters compile your components into SES-native Handlebars templates at deploy time — props become {{variables}}, component markup becomes email-safe HTML, and the result is uploaded to your SES account as a real SES template.

PRs validate with previews

Every pull request that touches a template gets validated and annotated with rendered previews, so reviewers see the actual email — not a JSX diff — before it merges.

Edits write back as PRs

When someone tweaks copy in the SendOps UI, the change is written back to your repo as a pull request. The repo stays the single source of truth — no silent drift between what's deployed and what's in git.

The payoff is architectural: no runtime dependency. What runs in SES is a plain SES template — no Node in the send path, no React at send time, nothing to keep warm. And because the deployed artifact is SES-native, those templates directly power broadcasts and workflows through your own SES account, including bulk sends that would otherwise require the hand-conversion dance from the previous section.

Images & Assets

Images: the part every React Email tutorial skips

React Email's Img component takes a src URL like any image tag — and that URL is your problem, not the framework's. Emails don't bundle assets: every image is fetched by the recipient's email client from wherever you host it, at open time, possibly years after you sent the message.

Rules that save you later

  • Host on a real CDN with stable URLs. The localhost:3000 URLs that work in the preview server are broken the moment a real recipient opens the email. Assets need public, permanent, HTTPS URLs.
  • Serve from your own domain. Images loaded from a domain that matches your sending domain look coherent to spam filters and recipients alike. SendOps handles this for you — template assets are served from your own domain, not a shared third-party bucket.
  • Set explicit width and height, and always design for images-off. Many clients block remote images until the user opts in. Meaningful alt text and a layout that survives missing images aren't nice-to-haves.
  • Don't inline images as base64. Data URLs balloon message size and are stripped or blocked by major clients, including Gmail. Remote-hosted images are the only reliable path.

Testing & Gotchas

What still bites, even with React Email

React Email removes the worst of email HTML pain, but it can't repeal the laws of email clients. Four things to keep on your checklist:

Email-client CSS constraints

Email clients support a fraction of modern CSS — no flexbox or grid in Outlook's rendering engine, inconsistent media-query support, aggressive style stripping. React Email's components handle most of this by compiling to tables and inline styles, but hand-written styles inside your components can still break in ways the browser preview won't show.

Test in real clients, not just the preview

The preview server renders in a browser — a far more forgiving environment than Outlook on Windows or Gmail's clipping behavior. Before a template ships, look at it in the clients your audience actually uses, via test sends or a rendering service. Gmail clips messages around 102 KB of HTML, which is easier to hit than you'd think.

SES message size limits

As of July 2026, SES caps outbound messages at 10 MB by default (larger by quota request) — including headers, HTML, text, and attachments after encoding. Rendered React Email output is rarely the culprit alone, but combined with attachments it can be. Keep rendered HTML lean and host media remotely instead of attaching it.

Dark mode is out of your hands

Several clients recolor emails in dark mode — inverting backgrounds, adjusting text — with no reliable opt-out. Test both modes and avoid designs that depend on exact background colors, like white logos on transparent backgrounds.

FAQ

Frequently asked questions

Can I use React Email with Amazon SES?

Yes. React Email outputs standard HTML, and Amazon SES sends standard HTML. Render your React Email component to an HTML string with @react-email/render, then pass that string to the SESv2 SendEmail API. The two tools were built by different companies — React Email by Resend, SES by AWS — but they compose cleanly because HTML is the interface between them.

Does Amazon SES support React components?

No. SES has no concept of React, JSX, or components — it accepts raw HTML and plain text, plus its own native template format based on Handlebars ({{variable}}) syntax. To use React Email with SES you either render components to HTML in your own code at send time, or compile them ahead of time into SES-native Handlebars templates, which is what SendOps does.

How do I send a React Email template through SES?

Install @react-email/components, @react-email/render, and @aws-sdk/client-sesv2. Write your template as a React component, call render() on it with the recipient's props to get an HTML string, then send that string via the SESv2 SendEmailCommand with your verified from address. Your send path needs a Node.js (or compatible) runtime to execute the render step.

Can React Email work with SES bulk sending?

Not directly. SES bulk sending (SendBulkEmail) requires an SES-native template using Handlebars {{variables}}, and React Email renders to finished HTML, not Handlebars. Teams either loop SendEmail per recipient — re-rendering each time — or convert their React output to a Handlebars template by hand. SendOps closes this gap by compiling React Email components to SES-native templates automatically at deploy time.

Who makes React Email?

React Email is an open-source project created and maintained by Resend, the email API company. It's MIT-licensed and provider-agnostic: although Resend built it, nothing in React Email ties you to Resend's sending API — the rendered HTML works with Amazon SES, SMTP, or any other provider.

Get Started

Write React Email. Deploy SES templates.

Keep authoring templates as React components in your repo — SendOps compiles them to SES-native templates, previews them on every PR, and runs your broadcasts through your own SES account. Free to start.