Skip to content

Postmark server-side templates vs React Email rendered in your app

Postmark can store templates and fill them with Mustachio. Rendering React Email in your app keeps templates in the repo, typed and reviewed. How to choose, and what each costs.

Postmark2 min readships at docs/solutions/postmark/postmark-templates-vs-react-email.md

Tags: postmark · templates · react-email · mustachio · code-review

Postmark has a real template system. You design a template in the dashboard, call sendEmailWithTemplate with an alias and a model, and Postmark fills it in. It also ships good starter templates.

The other option: write templates as React Email components, render them to HTML and text in your app, and send the result with sendEmail.

Both work. They put the template in different places, and that decides who can change it and how.

Postmark templates

await client.sendEmailWithTemplate({
  From: from,
  To: to,
  TemplateAlias: "receipt",
  TemplateModel: { reference, total, lines },
  MessageStream: "outbound",
});

Pros:

  • Non-developers can edit copy without a deploy.
  • Layouts are shared across templates.
  • Postmark can validate and preview with test data.

Costs:

  • The template is not in your repo. No diff, no review, no history next to the code that fills it.
  • The model is untyped. Rename total to amount in code and the template renders an empty cell. Nothing fails.
  • Environments drift. Staging and production servers each hold their own copy. Someone fixes a typo in one.
  • Logic lives in Mustachio. Loops and conditionals are fine. Currency formatting by locale is not.

React Email in the app

await sendEmail({
  to,
  subject: `Receipt ${reference}`,
  react: ReceiptEmail({ reference, paidAt, currency, lines, total, locale }),
});

sendEmail renders the component with @react-email/render twice: once to HTML, once with plainText: true. Both go to Postmark as HtmlBody and TextBody.

Pros:

  • Typed props. A missing field is a compile error.
  • Reviewed like code. A copy change is a pull request.
  • One source for every environment.
  • Real formatting. Intl.NumberFormat and Intl.DateTimeFormat with the recipient's locale.
  • Local preview with email dev (the react-email CLI) and realistic PreviewProps.

Costs:

  • A copy change needs a deploy.
  • Rendering costs a few milliseconds per send. Render once per message, not per recipient.

How to choose

  • Engineers own the emails, and the emails carry money, links or tokens: render in the app. Receipts and sign-in links should never break silently because a model field was renamed.
  • Marketing owns the copy and changes it weekly: Postmark templates can make sense, usually on a broadcast stream.
  • Mixing is fine. Keep each template in exactly one place. A receipt that exists both as a component and as a Postmark template will drift.

If you use Postmark templates anyway

  • Keep the aliases in one constants file.
  • Type the model yourself: satisfies ReceiptModel at each call site.
  • Push templates from the repo with the Templates API (/templates/push copies between servers) instead of editing each server by hand.
  • Always send a text body. Postmark templates support a text version. Fill it.