EmailTemplatesTemplate Engines

Template Engines

Use template engines for dynamic email content with @visulima/email

Template Engines

@visulima/email supports multiple template engines for rendering dynamic email content. All template engines implement the TemplateRenderer interface and can be used with the MailMessage.view() method.

type TemplateRenderer = (template: unknown, data?: Record<string, unknown>, options?: Record<string, unknown>) => string | Promise<string>;

Available Template Engines

Handlebars

Handlebars provides logic-less templating with support for helpers and partials.

Install: npm install handlebars

import { MailMessage } from "@visulima/email";
import { renderHandlebars } from "@visulima/email/template/handlebars";

const message = new MailMessage().to("user@example.com").from("sender@example.com").subject("Welcome");

await message.view(renderHandlebars, "<h1>Hello {{name}}!</h1><p>Welcome to {{company}}.</p>", {
    name: "John",
    company: "Acme Corp",
});

Registering Helpers

import { registerHandlebarsHelper } from "@visulima/email/template/handlebars";

registerHandlebarsHelper("uppercase", (str: string) => str.toUpperCase());

await message.view(renderHandlebars, "<h1>Hello {{uppercase name}}!</h1>", {
    name: "John",
});

Registering Partials

import { registerHandlebarsPartial } from "@visulima/email/template/handlebars";

registerHandlebarsPartial("header", "<header><h1>{{title}}</h1></header>");

await message.view(renderHandlebars, "{{> header}}<p>Content here</p>", {
    title: "Welcome",
});

MJML

MJML is a framework that makes it easy to create responsive emails. It compiles MJML markup to HTML.

Install: npm install mjml

import { MailMessage } from "@visulima/email";
import mjml from "@visulima/email/template/mjml";

const message = new MailMessage().to("user@example.com").from("sender@example.com").subject("Welcome");

const mjmlTemplate = `
<mjml>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text>Hello World!</mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>
`;

await message.view(mjml, mjmlTemplate);

MJML Options

OptionTypeDefaultDescription
beautifybooleanfalseBeautify the output HTML
minifybooleanfalseMinify the output HTML
keepCommentsbooleantrueKeep comments in the output
validationLevel"strict" | "soft" | "skip""soft"Validation strictness level
fontsRecord<string, string>-Custom fonts map
await message.view(
    mjml,
    mjmlTemplate,
    {},
    {
        minify: true,
        validationLevel: "strict",
    },
);

React Email

React Email lets you build beautiful emails using React components.

Install: npm install @react-email/render @react-email/components

import { MailMessage } from "@visulima/email";
import reactEmail from "@visulima/email/template/react-email";
import { Html, Head, Body, Text } from "@react-email/components";

const WelcomeEmail = ({ name }: { name: string }) => (
    <Html>
        <Head />
        <Body>
            <Text>Hello {name}!</Text>
        </Body>
    </Html>
);

const message = new MailMessage()
    .to("user@example.com")
    .from("sender@example.com")
    .subject("Welcome");

await message.view(reactEmail, <WelcomeEmail name="John" />);

React Email Options

OptionTypeDefaultDescription
plainTextbooleanfalseRender as plain text instead of HTML
prettybooleanfalsePretty print the output HTML

Vue Email

Vue Email lets you build emails with Vue components.

Install: npm install @vue-email/render @vue-email/components

import { MailMessage } from "@visulima/email";
import vueEmail from "@visulima/email/template/vue-email";

const message = new MailMessage().to("user@example.com").from("sender@example.com").subject("Welcome");

await message.view(vueEmail, VueEmailComponent, {
    name: "John",
    company: "Acme",
});

Vue Email Options

OptionTypeDefaultDescription
plainTextbooleanfalseRender as plain text instead of HTML
prettybooleanfalsePretty print the output HTML
htmlToTextOptionsRecord<string, unknown>-Options for HTML-to-text conversion

HTML-to-Text

The HTML-to-text converter transforms HTML content into readable plain text. It is used automatically by MailMessage when only HTML content is provided (auto-text generation).

Install: npm install html-to-text

import htmlToText from "@visulima/email/template/html-to-text";

const text = htmlToText("<h1>Hello World</h1><p>Welcome to our platform.</p>");
// Output: "HELLO WORLD\n\nWelcome to our platform."

HTML-to-Text Options

OptionTypeDefaultDescription
wordwrapnumber | false80Word wrap limit (false to disable)
preserveNewlinesbooleanfalsePreserve newlines in output
longWordSplitobject-Options for handling long words
selectorsobject[]-Custom selectors for formatting elements

Liquid

LiquidJS is the Shopify templating language, with tags, filters, and control flow.

Install: npm install liquidjs

import { MailMessage } from "@visulima/email";
import liquid from "@visulima/email/template/liquid";

const message = new MailMessage().to("user@example.com").from("sender@example.com").subject("Welcome");

await message.view(liquid, "{% if vip %}VIP {% endif %}Hello {{ name | upcase }}!", {
    name: "ada",
    vip: true,
});

The render function accepts an options object that is forwarded to the LiquidJS Liquid constructor as the third argument.

JSX Email

jsx-email renders email components with a Preact-compatible runtime.

Install: npm install jsx-email

import jsxEmail from "@visulima/email/template/jsx-email";
import { Text } from "jsx-email";

const html = await jsxEmail(<Text>Hello jsx-email</Text>);

// Pass `{ plainText: true }` as the third argument to render plain text instead.
const text = await jsxEmail(<Text>Plain body</Text>, undefined, { plainText: true });

i18n (Localized Rendering)

The i18n helper wraps any template engine with locale resolution: it picks the template for the requested locale, falling back to the language subtag (en-USen) and then to a default locale.

import { renderHandlebars } from "@visulima/email/template/handlebars";
import { createI18nRenderer, renderI18n } from "@visulima/email/template/i18n";

const templates = {
    en: "Hello {{name}}",
    de: "Hallo {{name}}",
};

// One-off render:
await renderI18n(templates, "de", renderHandlebars, { name: "Ada" }); // "Hallo Ada"

// Reusable renderer bound to an engine + default locale:
const render = createI18nRenderer(renderHandlebars, "en");

await render(templates, "fr", { name: "Cleo" }); // falls back to "en" → "Hello Cleo"

resolveLocale(templates, locale, defaultLocale?) is also exported if you only need the resolution logic. renderI18n throws an EmailError when neither the locale, its language subtag, nor the default locale has a template.

Auto-Generated Text Content

When you use message.view() or set HTML content with message.html(), @visulima/email automatically generates a plain text version using the HTML-to-text converter. This ensures email clients that don't support HTML still display readable content.

To disable auto-text generation:

await message.view(renderHandlebars, template, data, {
    autoText: false,
});

Text-Only Templates

Use viewText() for text-only template rendering:

import { renderHandlebars } from "@visulima/email/template/handlebars";

const message = new MailMessage().to("user@example.com").from("sender@example.com").subject("Welcome");

await message.viewText(renderHandlebars, "Hello {{name}}! Welcome to {{company}}.", {
    name: "John",
    company: "Acme Corp",
});

Custom Template Engines

You can create custom template engines by implementing the TemplateRenderer type:

import type { TemplateRenderer } from "@visulima/email";

const myRenderer: TemplateRenderer = (template, data, options) => {
    let result = template as string;

    for (const [key, value] of Object.entries(data || {})) {
        result = result.replaceAll(`\${${key}}`, String(value));
    }

    return result;
};

await message.view(myRenderer, "<h1>Hello ${name}!</h1>", {
    name: "John",
});
Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues