Templates
Create and render Handlebars email templates
Postkit templates let you create reusable email layouts with Handlebars variables. Define your template once, then send personalized emails by providing variable data at send time.
You need an API key to use the templates API. See the quickstart to get your key.
Create a template
Define your template
Create a template with POST /v1/templates. Provide a name, subject line, HTML body, and optionally a plain text version. Use Handlebars double-brace syntax for dynamic content.
curl -X POST https://api.postkit.eu/v1/templates \
-H "Authorization: Bearer pk_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"name": "Order Confirmation",
"subject": "Your order {{order_id}} has been confirmed",
"html": "<h1>Hi {{customer_name}},</h1><p>Your order {{order_id}} is confirmed. Total: {{total}}</p>",
"text": "Hi {{customer_name}}, Your order {{order_id}} is confirmed. Total: {{total}}",
"engine": "handlebars",
"variables": [
{ "name": "order_id", "type": "string", "required": true },
{ "name": "customer_name", "type": "string", "required": true },
{ "name": "total", "type": "string", "required": true }
]
}'const response = await fetch('https://api.postkit.eu/v1/templates', {
method: 'POST',
headers: {
'Authorization': 'Bearer pk_live_abc123...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Order Confirmation',
subject: 'Your order {{order_id}} has been confirmed',
html: '<h1>Hi {{customer_name}},</h1><p>Your order {{order_id}} is confirmed. Total: {{total}}</p>',
text: 'Hi {{customer_name}}, Your order {{order_id}} is confirmed. Total: {{total}}',
engine: 'handlebars',
variables: [
{ name: 'order_id', type: 'string', required: true },
{ name: 'customer_name', type: 'string', required: true },
{ name: 'total', type: 'string', required: true },
],
}),
});
const template = await response.json();
console.log(template.id); // tmpl_abc123import requests
response = requests.post(
'https://api.postkit.eu/v1/templates',
headers={'Authorization': 'Bearer pk_live_abc123...'},
json={
'name': 'Order Confirmation',
'subject': 'Your order {{order_id}} has been confirmed',
'html': '<h1>Hi {{customer_name}},</h1><p>Your order {{order_id}} is confirmed. Total: {{total}}</p>',
'text': 'Hi {{customer_name}}, Your order {{order_id}} is confirmed. Total: {{total}}',
'engine': 'handlebars',
'variables': [
{'name': 'order_id', 'type': 'string', 'required': True},
{'name': 'customer_name', 'type': 'string', 'required': True},
{'name': 'total', 'type': 'string', 'required': True},
],
},
)
template = response.json()
print(template['id']) # tmpl_abc123The response includes the template ID and its initial status:
{
"id": "tmpl_abc123",
"name": "Order Confirmation",
"status": "draft",
"version": 1,
"engine": "handlebars"
}Iterate on your draft
Templates start in draft status. You can update a draft as many times as you need before publishing. Each update increments the version number. The published version (if any) remains active while you iterate.
Template variables
Handlebars provides a straightforward syntax for dynamic content:
{{variable_name}} -- insert a variable value
{{#if condition}}...{{/if}} -- conditional block
{{#each items}}...{{/each}} -- iterate over arraysThe variables array in the create request defines expected variables with metadata for validation and documentation:
{
"variables": [
{ "name": "customer_name", "type": "string", "required": true },
{ "name": "order_id", "type": "string", "required": true },
{ "name": "total", "type": "number", "required": true },
{ "name": "is_premium", "type": "boolean", "required": false, "default_value": "false" },
{ "name": "order_date", "type": "date", "required": false },
{ "name": "tracking_url", "type": "url", "required": false }
]
}Supported variable types:
| Type | Description | Example |
|---|---|---|
string | Text value | "Max Mustermann" |
number | Numeric value | 49.99 |
boolean | True or false | true |
date | ISO 8601 date | "2026-04-01" |
url | URL string | "https://example.com/track" |
Preview a template
Render a template with sample data to preview the output without sending an email. Use POST /v1/templates/render with the template ID and variable data.
curl -X POST https://api.postkit.eu/v1/templates/render \
-H "Authorization: Bearer pk_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"template_id": "tmpl_abc123",
"template_data": {
"customer_name": "Max Mustermann",
"order_id": "ORD-4821",
"total": "49.99 EUR"
}
}'const response = await fetch('https://api.postkit.eu/v1/templates/render', {
method: 'POST',
headers: {
'Authorization': 'Bearer pk_live_abc123...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
template_id: 'tmpl_abc123',
template_data: {
customer_name: 'Max Mustermann',
order_id: 'ORD-4821',
total: '49.99 EUR',
},
}),
});
const rendered = await response.json();
console.log(rendered.html);response = requests.post(
'https://api.postkit.eu/v1/templates/render',
headers={'Authorization': 'Bearer pk_live_abc123...'},
json={
'template_id': 'tmpl_abc123',
'template_data': {
'customer_name': 'Max Mustermann',
'order_id': 'ORD-4821',
'total': '49.99 EUR',
},
},
)
rendered = response.json()
print(rendered['html'])The response includes the fully rendered HTML and text:
{
"html": "<h1>Hi Max Mustermann,</h1><p>Your order ORD-4821 is confirmed. Total: 49.99 EUR</p>",
"text": "Hi Max Mustermann, Your order ORD-4821 is confirmed. Total: 49.99 EUR"
}Publish a template
Once you are satisfied with your draft, publish it with POST /v1/templates/:id/publish. Only published templates can be used when sending emails.
curl -X POST https://api.postkit.eu/v1/templates/tmpl_abc123/publish \
-H "Authorization: Bearer pk_live_abc123..."const response = await fetch(
'https://api.postkit.eu/v1/templates/tmpl_abc123/publish',
{
method: 'POST',
headers: {
'Authorization': 'Bearer pk_live_abc123...',
},
}
);
const template = await response.json();
// template.status === "published"response = requests.post(
'https://api.postkit.eu/v1/templates/tmpl_abc123/publish',
headers={'Authorization': 'Bearer pk_live_abc123...'},
)
template = response.json()
# template['status'] == 'published'The template's status changes to published and its published_version is set to the current version number.
Publishing is a one-way action for that version. To make changes, update the template (which creates a new draft version) and publish again, or duplicate the template.
Send with a template
Once a template is published, use it when sending emails by passing template_id and template_data instead of html and text. The template's subject line is used by default, but you can override it.
curl -X POST https://api.postkit.eu/v1/emails \
-H "Authorization: Bearer pk_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"from": "Acme <noreply@acme.eu>",
"to": ["user@example.com"],
"template_id": "tmpl_abc123",
"template_data": {
"customer_name": "Max Mustermann",
"order_id": "ORD-4821",
"total": "49.99 EUR"
}
}'const response = await fetch('https://api.postkit.eu/v1/emails', {
method: 'POST',
headers: {
'Authorization': 'Bearer pk_live_abc123...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'Acme <noreply@acme.eu>',
to: ['user@example.com'],
template_id: 'tmpl_abc123',
template_data: {
customer_name: 'Max Mustermann',
order_id: 'ORD-4821',
total: '49.99 EUR',
},
}),
});
const email = await response.json();response = requests.post(
'https://api.postkit.eu/v1/emails',
headers={'Authorization': 'Bearer pk_live_abc123...'},
json={
'from': 'Acme <noreply@acme.eu>',
'to': ['user@example.com'],
'template_id': 'tmpl_abc123',
'template_data': {
'customer_name': 'Max Mustermann',
'order_id': 'ORD-4821',
'total': '49.99 EUR',
},
},
)See the Sending Email guide for all sending options including batch sending, scheduling, and attachments.
Duplicate a template
Create a copy of an existing template with POST /v1/templates/:id/duplicate. The copy starts in draft status so you can iterate on it independently. You can optionally provide a custom name for the duplicate.
curl -X POST https://api.postkit.eu/v1/templates/tmpl_abc123/duplicate \
-H "Authorization: Bearer pk_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"name": "Order Confirmation v2"
}'const response = await fetch(
'https://api.postkit.eu/v1/templates/tmpl_abc123/duplicate',
{
method: 'POST',
headers: {
'Authorization': 'Bearer pk_live_abc123...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Order Confirmation v2',
}),
}
);
const copy = await response.json();
// copy.status === "draft"response = requests.post(
'https://api.postkit.eu/v1/templates/tmpl_abc123/duplicate',
headers={'Authorization': 'Bearer pk_live_abc123...'},
json={'name': 'Order Confirmation v2'},
)
copy = response.json()
# copy['status'] == 'draft'If you omit the name field, the duplicate is named "Copy of Original Name".
List and manage templates
List templates
Retrieve all templates with cursor-based pagination:
curl https://api.postkit.eu/v1/templates?limit=10 \
-H "Authorization: Bearer pk_live_abc123..."const response = await fetch('https://api.postkit.eu/v1/templates?limit=10', {
headers: {
'Authorization': 'Bearer pk_live_abc123...',
},
});
const { data } = await response.json();response = requests.get(
'https://api.postkit.eu/v1/templates',
headers={'Authorization': 'Bearer pk_live_abc123...'},
params={'limit': 10},
)
templates = response.json()['data']Delete a template
Remove a template permanently with DELETE /v1/templates/:id:
curl -X DELETE https://api.postkit.eu/v1/templates/tmpl_abc123 \
-H "Authorization: Bearer pk_live_abc123..."const response = await fetch(
'https://api.postkit.eu/v1/templates/tmpl_abc123',
{
method: 'DELETE',
headers: {
'Authorization': 'Bearer pk_live_abc123...',
},
}
);
const result = await response.json();
// result.deleted === trueresponse = requests.delete(
'https://api.postkit.eu/v1/templates/tmpl_abc123',
headers={'Authorization': 'Bearer pk_live_abc123...'},
)
result = response.json()
# result['deleted'] == TrueDeleting a template is permanent. Any emails that reference the deleted template's ID will fail to send.
What's next?
- Sending Email -- all sending features including batch, scheduling, and attachments
- API Reference -- full template endpoint documentation