Sep 18, 2026 ยท 10 min read

How to Build Email Templates with JavaScript

Build maintainable email templates with JavaScript using components, typed data, HTML rendering, CSS inlining, plain-text output, and repeatable inbox tests.

How to Build Email Templates with JavaScript

JavaScript is useful for building email templates because it turns repeated markup into components and message data into a deterministic HTML artifact.

The runtime is not sent to the inbox. Your application renders the template on the server or during a build, converts the result to email-safe HTML, generates a plain-text alternative, and passes both parts to a delivery provider.

The JavaScript email pipeline

A maintainable pipeline looks like this:

typed message data
    โ†“
template components
    โ†“
rendered HTML source
    โ†“
email CSS + layout compilation
    โ†“
sanitized HTML and plain text
    โ†“
provider API

Keep rendering separate from sending. A template test should not call an external provider, and a delivery adapter should not contain layout markup.

Choose an authoring approach

Template literals

Template literals work for small projects with a few stable messages.

function passwordResetEmail({ name, resetUrl }) {
  return `<!doctype html>
  <html lang="en">
    <body>
      <h1>Reset your password</h1>
      <p>Hi ${escapeHtml(name)},</p>
      <p><a href="${escapeAttribute(resetUrl)}">Choose a new password</a></p>
    </body>
  </html>`;
}

The simplicity is attractive, but escaping, reusable layout, and conditional sections quickly become your responsibility.

React Email

React Email provides email-oriented components and renders JSX to HTML or plain text. It is a natural fit for TypeScript applications and teams that already review React components.

import { Button, Container, Html, Preview, Text } from 'react-email';

export function ResetEmail({ resetUrl }: { resetUrl: string }) {
  return (
    <Html lang="en">
      <Preview>Choose a new password</Preview>
      <Container>
        <Text>Use the link below to reset your password.</Text>
        <Button href={resetUrl}>Reset password</Button>
      </Container>
    </Html>
  );
}

See React Email with Tailwind CSS for styling and rendering details.

MJML with JavaScript

MJML is an email-specific language with a Node package. JavaScript can assemble or render MJML, then compile it to responsive HTML.

This fits teams that want an established email abstraction without making React part of the template model.

Tailwind-based compiler

A Tailwind email workflow lets JavaScript render data into utility-class markup before a compiler inlines CSS and rewrites the layout.

This is useful when the product design system already uses Tailwind. The key is to send compiled output, not raw utilities. Start with how to build a Tailwind email.

Define a message contract

Make every template accept one explicit data object:

type ReceiptEmailData = {
  customerName: string;
  receiptNumber: string;
  issuedAt: string;
  currency: 'USD' | 'EUR' | 'GBP';
  items: Array<{
    description: string;
    quantity: number;
    unitPrice: number;
  }>;
  receiptUrl: string;
};

Typed contracts expose missing values before a customer receives a broken message. They also create reliable fixtures for previews and tests.

Avoid passing a full database model into the template. Map application state to the minimal, display-ready email data.

Escape content correctly

Template literals do not escape values automatically.

Use different handling for:

  • text nodes;
  • HTML attributes;
  • URLs;
  • intentionally sanitized rich text; and
  • provider merge syntax.

Do not use a generic replacement function as a full HTML sanitizer. Prefer a rendering library that escapes text by default, and validate URLs before inserting them into href or src.

Build small email components

A useful component library includes:

  • document shell and preheader;
  • centered container;
  • section;
  • heading and paragraph;
  • button;
  • image;
  • divider and spacer;
  • data table; and
  • compliance footer.

Keep components email-specific. A web button component may assume JavaScript, hover behavior, CSS variables, or Flexbox. Reusing its design tokens is safer than reusing its markup.

Style the source

You can use inline style objects, component attributes, MJML attributes, or Tailwind utilities.

Whichever approach you choose, production output should contain conservative CSS:

  • explicit font stacks;
  • pixel-based spacing and dimensions where appropriate;
  • inline base declarations;
  • presentation tables for structural layout;
  • small retained media queries; and
  • fallbacks for Outlook-sensitive components.

Read CSS inlining in HTML email for the cascade rules that matter.

Generate plain text

Every message should include a useful plain-text part.

Do not strip tags blindly and accept the result. Good text output should:

  • preserve meaningful paragraph breaks;
  • show link destinations where useful;
  • represent list items clearly;
  • omit decorative alt text;
  • keep receipt rows understandable; and
  • include required preference or unsubscribe URLs.

Snapshot-test HTML and text together so the two versions do not drift.

Render without sending

Expose a pure rendering function:

type RenderedEmail = {
  subject: string;
  html: string;
  text: string;
};

async function renderReceiptEmail(data: ReceiptEmailData): Promise<RenderedEmail> {
  // Render components, compile email CSS, sanitize, then derive plain text.
}

This function should not know about API keys, queues, or recipient lists. It can run in unit tests, preview servers, deployment builds, and background jobs.

Connect a provider adapter

Keep delivery behind a narrow interface:

type SendEmailInput = {
  to: string;
  from: string;
  subject: string;
  html: string;
  text: string;
};

interface EmailTransport {
  send(input: SendEmailInput): Promise<{ messageId: string }>;
}

Now the rendering layer can stay unchanged when the application moves between providers.

Add development previews

A useful preview environment offers:

  • template selection;
  • fixture selection;
  • desktop and mobile widths;
  • HTML and plain-text tabs;
  • images-disabled mode;
  • long-content fixtures;
  • links to the source file; and
  • a test-send action separated from preview.

Preview speed improves iteration, but it does not replace inbox testing.

Test JavaScript email templates

Use four layers.

Data tests

Validate required fields, URL schemes, date formatting, currency, and conditional branches.

Structural tests

Assert one h1, a preheader, valid links, image alt text, a language attribute, and no scripts or forms.

Snapshot or fixture tests

Snapshots are helpful when they stay small and readable. Prefer focused component fixtures over one enormous snapshot that reviewers automatically approve.

Inbox tests

Send compiled output to Gmail, Outlook, Apple Mail, and mobile clients. Use the complete email testing guide for the matrix.

Avoid common JavaScript email mistakes

Rendering on the client

Emails should be rendered before delivery. Do not expect the recipient's inbox to run React or JavaScript.

Sharing web components blindly

Share types, copy, tokens, and pure formatters. Use email-specific markup components.

Sending before awaiting render

Many render and compile functions are asynchronous. Await the final HTML and validate that it is non-empty before calling the provider.

Mixing delivery and presentation

Provider calls inside components make previews and tests fragile. Render first, send second.

Ignoring long content

Fixtures should include realistic worst cases, not only short English demo copy.

Frequently asked questions

Which JavaScript email builder should I choose?

React Email is a strong choice for React/TypeScript teams; MJML is strong for an email-specific DSL; a Tailwind email compiler fits teams that want utility-based source. Choose based on source ownership and output requirements.

Can Node.js send HTML email directly?

Yes, but use an email provider or SMTP transport for delivery. Render and validate the template before passing HTML and text to that transport.

Can email templates use JavaScript in the inbox?

No. JavaScript is used during template generation, not inside the delivered message.

Should templates live in the application repository?

Application-owned transactional emails usually should. Marketing teams may prefer a governed editor, but approved exports should still be versioned.

How do I prevent injection?

Use a renderer that escapes values by default, validate URLs, sanitize intentionally allowed rich text, and never concatenate untrusted HTML.

Treat HTML as a build artifact

JavaScript makes email maintainable when it creates clear boundaries: typed data, reusable components, pure rendering, email-aware compilation, and an independent transport.

Keep the source pleasant to edit and the output conservative. Test both. That is the foundation of a dependable JavaScript email system.

Share with friends

Ready to ship email-safe HTML?

Start with Tailwind markup, compile to clean HTML, and always preview or send a test before campaigns.