---
title: Emails
description: A local preview app that shows every email template in your browser while you design it.
---

`apps/emails` lets you look at your emails while you write them. It opens each
template in your browser. You edit the template, save, and the preview reloads.

It is a preview tool only. It never sends an email. The app that sends emails is
[Mail](/packages/mail).

## Running it

```bash
pnpm run dev:emails
```

This starts the React Email preview server on port 3004. Open
`http://localhost:3004`. You get a list of every file in `apps/emails/emails/`.
Click one to see it.

Keep this running in its own terminal while you work on a template.

## What is inside

The folder `apps/emails/emails/` holds seven small files. Each one is a
preview of one real email:

- `authentication-verify-email.tsx`
- `authentication-reset-password.tsx`
- `authentication-delete-account.tsx`
- `authentication-invitation.tsx`
- `billing-trial-will-end.tsx`
- `billing-payment-failed.tsx`
- `billing-canceled.tsx`

These files hold no design. Each one imports the real template from
`@zap-ts/mail/templates`. It then calls that template with example values.

```tsx
import { verifyEmail } from "@zap-ts/mail/templates";

const VerifyEmailPreview = () =>
  verifyEmail("https://app.example.com/api/auth/verify-email?token=preview").react;

export default VerifyEmailPreview;
```

Each template returns two things: a `subject` and a `react` part. The preview
file only shows the `react` part. The `subject` is used when the email is sent.

Some templates need more than one value. You pass an object instead.

```tsx
import { invitationEmail } from "@zap-ts/mail/templates";

const InvitationPreview = () =>
  invitationEmail({
    organizationName: "Acme",
    inviterName: "Dana",
    url: "https://app.example.com/accept-invitation/preview",
  }).react;

export default InvitationPreview;
```

The three billing files all call the same function, `lifecycleEmail`. They pass
a different `type` each time: `"trial_will_end"`, `"payment_failed"` or
`"canceled"`.

## What to change first

Change the templates, not the previews. The templates live in
`packages/mail/src/templates/`:

1. `authentication.tsx` holds the emails about accounts.
2. `billing.tsx` holds the emails about payments.

Both files start with the same comment:

```tsx
// TODO: Replace these placeholder templates with real branded designs.
```

The templates that ship are plain. They are a paragraph, a link, and another
paragraph. They say the right thing, but they carry no design, no logo and no
colors. Replace them with your own.

:::note
Your app and this preview app read the same files. So a change you see in the
preview is the change your users will get.
:::

To add a new email:

1. Write the template in `packages/mail/src/templates/`.
2. Export it from `packages/mail/src/templates/index.ts`.
3. Add one small file in `apps/emails/emails/` that imports it and calls it.

## Exporting the HTML

```bash
pnpm --filter emails run export
```

This turns each template into a plain HTML file. You need this when someone
else has to open your emails. A designer may want to check them. A testing
service may want to see how they look in Outlook or Gmail.

Your app does not need this step. It renders the templates itself, through
[Mail](/packages/mail).

## Why there is no deploy

There is no `deploy:emails` script, and there should not be one.

This app is a tool for you, not a product for your users. Nobody outside your
team needs it. It shows fake data on purpose, with names like `Acme` and links
that end in `?token=preview`. Putting that on a public address would only leak
what your emails look like before you finish them.

The emails your users receive do not come from here. They come from your web
app and your API, through `@zap-ts/mail`. That package picks a mail provider
and sends the message.

## What it uses from the workspace

Only one package: `@zap-ts/mail`. The preview app has no environment file, no
database and no keys. It reads templates and shows them.

This also means you can run it on its own. You do not need Postgres running. You
do not need an API key from a mail provider.

## Related

- [Mail](/packages/mail) — where the templates live and how they are sent
- [Authentication](/packages/authentication) — what sends the account emails
- [Billing](/packages/billing) — what sends the payment emails
