> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Send React Email components with PostHog
Canonical URL: https://posthog.com/docs/workflows/transactional-emails/react-email
> **Cloud region:** This page uses US Cloud hosts. For an EU Cloud project, use `https://eu.i.posthog.com` instead of `https://us.i.posthog.com` (ingestion host).
## On this page
- [Install](#install)
- [Write an email](#write-an-email)
- [Set up the client](#set-up-the-client)
- [Send an email](#send-an-email)
- [Errors from a React send](#errors-from-a-react-send)
- [Send from Next.js](#send-from-nextjs)
- [Render the email yourself](#render-the-email-yourself)
[React Email](https://react.email) lets you write emails as React components. Keep React Email to write your emails, and send them with the [Node.js SDK](https://posthog.com/docs/workflows/transactional-emails/node.md). Pass the component to `send` as `react`, and the SDK renders its HTML and plain text. PostHog sends the content as you rendered it. You don't need a template in PostHog.
The Node.js SDK takes `react` from version 5.69.0. Before you start, [create a project secret API key](https://posthog.com/docs/workflows/transactional-emails.md#create-an-api-key) with the **Transactional email** scope.
## Install
```bash
npm install posthog-node@5 @react-email/render react-email react react-dom
```
The SDK renders the component with `@react-email/render`, version 2.1.0 or later. It's an optional peer dependency of the SDK, so install it next to `posthog-node`.
## Write an email
Write the email as a component, with the components from `react-email`:
```jsx
// emails/order-shipped.jsx
import { Body, Button, Html, Text } from "react-email"
export function OrderShippedEmail({ firstName, trackingUrl }) {
return (
Hi {firstName}, your order is on its way.
)
}
```
## Set up the client
Reuse the PostHog client your application already creates, and set `secretKey` to your project secret API key. See [set up the client](https://posthog.com/docs/workflows/transactional-emails/node.md#set-up-the-client) for the host and for local evaluation.
```javascript
// posthog.js
import { PostHog } from "posthog-node"
export const posthog = new PostHog("", {
host: "https://us.i.posthog.com",
secretKey: process.env.POSTHOG_PROJECT_SECRET_API_KEY,
// This client only sends email, so it doesn't poll flag definitions with the secret key
enableLocalEvaluation: false,
})
```
## Send an email
Pass the component as `react`. The SDK renders it to HTML, makes the plain text version from the HTML, and sends both:
```jsx
// send-order-shipped.jsx
import { OrderShippedEmail } from "./emails/order-shipped"
import { posthog } from "./posthog"
export async function sendOrderShipped({ userId, email, firstName, trackingUrl, orderId }) {
const { data, error } = await posthog.emails.send({
to: email,
templateKey: "order-shipped",
subject: "Your order shipped",
react: ,
from: "Shop ",
distinctId: userId,
// Identifies this one email, so a retry doesn't send it twice
idempotencyKey: `order-shipped-${orderId}`,
})
if (error) {
// An EmailError. Branch on error.code
throw error
}
return data.id
}
```
- **Key:** `templateKey` names the email. PostHog groups the email's metrics and sends under it, so use one key for each component, such as `order-shipped`.
- **Text:** The SDK makes the plain text with `toPlainText` from `@react-email/render`, which keeps the link of each button and drops the markup. To send your own plain text, pass `text`. The SDK then sends your `text` and renders only the HTML.
- **Props:** The SDK renders the element after you call `send`, before it sends the request. Don't change the objects that you pass as props until the send finishes.
- **Sender:** `from` needs an address on a domain that your project verified. Without it, PostHog uses the project's default sender.
- **User:** Pass the distinct ID that you pass to `identify()` as `distinctId`, so PostHog records the send and its [engagement events](https://posthog.com/docs/workflows/engagement-events.md) against that user. See [link emails to your users](https://posthog.com/docs/workflows/transactional-emails/node.md#link-emails-to-your-users).
- **Tracking:** An email with no template in PostHog is sensitive by default, so PostHog doesn't track its clicks. An order email carries no secrets, so you can pass `sensitive: false` to track its clicks. `sensitive` needs version 5.71.0 or later. See [open and click tracking](https://posthog.com/docs/workflows/transactional-emails/node.md#open-and-click-tracking).
- **Retries:** The idempotency key identifies the email. A retry with the same key in the 24 hours after PostHog queues the email doesn't send it again. See [retry safely](https://posthog.com/docs/workflows/transactional-emails.md#retry-safely-with-an-idempotency-key) and [timeouts and retries](https://posthog.com/docs/workflows/transactional-emails/node.md#timeouts-and-retries).
`send` returns `{ data, error }` and doesn't throw. `data.id` is the message ID, which you can pass to [`get`](https://posthog.com/docs/workflows/transactional-emails/node.md#check-an-emails-status) to read the email's status.
## Errors from a React send
A send with `react` can return these error codes. For each one, the SDK sends nothing:
| Code | Cause | Fix |
| --- | --- | --- |
| `invalid_form` | The send has `react` and `html`, or `react` and `templateId`. Or it has `sensitive` on an SDK version earlier than 5.71.0. `error.message` names the field. | `react` replaces `html`, so pass one of them. A template in PostHog takes no `react`, because PostHog renders the template. For `sensitive`, upgrade to version 5.71.0 or later. |
| `missing_react_renderer` | The SDK can't load `@react-email/render`. | Install `@react-email/render` with `react` and `react-dom` next to `posthog-node`. |
| `react_render_failed` | The component threw an error, or didn't finish rendering before the email request timeout. The timeout is 15 seconds by default, and `emailRequestTimeoutMs` on the client changes it. | If the component threw, `error.cause` holds its error. Fix the component, and render it in a test with the same props to see the error. |
For every other error code, see the [errors table](https://posthog.com/docs/workflows/transactional-emails.md#errors).
## Send from Next.js
Send from a route handler or a server action on the Node.js runtime, which is the default. React Email doesn't render in a Next.js route on the edge runtime, whether the SDK renders it or you do. A send with `react` from a route with `export const runtime = "edge"` returns `react_render_failed` after the email request timeout, and sends nothing.
## Render the email yourself
To keep the rendered HTML, such as to store a copy, or to send with an SDK version earlier than 5.69.0, render the component yourself. Render it with `render`, make the plain text with `toPlainText`, and pass both as `html` and `text`:
```jsx
// send-order-shipped-html.jsx
import { render, toPlainText } from "react-email"
import { OrderShippedEmail } from "./emails/order-shipped"
import { posthog } from "./posthog"
export async function sendOrderShipped({ userId, email, firstName, trackingUrl, orderId }) {
const html = await render()
const { data, error } = await posthog.emails.send({
to: email,
templateKey: "order-shipped",
subject: "Your order shipped",
html,
text: toPlainText(html),
from: "Shop ",
distinctId: userId,
idempotencyKey: `order-shipped-${orderId}`,
})
if (error) {
throw error
}
return data.id
}
```
The SDK sends content from your code from version 5.62.0. If you leave out `text`, PostHog makes it from `html`.