Docs
guides · templates

Templates

Store an email body once and send it by id or slug. Merge variables fill in the per-recipient details, so your copy lives on the platform — editable without a deploy — instead of buried in your code.

A template bundles a subject and an html and/or text body. Anywhere you write {{variable}}, the rendering engine substitutes a value from the data object you pass at send time. Templates are scoped to the sending project and addressable by their auto-generated id or a human-friendly slug.

01 Create a template

POST/v1/templates

Give it a name, a subject, and a body. An optional slug gives you a stable handle like welcome to reference in code; omit it and one is generated from the name. The response includes a variables array — every {{token}} the engine found across the subject and bodies.

import { DrinClient } from "@drin00/sdk";

const drin = new DrinClient({ apiKey: process.env.DRIN_API_KEY });

const template = await drin.templates.create({
  name: "Welcome",
  slug: "welcome",
  subject: "Welcome to {{company}}, {{firstName}}",
  html: "<h1>Hi {{firstName}}</h1><p>Thanks for joining {{company}}.</p>",
  text: "Hi {{firstName}} — thanks for joining {{company}}.",
});

console.log(template.id, template.slug, template.variables);
// "tmpl_…", "welcome", ["company", "firstName"]

02 Merge variables

Variables use double-brace {{handlebars}} syntax and can appear in the subject, the HTML, and the text part. At send (or render) time, pass a data object whose keys match the token names:

  • Subject: "Welcome to {{company}}, {{firstName}}" + data: { company: "Acme", firstName: "Ada" } "Welcome to Acme, Ada".
  • A token with no matching key renders empty and is reported in the missing array when you render or preview — so you can catch gaps before sending.
Values are escaped for HTMLMerge values are safe-rendered into the HTML body, so user-supplied data can't inject markup. Author the layout in the template; pass only data through data.

03 Render and preview

Two endpoints render without sending — wire them into a live editor or a test before you ship copy.

POST/v1/templates/{id}/render

Render a saved template by id or slug with a data object. Returns the resolved { subject, html, text, missing }.

Render a saved template
// Render a saved template with sample data — no email is sent.
const rendered = await drin.templates.render("welcome", {
  company: "Acme",
  firstName: "Ada",
});

console.log(rendered.subject); // "Welcome to Acme, Ada"
console.log(rendered.missing); // [] — variables with no value supplied
Render response
{
  "subject": "Welcome to Acme, Ada",
  "html": "<h1>Hi Ada</h1><p>Thanks for joining Acme.</p>",
  "text": "Hi Ada — thanks for joining Acme.",
  "missing": []
}
POST/v1/templates/preview

Preview an unsaved draft — pass the subject/html/text inline along with data. Ideal for a template editor that re-renders on every keystroke without persisting anything.

Preview a draft
// Preview an UNSAVED draft straight from your editor.
const draft = await drin.templates.preview({
  subject: "Hi {{firstName}}",
  html: "<p>Welcome to {{company}}.</p>",
  data: { firstName: "Ada" }, // company intentionally omitted
});

console.log(draft.html);    // "<p>Welcome to .</p>"
console.log(draft.missing); // ["company"]

04 Send by templateId

POST/v1/emails

To send a stored template, set templateId (id or slug) on a normal send and supply the merge values in data. The engine resolves the subject and body server-side.

await drin.emails.send({
  from: { email: "hello@acme.com" },
  to: [{ email: "ada@example.com" }],
  templateId: "welcome", // id or slug
  data: { company: "Acme", firstName: "Ada" },
});
templateId and inline bodies don't mixA send carries either a templateId or inline html/text — not both. Combining them is a validation_error. To override just the subject, pass subject alongside templateId; it is rendered with data and wins over the template's subject.

05 Template gallery

GET/v1/templates/gallery

The gallery is a set of curated starter templates — welcome emails, receipts, password resets, and the like — each with a category, a ready-made body, and sampleData. Use one as the starting point for a create call instead of authoring from a blank page.

Browse the gallery
const starters = await drin.templates.gallery();

for (const t of starters) {
  console.log(t.category, t.name, t.subject);
}

06 Fields

namestringRequired
A human label for the template.
subjectstringRequired
The subject line. May contain {{variables}}.
htmlstringOptional
The HTML body with optional merge variables. Provide html, text, or both.
textstringOptional
The plain-text body / alternative part.
slugstringOptional
A stable, URL-safe handle to reference the template in code. Generated from name when omitted.
dataobjectOptional
At render/send time: the merge values keyed by variable name.

07 Next