Skip to content

React Email

Last updated 7 September 2026

React Email is a rendering library, not a transport. It turns components into HTML in your own build — and HTML is what the send endpoint already takes, so there is nothing to configure here.

The shape of it

Two steps that people often expect to be one. render() gives you a string of HTML; emails.send takes a string of HTML. Nothing about the email leaves your process until you hand us the result.

This is deliberate. Rendering React on our side would mean running your code in our process, and there is no version of that which is worth the sandbox it would need — for a library whose whole output is a string you already have.

TYPESCRIPT
import { render } from '@react-email/render';
import { SadaSend } from 'sadasend';
import { Welcome } from './emails/welcome';

const sada = new SadaSend(process.env.SADASEND_API_KEY!);

const html = await render(<Welcome name="Dana" />);
const text = await render(<Welcome name="Dana" />, { plainText: true });

await sada.emails.send({
  from: 'you@yourdomain.com',
  to: 'dana@example.com',
  cc: ['manager@example.com'],
  bcc: ['archive@yourdomain.com'],
  reply_to: 'support@yourdomain.com',
  subject: 'Welcome aboard',
  html,
  text,
  tags: ['welcome'],
});

Stored templates use Liquid, not JSX

Templates on the account render with Liquid, and POST /templates refuses any other engine rather than accepting it and rendering Liquid anyway. Those are two different jobs: React Email is for composing in your codebase, where your build already runs; a stored template is for changing copy without a deploy.

You can use both. Render the layout once with React Email, store the result as a Liquid template, and let the variable holes be Liquid.

JSON
POST /templates
{
  "name": "welcome",
  "subject": "Welcome aboard, {{ name }}",
  "html": "<!-- the HTML React Email produced, with {{ name }} left in -->"
}

What tends to go wrong

  • Sending the component instead of the render. send({ html: <Welcome /> }) type-errors in TypeScript and silently sends "[object Object]" in JavaScript.
  • Forgetting await. render() is async in current versions, and a Promise stringifies to "[object Promise]" without complaint.
  • No plain-text part. Render it with { plainText: true } rather than stripping tags yourself.
  • Assuming inline styles are optional. React Email inlines for you; hand-written HTML elsewhere in the same message will not be.
  • Testing in a browser. Use dry_run: true to get back exactly what would have been sent, then look at that.

Checking it before you send it

Dry run validates, renders and checks suppression, and returns what would have gone out without sending it. It is the same path a real send takes, so what you read is what a recipient would have got.

TYPESCRIPT
const preview = await sada.emails.send({
  from: 'you@yourdomain.com',
  to: 'dana@example.com',
  subject: 'Welcome aboard',
  html: await render(<Welcome name="Dana" />),
  dry_run: true,
});

// preview.rendered.html — exactly what would have been sent.