Templates API
Templates API
Create reusable email templates with Handlebars variable substitution. Templates can be referenced by slug or UUID when sending emails.
Create Template
POST /v1/templates
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name (max 191 characters) |
slug | string | Yes | Unique identifier (lowercase alphanumeric + hyphens, 2-100 chars) |
description | string | No | Optional description |
subject | string | No | Subject line template (supports {{variables}}) |
html_body | string | No | HTML body template |
text_body | string | No | Plain text body template |
Example Request
curl -X POST https://api.postscale.io/v1/templates \
-H "Authorization: Bearer ps_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Welcome Email",
"slug": "welcome",
"description": "Sent to new users after signup",
"subject": "Welcome to {{company_name}}, {{first_name}}!",
"html_body": "<h1>Hello {{first_name}}</h1><p>Welcome to {{company_name}}!</p>",
"text_body": "Hello {{first_name}}, Welcome to {{company_name}}!"
}'
Response 201
{
"template": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"org_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"name": "Welcome Email",
"slug": "welcome",
"description": "Sent to new users after signup",
"subject": "Welcome to {{company_name}}, {{first_name}}!",
"html_body": "<h1>Hello {{first_name}}</h1><p>Welcome to {{company_name}}!</p>",
"text_body": "Hello {{first_name}}, Welcome to {{company_name}}!",
"active": true,
"created_at": "2026-03-16T12:00:00Z",
"updated_at": "2026-03-16T12:00:00Z"
}
}
Errors
| Status | Error | Description |
|---|---|---|
400 | Invalid slug | Slug doesn't match ^[a-z0-9][a-z0-9-]*[a-z0-9]$ |
400 | Invalid template syntax | Subject, html_body, or text_body has a Handlebars parse error |
403 | Template limit reached | Plan limit exceeded |
409 | Slug already exists | Another template in this org uses the same slug |
List Templates
GET /v1/templates
Query Parameters
| Parameter | Type | Description |
|---|---|---|
limit | integer | Number of results (default: 50, max: 100) |
offset | integer | Number of results to skip |
Example Request
curl https://api.postscale.io/v1/templates?limit=50 \
-H "Authorization: Bearer ps_live_your_api_key"
Response 200
{
"templates": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"org_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"name": "Welcome Email",
"slug": "welcome",
"description": "Sent to new users after signup",
"subject": "Welcome to {{company_name}}, {{first_name}}!",
"html_body": "...",
"text_body": "...",
"active": true,
"created_at": "2026-03-16T12:00:00Z",
"updated_at": "2026-03-16T12:00:00Z"
}
],
"total": 1
}
Get Template
GET /v1/templates/:id
The :id parameter accepts either a UUID or a slug.
Example Requests
# By UUID
curl https://api.postscale.io/v1/templates/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Authorization: Bearer ps_live_your_api_key"
# By slug
curl https://api.postscale.io/v1/templates/welcome \
-H "Authorization: Bearer ps_live_your_api_key"
Response 200
{
"template": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"org_id": "f1e2d3c4-b5a6-7890-abcd-ef1234567890",
"name": "Welcome Email",
"slug": "welcome",
"subject": "Welcome to {{company_name}}, {{first_name}}!",
"html_body": "<h1>Hello {{first_name}}</h1><p>Welcome to {{company_name}}!</p>",
"text_body": "Hello {{first_name}}, Welcome to {{company_name}}!",
"active": true,
"created_at": "2026-03-16T12:00:00Z",
"updated_at": "2026-03-16T12:00:00Z"
}
}
Update Template
PUT /v1/templates/:id
Partially update a template. Only provided fields are changed. The :id parameter accepts either a UUID or a slug.
Request Body
| Field | Type | Description |
|---|---|---|
name | string | Display name |
slug | string | Unique slug (validated for format and uniqueness) |
description | string | Description |
subject | string | Subject line template |
html_body | string | HTML body template |
text_body | string | Plain text body template |
active | boolean | Whether the template can be used for sending |
All fields are optional. Template syntax is validated on update.
Example Request
curl -X PUT https://api.postscale.io/v1/templates/welcome \
-H "Authorization: Bearer ps_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"subject": "Welcome aboard, {{first_name}}!",
"active": true
}'
Response 200
Returns the full updated template object.
Delete Template
DELETE /v1/templates/:id
Permanently delete a template. The :id parameter accepts either a UUID or a slug.
Example Request
curl -X DELETE https://api.postscale.io/v1/templates/welcome \
-H "Authorization: Bearer ps_live_your_api_key"
Response 200
{
"status": "deleted"
}
Preview Template
POST /v1/templates/:id/preview
Render a template with test variables and return the result. Useful for testing templates before sending. The :id parameter accepts either a UUID or a slug.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
variables | object | No | Key-value pairs to substitute into the template |
Example Request
curl -X POST https://api.postscale.io/v1/templates/welcome/preview \
-H "Authorization: Bearer ps_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"variables": {
"first_name": "Jane",
"company_name": "Acme Corp"
}
}'
Response 200
{
"subject": "Welcome to Acme Corp, Jane!",
"html": "<h1>Hello Jane</h1><p>Welcome to Acme Corp!</p>",
"text": "Hello Jane, Welcome to Acme Corp!"
}
Template Object
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Unique template identifier |
org_id | string (UUID) | Organization that owns this template |
name | string | Display name |
slug | string | Unique slug used to reference the template when sending |
description | string | null | Optional description |
subject | string | Subject line template with {{variable}} placeholders |
html_body | string | HTML body template |
text_body | string | Plain text body template |
active | boolean | Whether the template can be used for sending |
created_at | string (ISO 8601) | Creation timestamp |
updated_at | string (ISO 8601) | Last update timestamp |
Built-in Variables
These variables are automatically injected when rendering templates (they won't overwrite user-provided values):
| Variable | Description |
|---|---|
{{current_year}} | Current year (e.g., 2026) |
{{current_date}} | Current date in YYYY-MM-DD format |
{{unsubscribe_url}} | One-click unsubscribe URL (populated at send time) |
Template Syntax
Templates use Handlebars syntax:
- Variables:
{{variable_name}} - Nested access:
{{user.first_name}} - Conditionals:
{{#if paid}}...{{else}}...{{/if}} - Loops:
{{#each items}}{{this.name}}{{/each}} - Defaults:
{{default first_name "there"}}— returns"there"iffirst_nameis empty or missing
Plan Limits
| Plan | Max Templates |
|---|---|
| Free | 5 |
| Starter | 25 |
| Plus | 50 |
| Pro | 100 |
| Scale | 500 |