Skip to content

Templates

Create reusable email templates with variable substitution. Templates support HTML content with {{variable}} placeholders that are replaced at send time.

POST
/templates

Create a new email template. Templates start unpublished (published: false) and must be published before emails can be sent with them.

json
{
  "name": "Welcome Email",
  "subject": "Welcome to {{company_name}}, {{first_name}}!",
  "html": "<h1>Hello {{first_name}},</h1><p>Welcome to {{company_name}}.</p>",
  "text": "Hello {{first_name}}, Welcome to {{company_name}}.",
  "variables": ["first_name", "company_name"]
}
GET
/templates

List templates with pagination. Filter with search (name) and published=true|false.

json
{
  "data": [
    {
      "id": 77,
      "publicId": "tpl_abc123def456ghi789",
      "name": "Welcome Email",
      "subject": "Welcome to {{company_name}}, {{first_name}}!",
      "published": true,
      "version": 3,
      "createdAt": "2026-03-23T10:00:00.000Z",
      "updatedAt": "2026-03-23T12:00:00.000Z"
    }
  ],
  "meta": {
    "page": 1,
    "perPage": 20,
    "total": 12,
    "totalPages": 1
  }
}
GET
/templates/:id

Retrieve a single template including its full HTML content and variable list.

json
{
  "template": {
    "id": 77,
    "publicId": "tpl_abc123def456ghi789",
    "name": "Welcome Email",
    "subject": "Welcome to {{company_name}}, {{first_name}}!",
    "htmlBody": "<h1>Hello {{first_name}},</h1><p>Welcome to {{company_name}}.</p>",
    "textBody": "Hello {{first_name}}, Welcome to {{company_name}}.",
    "variables": ["first_name", "company_name"],
    "published": true,
    "version": 3,
    "createdAt": "2026-03-23T10:00:00.000Z",
    "updatedAt": "2026-03-23T12:00:00.000Z"
  }
}
PATCH
/templates/:id

Update a template. Changing the subject or content bumps its version number. A published template stays published, so the change applies to the next send that uses it.

json
{
  "name": "Welcome Email v2",
  "html": "<h1>Hi {{first_name}}!</h1><p>Thanks for joining {{company_name}}.</p>"
}
DELETE
/templates/:id

Delete a template. This does not affect emails already sent using this template.

json
{
  "success": true
}
POST
/templates/:id/publish

Publish a template, making it available for sending. Sending with an unpublished template returns 422.

json
{
  "template": {
    "id": 77,
    "publicId": "tpl_abc123def456ghi789",
    "published": true,
    "updatedAt": "2026-03-23T15:00:00.000Z"
  }
}
POST
/templates/:id/unpublish

Unpublish a template. Sends that reference it fail with 422 until it is published again.

json
{
  "template": {
    "id": 77,
    "publicId": "tpl_abc123def456ghi789",
    "published": false
  }
}
POST
/templates/:id/duplicate

Create a copy of an existing template. The copy is unpublished, starts at version 1, and is named 'Copy of <name>'.

json
HTTP/1.1 201 Created

{
  "template": {
    "id": 78,
    "publicId": "tpl_new456copy789ghi01",
    "name": "Copy of Welcome Email",
    "published": false,
    "version": 1,
    "createdAt": "2026-03-23T16:00:00.000Z"
  }
}
GET
/templates/presets

List the built-in template presets (welcome, password-reset, invoice, newsletter, announcement, event-invitation, order-confirmation, feedback-request, …). Use one as a starting point.

json
{
  "data": [
    {
      "id": "welcome",
      "name": "Welcome Email",
      "subject": "Welcome to {{company_name}}",
      "html": "<!DOCTYPE html>...",
      "variables": ["company_name", "first_name", "dashboard_url", "unsubscribe_url"]
    },
    {
      "id": "password-reset",
      "name": "Password Reset",
      "subject": "Reset your password",
      "html": "<!DOCTYPE html>...",
      "variables": ["company_name", "first_name", "reset_url", "expiry_hours", "unsubscribe_url"]
    }
  ]
}
POST
/templates/presets/:presetId/use

Create a new, unpublished template from a preset, addressed by the preset's string id.

json
HTTP/1.1 201 Created

{
  "template": {
    "id": 79,
    "publicId": "tpl_fromPreset12345678",
    "name": "Welcome Email",
    "subject": "Welcome to {{company_name}}",
    "published": false,
    "version": 1,
    "createdAt": "2026-03-23T16:00:00.000Z"
  }
}

Using Templates with Emails

Reference a published template by ID when sending an email. Provide the variable values to populate the placeholders:

typescript
await poststack.emails.send({
  from: 'you@yourdomain.com',
  to: ['alice@example.com'],
  template_id: 'tpl_abc123def456ghi789',
  variables: {
    first_name: 'Alice',
    company_name: 'Acme Inc',
  },
});
json
{
  "from": "you@yourdomain.com",
  "to": ["alice@example.com"],
  "template_id": "tpl_abc123def456ghi789",
  "variables": {
    "first_name": "Alice",
    "company_name": "Acme Inc"
  }
}

Unsubscribe Placeholders

A variable with no matching value renders as an empty string. Two placeholders are exempt, because they are not yours to supply: {{unsubscribe_url}} and {{preferences_url}} are filled in per recipient when the message is sent, so leave them out of variables and they resolve to a real signed link. Pass an explicit value only if you host your own opt-out page. Including either one also switches on the RFC 8058 one-click unsubscribe headers for that send.

Conditional Blocks

Templates support flat conditional blocks for inclusion or exclusion of content based on whether a variable is truthy. Useful for optional greetings, footer lines that only appear for certain audiences, or fallback copy when a value is missing:

html
{{#if first_name}}Hi {{first_name}},{{/if}}
{{#unless first_name}}Hi there,{{/unless}}

Welcome to {{company_name}}.

{{#if support_url}}Need help? Visit {{support_url}}.{{/if}}

A variable counts as truthy when present and not the empty string, "0", or "false". Conditionals do not nest in this pass — flat use covers the common cases (greet when known, fall back when unknown, hide CTAs that lack a URL). Conditionals are evaluated before variable substitution, so the inner content can reference the same variables.

Visual Template Builder

The dashboard includes a block-based visual editor as an alternative to hand-written HTML. When you choose Visual Builder at create time, you compose the email from typed blocks — heading, paragraph, button, image, divider — and PostStack renders them to the final HTML on save. The underlying builder_blocks JSON is stored alongside the compiled HTML so the editor can round-trip without lossy re-parsing. Variable placeholders ({{first_name}}) pass through any block's text content untouched and are substituted at send time.

You can switch a template between builder_type "html" and "visual", but going visual → html is one-way: editing the HTML directly discards the block tree. Editing the subject or content bumps the version; renaming does not.

React Email Components

PostStack does not run a React renderer server-side. If you author your emails as React components (for example via react-email), render to HTML at build time or in your application before sending. The TS SDK accepts plain HTML on the html field — bring your own renderer:

typescript
import { render } from '@react-email/render';
import { WelcomeEmail } from './emails/welcome';

const html = render(<WelcomeEmail name="Alice" />);

await poststack.emails.send({
  from: 'you@yourdomain.com',
  to: ['alice@example.com'],
  subject: 'Welcome',
  html,
});

This pattern keeps PostStack's surface small and the SDK runtime fast — the React renderer stays in your application's deployment, not in the email pipeline.

Related

Email templates overviewHandlebars and React Email templates, versioning and previews.