Templates
Create and manage reusable email templates with variable substitution.
Templates let you define reusable HTML email layouts with {{variable}} placeholders. When you send an email using template_id, Sendry substitutes the variables before delivery.
Templates can be grouped with an optional folder label. A folder is a plain string, not a resource: it exists as long as at least one template carries it and disappears when the last one moves out. Folders are a single level; slashes are rejected.
Create template
POST /v1/templates
Requires full_access scope.
Request body
{
"name": "Welcome Email",
"folder": "Onboarding",
"subject": "Welcome to {{company_name}}, {{name}}!",
"html": "<h1>Hi {{name}}</h1><p>Thanks for joining {{company_name}}.</p>",
"text": "Hi {{name}}, thanks for joining {{company_name}}."
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Human-readable template name. |
folder | string | No | Folder to group the template under. Max 64 characters; slashes and control characters are rejected. Surrounding whitespace is trimmed and internal runs are collapsed to a single space. Omit it, or send null or "", to leave the template unfiled. |
subject | string | Yes | Subject line with optional {{variables}}. |
html | string | No* | HTML body. At least one of html or text required. |
text | string | No* | Plain-text body. |
Response
{
"id": "tmpl_abc123",
"name": "Welcome Email",
"folder": "Onboarding",
"subject": "Welcome to {{company_name}}, {{name}}!",
"created_at": "2025-03-12T09:00:00Z",
"updated_at": "2025-03-12T09:00:00Z"
}
Get template
GET /v1/templates/:id
Requires read_only scope.
List templates
GET /v1/templates
Requires read_only scope.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 50 | Results per page, 1 to 100. |
cursor | string | next_cursor from the previous response. | |
folder | string | Return only templates in this folder. Pass the literal value none to return only templates that are in no folder. Omit it to return every template. |
Response
{
"data": [
{
"id": "tmpl_abc123",
"name": "Welcome Email",
"folder": "Onboarding",
"subject": "Welcome to {{company_name}}, {{name}}!",
"created_at": "2025-03-12T09:00:00Z",
"updated_at": "2025-03-12T09:00:00Z"
}
],
"has_more": false,
"next_cursor": null
}
folder is null on templates that are not in a folder.
List folders
GET /v1/templates/folders
Requires read_only scope. Returns every folder currently in use, with the number of templates in it, sorted alphabetically. Templates with no folder are not counted here.
Response
{
"data": [
{ "folder": "Onboarding", "count": 4 },
{ "folder": "Receipts", "count": 2 }
]
}
List starter templates
GET /v1/templates/starters
Requires read_only scope. Returns the built-in starter templates, including their default subject, HTML, and available variables.
Response
{
"data": [
{
"id": "welcome",
"name": "Welcome Email",
"description": "Clean welcome email with logo, greeting, and CTA.",
"subject": "Welcome to {{company_name}}, {{name}}!",
"html": "<h1>Hi {{name}}</h1><p>Thanks for joining {{company_name}}.</p>",
"variables": ["name", "company_name"],
"engine": "react"
}
]
}
engine is always react for starter templates.
List visual starters
GET /v1/templates/visual-starters
Requires read_only scope. Returns the available visual-editor starter templates.
Response
{
"data": [
{
"id": "vs-welcome",
"name": "Welcome Email",
"description": "Clean welcome email with logo, greeting, and CTA.",
"category": "transactional"
}
]
}
category is one of transactional, marketing, or notification.
Get visual starter
GET /v1/templates/visual-starters/:starterId
Requires read_only scope. Returns one visual starter including its full block design.
Response
{
"id": "vs-welcome",
"name": "Welcome Email",
"description": "Clean welcome email with logo, greeting, and CTA.",
"category": "transactional",
"design": {
"blocks": []
}
}
Update template
PATCH /v1/templates/:id
Requires full_access scope.
Request body
All fields are optional — only include fields you want to change.
{
"subject": "Updated subject: welcome {{name}}!",
"html": "<h1>Updated body for {{name}}</h1>"
}
Moving a template between folders
Send only folder. Pass a name to file the template, or null to take it out of every folder.
{ "folder": "Receipts" }
{ "folder": null }
PUT /v1/templates/:id is the full-replace variant of PATCH /v1/templates/:id. It accepts the same body and behaves identically; every field stays optional and omitted fields are left unchanged, so the two verbs are interchangeable.
Delete template
DELETE /v1/templates/:id
Requires full_access scope.
Duplicate template
POST /v1/templates/:id/duplicate
Requires full_access scope. Creates a copy of the template and returns it.
Response
Returns the new template object, same shape as Create template.
List template versions
GET /v1/templates/:id/versions
Requires read_only scope. Returns the version history for a template, newest first.
Response
{
"data": [
{
"id": "tv_abc123",
"version": 3,
"subject": "Welcome to {{company_name}}, {{name}}!",
"created_at": "2025-03-12T09:00:00Z"
}
]
}
Publish template version
POST /v1/templates/:id/publish/:versionId
Requires full_access scope. Publishes a specific version, making it the template's current content.
Response
Returns the updated template object, same shape as Get template.
Render template (preview)
POST /v1/templates/:id/render
Requires read_only scope. Returns the rendered HTML and subject with variables substituted — useful for previewing before sending.
Request body
{
"variables": {
"name": "Alice",
"company_name": "Acme Corp"
}
}
Response
{
"subject": "Welcome to Acme Corp, Alice!",
"html": "<h1>Hi Alice</h1><p>Thanks for joining Acme Corp.</p>",
"text": "Hi Alice, thanks for joining Acme Corp."
}
Render ad-hoc template HTML
POST /v1/templates/render
Requires sending_access scope. Renders inline HTML passed in the request with variable substitution, without saving a template. Use this for previews of unsaved content; to render a saved template by ID, use Render template (preview).
Request body
| Field | Type | Required | Description |
|---|---|---|---|
html | string | Yes | Raw HTML to render with variable substitution. |
engine | string | No | Template engine: html, react, or visual. |
variables | object | No | Variable name/value pairs to substitute. Values are strings. |
Example request
{
"html": "<h1>Hello {{name}}</h1>",
"variables": {
"name": "Alice"
}
}
Response
{
"html": "<h1>Hello Alice</h1>",
"text": "Hello Alice"
}
Compile visual blocks
POST /v1/templates/compile-blocks
Requires sending_access scope. Compiles a visual block design to email-safe HTML. Used for live preview in the visual editor.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
design | object | Yes | Visual block design JSON to compile. |
variables | object | No | Variable values substituted during compilation. |
Example request
{
"design": {
"blocks": []
},
"variables": {
"company_name": "Acme Corp"
}
}
Response
{
"html": "<html><body></body></html>",
"text": ""
}
Using templates when sending
Pass template_id and variables when calling the send endpoint:
await sendry.emails.send({
from: "hello@acme.com",
to: "user@example.com",
template_id: "tmpl_abc123",
variables: {
name: "Alice",
company_name: "Acme Corp",
},
});
template_id overrides the html and text fields.
Plan limits
| Plan | Templates |
|---|---|
| Free | 5 |
| Pro | 25 |
| Business | Unlimited |
| Enterprise | Unlimited |