Handlebars
Templates

Handlebars

Build email templates with Handlebars

The @betternotify/handlebars adapter compiles Handlebars templates and renders them with the procedure's validated input at send time. Templates are pre-compiled at creation time for fast renders.

Install

pnpm add @betternotify/handlebars handlebars

Basic usage

import { z } from 'zod';
import { handlebarsTemplate } from '@betternotify/handlebars';

const welcome = rpc
  .email()
  .input(z.object({ name: z.string() }))
  .subject(({ input }) => `Welcome, ${input.name}!`)
  .template(handlebarsTemplate('<h1>Hello {{name}}</h1>'));

The template receives the procedure's input as the Handlebars data context. All standard Handlebars syntax works: {{#each}}, {{#if}}, {{#unless}}, {{> partial}}, and custom helpers.

Text and subject templates

Pass text and subject options to render all three from a single adapter:

.template(handlebarsTemplate(
  '<h1>Hello {{name}}</h1><ul>{{#each items}}<li>{{this}}</li>{{/each}}</ul>',
  {
    text: 'Hello {{name}}. Items: {{#each items}}{{this}} {{/each}}',
    subject: 'Welcome, {{name}}!',
  },
))

All templates share the same Handlebars instance, so helpers and partials registered on the adapter are available in all three.

Helpers and partials

.template(handlebarsTemplate(
  '<p>{{upper name}}</p><div>{{> footer}}</div>',
  {
    helpers: {
      upper: (str: string) => str.toUpperCase(),
    },
    partials: {
      footer: '<p style="color: gray">Sent by Acme Corp</p>',
    },
  },
))

Each adapter gets its own isolated Handlebars instance — helpers and partials don't leak between templates.

API

handlebarsTemplate(source, opts?)

Prop

Type

Returns a TemplateAdapter<TInput>.

When to use Handlebars

Handlebars is a good fit when you have existing .hbs templates from another system, when non-developers need to edit templates without touching TypeScript, or when you want logic-light templates with simple {{variable}} interpolation. For responsive email layouts, consider combining Handlebars with MJML — the MJML adapter uses Handlebars under the hood.

On this page