Skip to content
zap.ts
Esc
navigateopen⌘Jpreview
On this page

Emails

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.

Running it

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.

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.

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:

// 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.

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

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.

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.

  • Mail — where the templates live and how they are sent
  • Authentication — what sends the account emails
  • Billing — what sends the payment emails

Last updated on September 22, 2026

Was this page helpful?