Templates API

API reference for creating, managing, and previewing email templates.

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

FieldTypeRequiredDescription
namestringYesDisplay name (max 191 characters)
slugstringYesUnique identifier (lowercase alphanumeric + hyphens, 2-100 chars)
descriptionstringNoOptional description
subjectstringNoSubject line template (supports {{variables}})
html_bodystringNoHTML body template
text_bodystringNoPlain 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

StatusErrorDescription
400Invalid slugSlug doesn't match ^[a-z0-9][a-z0-9-]*[a-z0-9]$
400Invalid template syntaxSubject, html_body, or text_body has a Handlebars parse error
403Template limit reachedPlan limit exceeded
409Slug already existsAnother template in this org uses the same slug

List Templates

GET /v1/templates

Query Parameters

ParameterTypeDescription
limitintegerNumber of results (default: 50, max: 100)
offsetintegerNumber 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

FieldTypeDescription
namestringDisplay name
slugstringUnique slug (validated for format and uniqueness)
descriptionstringDescription
subjectstringSubject line template
html_bodystringHTML body template
text_bodystringPlain text body template
activebooleanWhether 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

FieldTypeRequiredDescription
variablesobjectNoKey-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

FieldTypeDescription
idstring (UUID)Unique template identifier
org_idstring (UUID)Organization that owns this template
namestringDisplay name
slugstringUnique slug used to reference the template when sending
descriptionstring | nullOptional description
subjectstringSubject line template with {{variable}} placeholders
html_bodystringHTML body template
text_bodystringPlain text body template
activebooleanWhether the template can be used for sending
created_atstring (ISO 8601)Creation timestamp
updated_atstring (ISO 8601)Last update timestamp

Built-in Variables

These variables are automatically injected when rendering templates (they won't overwrite user-provided values):

VariableDescription
{{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" if first_name is empty or missing

Plan Limits

PlanMax Templates
Free5
Starter25
Plus50
Pro100
Scale500