Templates
Reusable email templates with Handlebars, immutable versions, shared partials and an optional AI assist that drafts a template from a plain-English prompt.
How templates work
A template is a saved (slug, subject, body) triple, scoped to your product. Every save creates a new immutable version — older versions stay queryable so in-flight sends are deterministic even mid-edit.
You send by slug; the platform resolves the currently active version at send time and pins it into the queued message. Newsletters pin the version at fire time so a paused-and-resumed broadcast uses one consistent version end-to-end.
Create a template
POST /api/v1/templates
{
"slug": "welcome",
"subject": "Welcome to {{product_name}}, {{name}}!",
"body": "<!DOCTYPE html>\n<html>..."
} Slug rules: lowercase letters, digits, hyphens. Must be unique within the product. Once created, the slug is immutable — to "rename," create a new template and migrate references.
Handlebars syntax
Both subject and body are rendered through Handlebars at send time. You get:
{{variable}}placeholders- Conditionals:
{{#if foo}}...{{/if}} - Loops:
{{#each items}}...{{/each}} - Partials:
{{> my-partial}}
Always-available variables
| Variable | Meaning |
|---|---|
{{name}} | Contact's display name (or the synthetic preview name during preview). |
{{email}} | Contact's email address. |
{{unsubscribeUrl}} | Per-list one-click unsubscribe URL. Used inside the partial, not directly. |
Anything in the API call's variables object is also available. Unknown placeholders render as empty strings — they don't crash the send.
Partials
Reusable HTML blocks that live at the product level and can be included in any template:
{{> header}}
{{> product-card}}
{{> unsubscribe-footer}} unsubscribe-footer partial is mandatory on every marketing email. It renders the compliant unsubscribe block (CAN-SPAM, RFC 8058 one-click) and lets us track per-list opt-outs. The AI generator refuses to produce a body without it.
HTML rules for inbox-safe email
- Use a full HTML document starting with
<!DOCTYPE html>. - Inline all styles — most email clients (Gmail web, Outlook desktop) strip
<style>blocks. - Table-based layout for max compatibility. CSS grid and flexbox are unreliable in Outlook.
- Keep visible content under 600px wide.
- System fonts only unless you accept a fallback. Embedded fonts don't render in Outlook.
AI template generator
Skip the boilerplate: describe what you want in plain English and Mailazy drafts a working template (subject + slug + body) — inline-styled, table-laid, with the unsubscribe partial wired in.
Configure once
Bring your own OpenRouter API key (one-time, per product) under Settings → AI. Choose a model (any OpenRouter-compatible model; Claude Sonnet and Haiku work especially well for HTML email). Mailazy never proxies through our LLM provider — your bill is between you and OpenRouter.
Generate from a prompt
From the Templates page, click + Generate with AI. The modal asks for:
- Prompt — e.g. "Welcome email for SaaS signup, friendly but professional, ~120 words, CTA to dashboard"
- Additional variables — any Handlebars vars the AI should use beyond the always-on set, e.g.
product_name, dashboard_url, plan - Tone hint (optional) — "casual," "formal," "no emoji"
Hit Generate. The right pane fills with a live iframe preview. Click Regenerate to retry, Edit body in raw editor to tweak, or Save as template to commit. Nothing is saved automatically — every generation is a draft until you press Save.
Per-tenant brand voice
Set a defaultSystemPrompt on your AI config to inject brand-voice rules into every generation (e.g. "always end with the line 'Built in Bengaluru.'"). It's appended to the built-in rules, never replacing them.
Rate limits
20 generations per minute per product. Bumps available on Scale tier.
Preview and test-send
Every saved template has a View action that opens a live preview using a synthetic contact (or any real contact id you paste). The preview renders through the exact same Handlebars + partial pipeline as a production send — what you see is what gets delivered.
Send a test email
Inside the preview modal, drop an address in Test send to and click Send. The email routes through your tenant SMTP exactly like a real send, but does not write a send_logs row, does not increment newsletter counts, and (intentionally) does not check suppressions — test sends are operator-initiated and meant to verify rendering.
Programmatic equivalent:
POST /api/v1/templates/{id}/preview → { subject, html, fromAddress }
POST /api/v1/templates/{id}/test-send → { to, sentAt } Rate limit on test-send: 10 / minute / template, returning 429 with Retry-After.
Versioning
Every POST /api/v1/templates with an existing slug creates a new active version; the prior version is retained for audit and for in-flight messages. Query by version with GET /api/v1/templates/{slug}?version=4.
To roll back, send the previous body as a fresh save — there's no destructive delete on versions. Templates marked inactive disappear from dropdowns but remain queryable.