---
title: Building Email Templates as Components
description: "Why we use the same component model for emails and app UI, and how it simplifies our development workflow — shared design tokens, testing, and versioning."
canonical: https://nowah.xyz/blog/react-email-templates
lastModified: "2026-08-07T03:53:56.206Z"
---

# Building Email Templates as Components

Why we use the same component model for emails and app UI, and how it simplifies our development workflow — shared design tokens, testing, and versioning.

The booking confirmation email was broken for 3 days and nobody noticed. A deployment changed a component that the email template used, and the email started rendering with a missing flight time. Nobody caught it because there was no way to preview the email without actually sending it. Testing required booking a real flight in staging, finding the email in the inbox, and visually inspecting it. Nobody was doing that on every deploy.

This experience pushed us to adopt React-based email templates, and it's one of the best developer experience decisions we've made.

## React components for email

![Illustration for this section](https://pics.nowah.xyz/website-media/infrastructure-045-img-1.webp)

Our email templates are written in JSX, the same syntax we use for the app UI. A booking confirmation email is composed of React components: a header component, a flight details component, a payment summary component, and a footer component.

These components compile to the inline\-styled HTML that email clients require\. The rendering layer handles the translation from clean JSX to the ugly\-but\-necessary \`<table\>\` layouts and inline CSS that Gmail, Outlook, and Apple Mail need\.

The developer experience difference is stark\. Instead of maintaining a separate templating system with its own syntax, partial includes, and string interpolation, we write standard React components\. Conditional rendering is \`\{showHotel && <HotelSection /\>\}\`\. Data mapping is \`\{flights\.map\(f =\> <FlightRow key=\{f\.id\} flight=\{f\} /\>\)\}\`\. These are patterns every React developer already knows\.

The email service file has a \`\.tsx\` extension because it's a single typed language across the stack JSX\. Type checking works across the email templates just like it does in the app\. A missing required prop on the FlightDetails component is a type error, caught at build time\.

## Shared design tokens

Our email templates use the same colors, fonts, and spacing as the app. Both share a design token system that defines the brand: primary color, text colors, spacing scale, font families.

This consistency matters for brand trust. When a traveler gets a booking confirmation email, it should look like it came from the same product they just used. Mismatched colors or fonts create a subtle "is this legit?" feeling that undermines trust.

Sharing tokens also reduces maintenance. When we update the primary color, it updates everywhere: app, web, and emails. One change, consistent branding.

## Testing email rendering

![Supporting diagram](https://pics.nowah.xyz/website-media/infrastructure-045-img-2.webp)

The biggest improvement from React-based emails is testability. We can render an email template to HTML in a test, without sending it. We can inspect the output. We can snapshot test it for regressions.

Our email testing covers:

**Data injection.** Feed the template with test data and verify the output includes the right flight times, traveler names, and amounts. This catches the class of bugs where a template references a field that doesn't exist in the data model.

**Conditional sections.** Test that the hotel section only renders when there's a hotel booking. Test that the [multi-city](/blog/multi-city-flight-booking-ai-agents) itinerary layout activates for multi-city trips. Test that the single-flight layout activates for simple bookings.

**Required fields.** Test that the template handles missing or null fields gracefully rather than rendering "undefined" or crashing.

For cross-client rendering, we use preview tools that show how the email looks in Gmail, Outlook, and Apple Mail. These are manual checks, not automated tests, but they run as part of our pre-release QA process.

Gmail is generally the most forgiving. It handles modern CSS reasonably well. Outlook is the nightmare. It uses Word's rendering engine for HTML email, which means many CSS properties don't work. Dark mode in email clients adds another layer of chaos. We test all of these.

## Dynamic data injection

A booking confirmation email needs real data: flight details, traveler information, payment summary, and a deep link back to the trip.

The email worker receives a job with the booking ID. It queries the database for the booking, the associated trip, the traveler profile, and the payment record. It assembles this data into a typed object that matches the email template's props interface.

Type checking between the data assembly and the template props catches mismatches at build time\. If the template expects a \`departureTime\` field and the data assembly provides \`departure\_time\`, the a single typed language across the stack compiler catches it\. This eliminates an entire class of runtime bugs where the template receives data in the wrong shape\.

## Template versioning

We version our email templates alongside our application code. Each deployment produces a specific template version. If we need to debug a weird-looking email from two weeks ago, we can check which template version was deployed at that time and reproduce the rendering.

Template changes go through code review. Email templates are product-facing, customer-visible artifacts. A typo in a booking confirmation email is an embarrassment. A wrong flight time is a potential safety issue. Reviews catch these before they ship.

Rollback for email templates is the same as rollback for the application: deploy the previous version. Since email sending is asynchronous, a quick rollback means only a small window of emails sent with the bad template.

## Getting started with React email

If you want to adopt React-based email templates:

Start with your most critical transactional email. The booking confirmation, the password reset, or the payment receipt. Convert one template and validate the workflow before converting everything.

Accept that email HTML is ugly. The compiled output will be full of tables, inline styles, and compatibility hacks. Don't look at the output. Work in the JSX, which is clean.

Test in Outlook. If it works in Outlook, it works everywhere. Outlook's rendering engine is the worst common denominator. Fix Outlook first, then enjoy that everything else looks better.

Share design tokens between your app and your emails. Maintaining two color palettes is a guaranteed source of brand inconsistency.

Set up preview rendering in your development workflow. The ability to see what the email looks like without sending it is the single biggest improvement over traditional templating.

---

Nowah is an AI travel agent that searches and books real flights and hotels through conversation — no filters, no thirty open tabs. [Plan your next trip](https://app.nowah.xyz).
