# Previewing React Email templates locally, and what the preview cannot tell you

> React Email's preview server renders your templates with realistic props and hot reload. Here is how to set it up, what to check, and the four failure modes only a real client will show you.

Email is the last place in a modern stack where you cannot see what you built
until someone receives it. React Email fixes most of that: templates are
components, they render in a browser, and they hot reload.

Most of it. There is a specific list of things the preview cannot show you, and
knowing where the line is saves you from shipping a message that looked perfect
locally and arrives broken in Outlook.

## Running the preview

```bash
bun run email:dev
```

That runs `email dev --dir src/lib/email/templates`, the CLI that ships with the
`react-email` package. Point it at the directory where the templates live rather than moving them to the tool's default
location: templates belong next to the code that sends them, not in a top-level
folder that looks like content.

It opens a browser with every template in the directory listed down the side. It
watches the files, so editing a component re-renders immediately.

Each template needs a **default export** to appear. A named export renders
nothing and the tool does not explain why, which costs people twenty minutes the
first time.

## PreviewProps: realistic data, no credentials

```tsx
MagicLinkEmail.PreviewProps = {
  url: "https://example.com/auth/verify?token=preview-token-not-a-real-credential",
  expiresInMinutes: 15,
  requestedFrom: "Chrome on macOS, Lisbon",
} satisfies MagicLinkEmailProps;
```

`satisfies` is doing real work: change a prop type and the preview data fails to
typecheck instead of silently rendering `undefined`.

Two rules for the data itself. Make it **realistic**: a name of "Test" and a
total of `$1.00` hide the layout bugs that a 40-character company name and
`$1,234.56` expose. And never put a **real** credential in there; preview props
sit in your repository forever.

## What to check in the preview

- **The plain-text tab.** Read it as if it were all you had. If the message
  makes no sense without the button, the copy is wrong, and a text-only client
  is exactly what some recipients use.
- **The `<Preview>` line.** It is the grey text next to the subject in an inbox.
  Without one, the client shows the first words of the body, which is usually
  "View this email in your browser" or an alt attribute.
- **Long values.** A 60-character name, a nine-line item list, a URL with no
  spaces. Overflow is the most common template bug and the easiest to find.
- **Empty and singular states.** One line item, zero line items, no optional
  prop. Optional props are optional at runtime whether or not you rendered them
  in the preview.
- **The rendered HTML source.** If you see a `<div>` doing layout, Outlook will
  disagree with you. React Email's components emit tables for a reason.

## Rendering to HTML in a test

For a snapshot test or a script:

```ts
// `render` comes from react-email, which re-exports @react-email/render. Resend
// renders the `react` field with that same package on the server.
import { render } from "react-email";
import WelcomeEmail from "@/lib/email/templates/welcome";

const html = await render(WelcomeEmail({ name: "Sam", ctaUrl: "https://example.com/app" }));
const text = await render(WelcomeEmail({ name: "Sam", ctaUrl: "https://example.com/app" }), {
  plainText: true,
});
```

Worth asserting in a unit test: that the CTA URL appears in both outputs, that no
`undefined` string leaked in, and (for anything security-relevant) that a token
appears exactly once, in the href.

## What the preview cannot tell you

**1. How Outlook renders it.** The Windows Outlook clients use the Word
rendering engine, which ignores flexbox, grid, most positioning and many CSS
properties. Your browser preview will not warn you. Staying inside
React Email's components (imported from `react-email`) keeps you on the safe path, and adding raw markup is
where people leave it.

**2. Whether it lands in the inbox.** Placement depends on authentication,
domain reputation, content signals and the recipient's own filters. None of that
exists locally. `bun run email:send-test you@example.com` is how you find out.

**3. Whether Gmail clips it.** Gmail truncates around 102KB of HTML and hides
the rest behind "View entire message". Inline data URIs eat that budget fast.
Check the rendered size, not the source size.

**4. How it looks in dark mode.** Some clients invert colours, some respect
`prefers-color-scheme`, some do neither. The templates here are monochrome and
inherit the client's own palette specifically so this cannot go wrong: the
moment you hardcode a colour, you own that problem in every client.

## The loop that works

1. Build in the preview until the layout and copy are right.
2. `bun run email:send-test you@example.com`.
3. Open it on a phone and on a desktop client. Check the subject is not cut off
   at 40 characters, that the links survived the client rewriting them, and that
   it landed in the inbox rather than Promotions.
4. If you support enterprise customers, get one real Outlook screenshot before
   you ship. Once, per template. It is the only way to know.

Steps 1 and 2 are fast enough to do on every change. Steps 3 and 4 are worth it
per template, not per edit.

---

Agentic Boilerplate: A Next.js repo your agent already knows. Free during launch, then $99 once.

- Site map for agents: https://agenticboilerplate.com/llms.txt
- Public API: https://agenticboilerplate.com/openapi.json
- Contact: agenticstudio@gmail.com
