May 4, 202611 minute read

How to Send Email from a React App (2026): The Practical Guide

A developer guide to sending email from a React app: why the browser can't do it, the server-side options, building templates with react-email, and a working code example.

Ajay Sohmshetty

Ajay Sohmshetty

How to Send Email from a React App (2026): The Practical Guide

If you have ever tried to send an email straight from a React component and watched it fail silently, you already know the first lesson of this guide: the browser will not let you do it, and that is a good thing.

This post walks through how email actually gets sent from a React app in 2026. We will cover why the client side is a dead end, the server-side options you have, how to build templates with the react-email library, a minimal working example with a real provider, and a database-triggered approach for apps running on Supabase or Postgres. By the end you should know which path fits your project and be able to copy working code.

Why you cannot send email from the browser

React runs in the browser. The browser speaks HTTP. Email delivery happens over SMTP, a protocol the browser has no access to. There is no sendEmail() you can call from a component that reaches a mail server directly.

You could try to call an email API like Resend or SendGrid from your React code with fetch. The problem is the API key. Anything that ships to the browser is readable by anyone who opens the network tab or devtools. If you put RESEND_API_KEY in a NEXT_PUBLIC_ or VITE_ variable, you have just published it. Someone can grab it and send email as you, burn your quota, or get your sending domain flagged for abuse.

So the rule is simple. The React side of your app collects input and triggers an action. A server-side piece holds the secret and does the actual send. Every approach below is a different shape of that same server-side piece.

The server-side options

You have a few ways to add that server step. They differ in how much infrastructure you run and how much code you write.

Email API providers

Services like Resend, SendGrid, and Postmark give you an HTTP API and an SDK. You call them from your backend, they handle SMTP, deliverability, bounces, and the IP reputation work you do not want to do yourself. For most React apps this is the path of least resistance. You install one package, set one environment variable, and write a few lines.

We go deeper on the field in our roundups of the best email API providers and the best transactional email services, so I will keep the comparison here short. Most of what a React app sends this way is transactional email: codes, receipts, and account messages a user is actively waiting on.

A quick note on which provider tends to suit which React project. Resend is the newest of the popular options and it was built by people who also maintain react-email, so the developer experience is the smoothest if you are writing templates as components. Postmark has a long track record for fast, reliable transactional delivery and a clean dashboard, which makes it a good pick when password resets and receipts absolutely have to arrive. SendGrid is the heavyweight; it scales to very high volume and has every feature you might eventually want, at the cost of a steeper setup. If you are weighing Resend specifically, our Resend alternatives post lays out where each one is stronger.

Raw SMTP

You can also talk SMTP directly from a Node backend using a library like Nodemailer, pointed at your own mail server or a relay. This gives you control and avoids vendor lock-in. It also means you own deliverability, which is harder than people expect. Self-hosted SMTP from a fresh IP tends to land in spam until you build reputation. For a side project or an internal tool it is fine. For anything customer-facing I would reach for a provider.

Worth knowing: most providers also expose SMTP credentials, so you can use Nodemailer pointed at Resend or SendGrid and get their deliverability without their SDK. That is a useful escape hatch if you have existing SMTP code or a framework that expects an SMTP transport. The downside is you lose the nicer SDK features like the react property and structured error responses, so for a new React project I would use the SDK and keep SMTP in your back pocket.

Serverless functions

If your React app has no traditional server, a serverless function is the smallest possible backend. A Vercel function, a Netlify function, a Supabase Edge Function, or a Cloudflare Worker can hold your API key and run the send on demand. Your React component does a fetch('/api/send-email') and the function does the rest. This is the most common pattern for apps deployed on modern hosting.

Here is how the three compare for a typical React project.

Breakdown

Server-side ways to send email from React

Chart data
Server-side ways to send email from React
ItemValue
Email API provider50
Serverless function30
Raw SMTP20
ApproachInfra you runDeliverability handled byBest for
Email API providerNone (just an SDK)The providerMost apps, fastest setup
Raw SMTP (Nodemailer)Your own server or relayYouInternal tools, full control
Serverless functionA single functionWhatever provider you callClient-side React apps with no backend

Pricing across providers is current as of 2026 and changes often; confirm on each provider's site.

Building the email itself with react-email

Separate from how you send, there is the question of what you send. Coding email HTML by hand is miserable. Email clients are stuck in the past, so you end up writing nested tables and inline styles to make a layout render the same in Gmail and Outlook.

The react-email library fixes the authoring side. You write your email as a React component using primitives like Html, Container, Text, and Button, and the library renders that component to email-compatible HTML. You get components, props, and reuse, without the table soup.

To set it up, install react-email as a runtime dependency because the components and render utilities run when you send. Install @react-email/ui only if you want the local preview-server workflow:

bash
npm install react-email
npm install @react-email/ui -D

A template looks like a normal React component:

tsx
// emails/welcome.tsx
import {
  Html,
  Container,
  Text,
  Button,
} from 'react-email';

interface WelcomeEmailProps {
  name: string;
}

export function WelcomeEmail({ name }: WelcomeEmailProps) {
  return (
    <Html>
      <Container>
        <Text>Hey {name}, welcome aboard.</Text>
        <Button href="https://example.com/start">
          Get started
        </Button>
      </Container>
    </Html>
  );
}

export default WelcomeEmail;

When you need raw HTML to hand to a provider that does not accept React directly, you render it with the render function. Note that it is async:

tsx
import { render, toPlainText } from 'react-email';
import { WelcomeEmail } from './emails/welcome';

const html = await render(<WelcomeEmail name="Ada" />);
const text = toPlainText(html);

You can pass options to render for output formatting, and toPlainText gives you a text fallback. The same component drives both.

One workflow benefit that is easy to miss: react-email ships a local dev server. Run the CLI and it opens a preview of every template in your emails folder in the browser, with hot reload as you edit. You can see how a layout renders without sending yourself a hundred test emails, and you can preview the plain-text and source HTML versions side by side. For anyone who has debugged email layouts by repeatedly hitting send and refreshing an inbox, this alone is worth the install. It also means designers and non-engineers can review templates from a URL instead of waiting for a deploy.

Because templates are just components, you can compose them. A shared Layout component with your header, footer, and brand colors wraps every email, and individual templates only describe their unique body. When the footer changes you edit it once. This is the same reuse you already get in your app UI, applied to email, and it is the main reason I prefer react-email over storing HTML strings in a constants file.

A minimal working example

Let me put it together with Resend, since it has the closest tie to react-email (same team). The flow has two halves: a server route that holds the key and sends, and a React component that calls it.

First, install the SDK:

bash
npm install resend

Store the key in a server-only environment variable. In a Next.js project that is .env.local:

bash
RESEND_API_KEY=re_your_real_key_here

The server route. This example uses a Next.js App Router route handler, but the body is the same idea in an Express handler or a serverless function:

tsx
// app/api/send/route.ts
import { Resend } from 'resend';
import { WelcomeEmail } from '@/emails/welcome';

const resend = new Resend(process.env.RESEND_API_KEY);

export async function POST(request: Request) {
  const { email, name } = await request.json();

  const { data, error } = await resend.emails.send({
    from: 'Acme <[email protected]>',
    to: [email],
    subject: 'Welcome aboard',
    react: <WelcomeEmail name={name} />,
  });

  if (error) {
    return Response.json({ error }, { status: 500 });
  }

  return Response.json({ data });
}

Two things to notice. The key is read from process.env on the server, so it never reaches the browser. And Resend accepts the React component directly through the react property, so you skip the manual render step. If your provider does not support React components, call render yourself and pass the resulting html string instead.

Now the React side. The component just collects input and posts to the route:

tsx
'use client';
import { useState } from 'react';

export function SignupForm() {
  const [email, setEmail] = useState('');
  const [name, setName] = useState('');
  const [status, setStatus] = useState<'idle' | 'sending' | 'sent'>('idle');

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setStatus('sending');
    const res = await fetch('/api/send', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, name }),
    });
    setStatus(res.ok ? 'sent' : 'idle');
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e)=> setName(e.target.value)} placeholder="Name" />
      <input value={email} onChange={(e)=> setEmail(e.target.value)} placeholder="Email" />
      <button disabled={status= 'sending'}>
        {status === 'sent' ? 'Sent' : 'Sign up'}
      </button>
    </form>
  );
}

That is a complete send path. Form posts to a route, route holds the key and sends a React-rendered email. For a Vite or Create React App project with no server framework, the route becomes a standalone serverless function, but the split between UI and secret-holding backend stays the same.

Where this gets repetitive

The example above is one email. Real apps need more than one. A welcome email on signup, a confirmation when an order completes, a reset link, a nudge when someone abandons a flow. Each one needs a trigger somewhere in your code, a template, copy that does not sound robotic, and logic for when it should and should not fire. See also welcome emails that convert.

You end up scattering resend.emails.send() calls across your codebase, each tied to some event. When the schema changes or the copy needs an edit, you go hunting through routes and handlers. This is the part that quietly eats developer time, and it is why we wrote about thinking in database-driven notifications rather than imperative send calls.

The database-triggered approach

There is a different way to think about the trigger. Most of the events that should send an email already show up as changes in your database. A new signup is a new row in users. A completed order is a status change in orders. Instead of remembering to call the email API at every one of those spots, you let the database change itself be the trigger.

Your React app keeps doing what it already does: write to the database. The email layer watches for the relevant changes and sends. The send logic lives in one place, decoupled from your UI code, and you stop sprinkling API calls through your components.

There is a reliability angle here too. When the email send is wired into your request handler, an outage at your provider can fail the whole request, and a user who signed up successfully might see an error because the welcome email choked. When the trigger is a database change instead, the signup commits cleanly and the email becomes a separate concern that can retry on its own. The user's action does not depend on the email going out at that exact millisecond. You can build this yourself with Postgres triggers, listen/notify, or a queue, and plenty of teams do. The trade-off is that it is more moving parts to own.

If your app runs on Supabase or Postgres, this is where Dreamlit fits. Dreamlit connects to your database, reads the schema, and you describe in plain English what should happen. "Send a welcome email when a user signs up. Send an order confirmation when an order is marked paid." It handles the trigger logic, the templates, the copy, and the timing. You are not writing react-email components or wiring up a provider, and you are not maintaining a pile of send calls.

To be straight about scope: Dreamlit is Supabase and Postgres only, and it is centered on database-triggered email workflows. There is no REST API, SMTP relay, or SDK; it works through an MCP server, so you can drive it from Claude, Cursor, Lovable, or Bolt. If your stack is not on Postgres, or you want to own the templates in code, the react-email plus provider path above is the better fit. We cover the Supabase side in depth in how to send emails from Supabase and the template side in Supabase email templates.

How to choose

For a quick gut check:

  • If you want to own your email templates in code and you are comfortable adding a backend route, use react-email with a provider like Resend, Postmark, or SendGrid. This is the most flexible path and the one most React developers reach for.
  • If you have a pure client-side React app with no server, wrap the send in a serverless function so the key stays off the client.
  • If your app runs on Supabase or Postgres and you would rather not build and maintain the email layer at all, let the database trigger the sends through Dreamlit.

None of these is the wrong answer. They sit at different points on a build-it-yourself versus have-it-built spectrum. A solo developer shipping a side project and a team that does not want email code in its repo will land in different places, and both are reasonable.

By the numbers

Relative setup effort by sending approach

Email API + SDK 2 Provider SMTP 4 Raw SMTP (self-host) 8 Serverless fn 5
Chart data
Relative setup effort by sending approach
ItemValue
Email API + SDK2
Provider SMTP4
Raw SMTP (self-host)8
Serverless fn5

Common mistakes to avoid

A few things I see go wrong, in rough order of how often they bite people:

The exposed key is the big one. If your email send works locally but you are calling the provider from a client component, you have a leak. Move it server-side.

Sending from an unverified domain. Providers let you send from a test domain to get started, but real email needs a domain you have verified with SPF and DKIM records, or it lands in spam. We have a whole email deliverability guide on this, and Supabase auth emails are a frequent offender, which we cover in why Supabase auth emails go to spam.

Forgetting the plain-text version. Some clients and spam filters dislike HTML-only emails. react-email's render can produce a text fallback from the same component, so there is no reason to skip it.

Blocking the request on the send. If your signup route waits for the email to finish before responding, a slow provider makes your UI feel slow. For non-critical emails, fire the send and respond, or push it to a queue.

Send email from a React app and you will keep coming back to the same split: the browser collects, the server sends. Once that clicks, the rest is choosing how much of the server side you want to build yourself.


Frequently asked questions

Can you send email directly from a React app in the browser?

No. The browser has no way to talk to SMTP, and any email API key you put in client-side React code is visible to anyone who opens devtools. You always need a server-side step: a backend route, a serverless function, or a service that holds the key for you. The React part of your app collects the data and calls that endpoint.

What is react-email and do I need it?

react-email is a framework for writing email templates as React components instead of hand-coding HTML tables. In current React Email versions, you import components such as Html, Container, Text, Button, plus render and toPlainText, from react-email. You then call render() to turn the component into email-compatible HTML you can send through any provider. You do not strictly need it, but it makes templates far easier to maintain than raw HTML strings.

What is the difference between react-email and Resend?

react-email builds the email content. Resend (or SendGrid, Postmark, etc.) actually delivers it. They are made by the same team and work well together, but they solve different problems. You can use react-email with any provider, and you can use Resend without react-email by passing a plain HTML string.

Which email provider should I use with React?

For a developer-first transactional setup, Resend, Postmark, and SendGrid are all solid. Resend has the tightest react-email integration. Postmark is known for fast transactional delivery. SendGrid scales to high volume. Pick based on volume and whether you want a React-native workflow. We compare them in our email API providers and transactional email guides.

How do I keep my email API key secret in a React app?

Never put the key in client-side code or any variable prefixed with NEXT_PUBLIC_ or VITE_. Store it in a server-only environment variable like RESEND_API_KEY and read it inside an API route, server action, or serverless function. The React UI calls that route over fetch; the route holds the key.

Can I send email from a Vite or Create React App project with no backend?

Not on its own. A pure client-side React app has no server to hold credentials or talk to SMTP. You need to add a backend: a serverless function (Vercel, Netlify, Cloudflare Workers), a small Express server, or a database-triggered service. If your app runs on Supabase or Postgres, a tool like Dreamlit can handle the sending side without you writing that backend.

How does the database-triggered approach work?

Instead of calling an email API from your React code on every event, you let your database be the trigger. When a row is inserted or updated (a new signup, a completed order), that change fires the email. Your React app just writes to the database as it already does. On Supabase or Postgres, Dreamlit watches the schema and sends the right email automatically.

Do I need react-email if I use Dreamlit?

No. Dreamlit generates the templates and copy for you from your database schema and a plain-English description, so you are not writing React email components or wiring up a provider. react-email is the right choice when you want to own the templates in code. Dreamlit is the right choice when you want the email layer built and maintained for you. Sources:

About the Author

Ajay Sohmshetty
Ajay Sohmshetty

Co-Founder

Ajay is CEO and Co-Founder of Dreamlit AI. His job is to get Dreamlit in front of the businesses that need it and to make sure the company scales in a way that actually works. Full bio →

Other articles

Ajay Sohmshetty
Ajay Sohmshetty
May 23, 2025Engineering

How to Send Emails from Supabase (Beyond the 2/Hour Limit)

Supabase caps you at 2 emails per hour out of the box. Here's how to set up auth, transactional, and scheduled emails that actually scale.

Andrew Kim
Andrew Kim
May 29, 2026Company

Best Email API Providers

Choosing the best email API provider is a critical decision that impacts deliverability, developer experience, and cost efficiency.