[React Email](https://react.email) lets you write email templates as React components and render them to email-safe HTML. Lumail accepts that HTML directly through `html` on `POST /api/v2/emails`, so React Email covers the template layer and Lumail covers sending, domain authentication, subscriber records, and delivery tracking.

## When to Use This

Use React Email with Lumail for transactional email: receipts, password resets, magic links, invitations, order confirmations, and product notifications.

Campaigns are a different content pipeline: they accept `MARKDOWN` and `MAILY`, not `HTML`. Newsletters and broadcasts stay in Lumail campaigns and workflows so unsubscribes, segmentation, and campaign reporting remain correct.

## Prerequisites

1. A verified Lumail email domain, so your `from` address is allowed.
2. A Lumail API token (starts with `lum_`). See [Create API Token](/docs/tutorials/create-api-token).
3. A Node.js project that can render React on the server.

## Step 1 - Install the Packages

```bash
pnpm add lumail @react-email/components @react-email/render
```

`@react-email/components` provides the email-safe primitives. `@react-email/render` turns a component into an HTML string. `lumail` is the [TypeScript SDK](/docs/sdk/introduction).

To preview templates locally while you build them, add the React Email dev server:

```bash
pnpm add -D react-email
```

## Step 2 - Write the Template

Create `emails/order-confirmation.tsx`:

```tsx
import {
  Body,
  Button,
  Container,
  Head,
  Heading,
  Hr,
  Html,
  Link,
  Preview,
  Section,
  Text,
} from "@react-email/components";

type OrderConfirmationProps = {
  name: string;
  orderNumber: string;
  total: string;
  receiptUrl: string;
};

export default function OrderConfirmation({
  name,
  orderNumber,
  total,
  receiptUrl,
}: OrderConfirmationProps) {
  return (
    <Html>
      <Head />
      <Preview>Your order {orderNumber} is confirmed</Preview>
      <Body
        style={{ backgroundColor: "#f6f6f6", fontFamily: "Arial, sans-serif" }}
      >
        <Container
          style={{
            maxWidth: "600px",
            padding: "24px",
            backgroundColor: "#ffffff",
          }}
        >
          <Heading style={{ fontSize: "22px", color: "#111111" }}>
            Thanks for your order, {name}
          </Heading>
          <Text style={{ fontSize: "15px", color: "#333333" }}>
            Order <strong>{orderNumber}</strong> is confirmed. Total: {total}.
          </Text>
          <Section style={{ margin: "24px 0" }}>
            <Button
              href={receiptUrl}
              style={{
                backgroundColor: "#111111",
                color: "#ffffff",
                padding: "12px 20px",
                borderRadius: "6px",
              }}
            >
              View your receipt
            </Button>
          </Section>
          <Hr />
          <Text
            style={{ fontSize: "12px", color: "#666666", textAlign: "center" }}
          >
            Questions?{" "}
            <Link href="mailto:support@yourdomain.com">Contact us</Link>
          </Text>
        </Container>
      </Body>
    </Html>
  );
}
```

Pass per-recipient data as React props. A transactional send targets one recipient, so the values are already known at render time and you keep full type safety.

## Step 3 - Render to HTML

`render()` from `@react-email/render` v2 is asynchronous and returns the full HTML document.

```typescript
import { render } from "@react-email/render";

import OrderConfirmation from "@/emails/order-confirmation";

const html = await render(
  <OrderConfirmation
    name="Jane"
    orderNumber="A-1042"
    total="$99.00"
    receiptUrl="https://yourdomain.com/receipts/A-1042"
  />,
);
```

Render in a `.tsx` file so JSX compiles. If your send logic lives in a `.ts` file, keep rendering in a separate `.tsx` module and import the resulting function.

## Step 4 - Send Through Lumail

```typescript
import { Lumail } from "lumail";

const lumail = new Lumail({ apiKey: process.env.LUMAIL_API_KEY! });

const { id } = await lumail.emails.send({
  to: "customer@example.com",
  from: "orders@yourdomain.com",
  subject: "Your order A-1042 is confirmed",
  html,
});
```

The `preview` field is ignored for `HTML` content - only the Markdown and Tiptap pipelines inject it. The preheader comes from React Email's `<Preview>` component in your template.

Each recipient of a transactional send becomes a Lumail contact if they are not one already (`TRANSACTIONAL` unless **Add transactional recipients to the marketing list** is on), and addresses on the blocklist are rejected before queueing.

The equivalent raw API call:

```bash
curl -X POST https://lumail.io/api/v2/emails \
  -H "Authorization: Bearer $LUMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "customer@example.com",
    "from": "orders@yourdomain.com",
    "subject": "Your order A-1042 is confirmed",
    "html": "<html>...rendered react email...</html>"
  }'
```

A success response means Lumail queued the email on the transactional priority lane. See [Send Transactional Email](/docs/api-reference/api-emails-send) for the full field list and response shape.

## Complete Example

A single server function that renders and sends:

```tsx
import { render } from "@react-email/render";
import { Lumail } from "lumail";

import OrderConfirmation from "@/emails/order-confirmation";

const lumail = new Lumail({ apiKey: process.env.LUMAIL_API_KEY! });

export async function sendOrderConfirmation(order: {
  email: string;
  name: string;
  orderNumber: string;
  total: string;
  receiptUrl: string;
}) {
  const html = await render(
    <OrderConfirmation
      name={order.name}
      orderNumber={order.orderNumber}
      total={order.total}
      receiptUrl={order.receiptUrl}
    />,
  );

  return lumail.emails.send({
    to: order.email,
    from: "orders@yourdomain.com",
    subject: `Your order ${order.orderNumber} is confirmed`,
    html,
  });
}
```

## React Props vs Lumail Variables

Lumail replaces `{{variable}}` placeholders in HTML content before sending. Both mechanisms work, and they are good at different things.

| Data                                                     | Use              |
| -------------------------------------------------------- | ---------------- |
| Order totals, names, URLs, anything your app already has | React props      |
| `{{unsubscribeUrl}}`, `{{onlineUrl}}`                    | Lumail variables |
| Lumail custom subscriber fields                          | Lumail variables |

Lumail owns the unsubscribe and view-in-browser URLs, so those must stay as placeholders.

Two limits are worth knowing before you rely on placeholders in a React Email template:

- Placeholder names must be word characters. Hyphenated organization variables such as `{{org-name}}` are not substituted in `HTML` content and are delivered literally. Pass organization values as React props instead.
- `{{unsubscribeUrl}}` resolves to an empty string on transactional sends, because a transactional email has no unsubscribe link. It is only meaningful when `transactional` is false.

See [Send Email in HTML](/docs/api-reference/api-emails-html) for the full variable list.

### Escaping Braces in JSX

`{{unsubscribeUrl}}` inside JSX is parsed as a JavaScript expression containing an object literal, not as text. Always pass it as a string:

```tsx
// Correct
<Link href={"{{unsubscribeUrl}}"}>Unsubscribe</Link>
<Text>Hello {"{{name}}"}</Text>

// Broken - JSX evaluates the braces
<Link href={{unsubscribeUrl}}>Unsubscribe</Link>
```

## What Lumail Does to Your HTML

Your rendered document is sent through mostly untouched. Lumail applies these transformations:

- **Variable replacement** - `{{...}}` placeholders are substituted with subscriber values.
- **Open tracking** - a 1x1 pixel is inserted just before `</body>` when open tracking is on. React Email always emits a `</body>`, so the pixel lands inside the document.
- **Unsubscribe fallback** - for non-transactional sends only, if the HTML contains no `{{unsubscribeUrl}}`, Lumail appends an unsubscribe block to the end of the string, after your closing `</html>`. Include `{{unsubscribeUrl}}` in your template to control where it renders.
- **Plain text version** - generated by stripping HTML tags from your content.
- **Click tracking** - link rewriting for `HTML` content targets Markdown-style `[text](url)` links, so `<a href>` URLs in a React Email template are delivered unchanged. Your original links stay intact, which is usually what you want for password resets and magic links.

Set `tracking.open: false` if you do not want the pixel at all.

## Notes on Styling

- Inline styles are the safest option and are what the React Email primitives use by default.
- `<Tailwind>` works, since it inlines utility classes at render time. Responsive variants and pseudo-classes still fall back to a `<style>` block in `<head>`, which many clients ignore.
- Keep the body width at or below 600px.
- The auto-generated plain text version comes from a tag strip, so text inside `<style>` blocks can leak into it. Prefer inline styles and keep `<head>` CSS minimal.
- Preview across clients before shipping a new template.

## Preview Templates Locally

With `react-email` installed as a dev dependency, add a script:

```json
{
  "scripts": {
    "email": "email dev --dir ./emails"
  }
}
```

Then run `pnpm email` and open the local preview to iterate on the template before wiring up the send.

## Related

- [Send Transactional Email](/docs/api-reference/api-emails-send) - full endpoint reference
- [Send Email in HTML](/docs/api-reference/api-emails-html) - variables and email-safe HTML rules
- [SDK Resources](/docs/sdk/resources) - other SDK operations
- [Send Transactional Email with SMTP](/docs/tutorials/smtp-transactional-emails) - for tools that cannot call the API
