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.
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}}readsvariables.user.name. - Values are stringified;
nullandundefinedrender 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>) => stringSubstitute {{var}} placeholders in a single string.
interpolateFields(fields: { subject?, html?, text? }, variables) => fieldsInterpolate 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.
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 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>ProRegister (or replace) a template. Validates it first.
email.templates.update(template): Promise<void>ProUpdate an existing template. Identical to register — an upsert by id.
email.templates.get(id): Promise<EmailTemplate | undefined>ProFetch one template by id, or undefined if absent.
email.templates.list(): Promise<EmailTemplate[]>ProList every registered template.
email.templates.delete(id): Promise<boolean>ProDelete 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).
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:
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
Sending
Everything on an EmailMessage — recipients, HTML and text bodies, reply-to, tags, headers, metadata, and the providerOptions escape hatch.
Batch sending
Send many distinct emails in one call with sendBatch — native bulk endpoints where the provider has one, graceful sequential fallback everywhere else.