Radondocs

Templates

Two template layers in Radon Email — free provider-agnostic {{variable}} interpolation, and Pro managed templates with a pluggable store.

Radon has two template layers. Inline {{variable}} interpolation is free and works on every provider. Managed templates — a named, reusable store you register into — are a Pro feature Pro. Both are interpolated in the core, before any adapter sees the message, so they behave identically no matter which provider sends.

Inline interpolation (free)

Any subject, html, or text can contain {{placeholder}} markers. Pass variables and Radon substitutes them before sending.

Inline variables
await email.send({
  to: "ada@example.com",
  subject: "Welcome, {{name}}!",
  html: "<p>Hi {{name}}, your plan is <b>{{plan}}</b>.</p>",
  variables: { name: "Ada", plan: "Pro" },
});

Syntax rules, all handled for you:

  • Whitespace tolerant{{ name }} and {{name}} are equivalent.
  • Dotted paths index nested objects — {{user.name}} reads variables.user.name.
  • Values are stringified; null and undefined render as an empty string.
  • Unknown variables render empty — a missing {{coupon}} becomes "", never the literal text.

No license needed

Inline interpolation is part of a plain send() — it works on the free providers with no license and no email.init(). Only managing a template store (below) is Pro.

Interpolation helpers

The same engine is exported for direct use — handy for previewing content or validating that every placeholder has a value.

interpolate(template: string, variables: Record<string, unknown>) => string

Substitute {{var}} placeholders in a single string.

interpolateFields(fields: { subject?, html?, text? }, variables) => fields

Interpolate the subject/html/text of a message-like object; returns a new object.

extractVariables(template: string) => string[]

List the variable names a template references, deduped and in order.

Validate before sending
import { extractVariables, interpolate } from "@radonsdk/email";

extractVariables("Hi {{name}}, order {{order.id}}");
// → ["name", "order.id"]

interpolate("Hi {{name}}", { name: "Ada" }); // → "Hi Ada"

Managed templates (Pro)

Register named templates once, then reference them by id at send time. Managing the store (register, update, delete, list, get) requires a verified license — call await email.init() after setting licenseKey.

Register and send
// Register once (Pro — needs a verified license).
await email.templates.register({
  id: "welcome",
  subject: "Welcome, {{name}}!",
  html: "<p>Hi {{name}}, thanks for joining {{company}}.</p>",
  text: "Hi {{name}}, thanks for joining {{company}}.",
});

// Send referencing it — Radon loads the stored content and interpolates.
await email.send({
  to: "ada@example.com",
  template: { id: "welcome", variables: { name: "Ada", company: "Acme" } },
});

A template must have an id and at least one of subject / html / text, else register() throws TemplateInvalidError. Referencing an id that isn't in the store throws TemplateNotFoundError.

Variable precedence

At send time Radon merges variables as { ...message.variables, ...template.variables } — so a value in template.variables wins over the same key in the message's top-level variables. Fields you set directly on the message (a message-level subject) also take precedence over the stored template's field.

Managing the store

email.templates.register(template): Promise<void>Pro

Register (or replace) a template. Validates it first.

email.templates.update(template): Promise<void>Pro

Update an existing template. Identical to register — an upsert by id.

email.templates.get(id): Promise<EmailTemplate | undefined>Pro

Fetch one template by id, or undefined if absent.

email.templates.list(): Promise<EmailTemplate[]>Pro

List every registered template.

email.templates.delete(id): Promise<boolean>Pro

Delete a template by id. Returns true if one was removed.

Custom templateStore

Templates default to an in-memory store — perfect for a single process, but they vanish on restart and don't share across instances. For persistence, pass your own templateStore implementing the four-method interface (backed by a database, Redis, anything).

Bring your own store
import { RadonEmail } from "@radonsdk/email";
import type { EmailTemplate, TemplateStore } from "@radonsdk/email";

const dbStore: TemplateStore = {
  async get(id) { return db.templates.findById(id); },
  async set(template) { await db.templates.upsert(template); },
  async delete(id) { return db.templates.remove(id); },
  async list() { return db.templates.all(); },
};

const email = new RadonEmail({
  providers: { resend: {} },
  licenseKey: process.env.RADON_LICENSE_KEY,
  templateStore: dbStore,
});
await email.init();

Native provider templates

Radon does not reimplement a provider's own template system (SendGrid dynamic templates, Postmark/Mailgun/SES stored templates, Loops transactional templates). Those are exposed via the escape hatch, providerOptions — pass the provider's own template id and data straight through:

SendGrid dynamic template
await email.send({
  to: "ada@example.com",
  providerOptions: { templateId: "d-abc123", dynamicTemplateData: { name: "Ada" } },
}, { provider: "sendgrid" });

Each provider's native-template field name differs — see the per-provider notes in Providers. capabilities.templates tells you which providers have a native system.

Next steps

On this page