Sign inGet Started

Create an email template

POST
/v1/email/templates
const template = await bird.email.templates.create({
  slug: "welcome-email",
  category: "transactional",
  source: "html",
  languages: {
    en: {
      subject: "Welcome, {{ first_name }}",
      preview_text: "Your account is ready",
      html: "<!DOCTYPE html>\n<html>\n<body>\n<p>Hi {{ first_name }}, thanks for signing up.</p>\n</body>\n</html>\n",
    },
  },
});
console.log(template.id, template.draft_version_id);
Response201
{
  "id": "emt_01krdgeqcxet5s7t44vh8rt9mg",
  "workspace_id": "ws_01krdgeqcxet5s7t44vh8rt9mg",
  "slug": "welcome-email",
  "name": "Welcome email",
  "scope": "workspace",
  "status": "active",
  "category": "transactional",
  "source": "html",
  "theme": null,
  "draft_version_id": "emv_01krdgeqcxet5s7t44vh8rt9mg",
  "live_version_id": "emv_01krdgeqcxet5s7t44vh8rt9mg",
  "published_version_id": "emv_01krdgeqcxet5s7t44vh8rt9mg",
  "languages": {
    "en": {
      "status": "live"
    },
    "pt-BR": {
      "status": "live",
      "draft": true
    },
    "de": {
      "status": "draft"
    }
  },
  "default_language": "pt-BR",
  "live_default_language": "en",
  "available_languages": [
    "en"
  ],
  "on_missing_language": "fallback"
}

Creates a template and its first editable draft.

Send the template's slug (the name you send the template by), an optional display name that defaults to the slug, a category, the authoring format (source), and the draft's content in one or more languages. Leave the content out to start from an empty draft. A slug already used in the workspace returns a conflict.

Request Payload

slug
string
required

The template's workspace-unique handle, and a stable alternative to the template ID when sending by template. It can contain lowercase letters, numbers, hyphens, and underscores. It is fixed at creation, so pick it deliberately. Two prefixes are rejected: bird_, reserved for our built-in templates, and emt_, the template ID format, which a slug could never be distinguished from.

name
string

The template's display name, shown wherever the template is listed. You can change it any time. It defaults to the slug if you do not set one.

description
string

What the template is for, in your own words.

category
string
required

Whether the template is for transactional email or marketing email.

Possible values: transactional, marketing

source
string
required

The authoring format the template is written in, fixed at creation. html is finished markup you provide, optionally personalized with Liquid. Liquid supports variables, filters, and control flow such as {% if %} conditionals and {% for %} loops. A few constructs are rejected when you submit, and the error names exactly what to change:

  • Partial includes ({% include %}, {% render %}).
  • The increment, decrement, and ifchanged tags.
  • The money, format_date, format_time, json, inspect, and type filters.
  • Comparing against empty/blank (use .size == 0 instead).
  • Blocks nested far deeper than real email markup needs.

Broadcast template parameters accept scalar values; arrays and objects are unsupported. Broadcasts support {% for %} over a local collection created with split. Broadcasts without caller-supplied parameters also support literal ranges and {% tablerow %}. When a broadcast uses caller parameters, ranges and {% tablerow %} are unsupported.

Possible values: html

languages
object

The initial draft's content, keyed by language tag in BCP-47 form such as en or pt-BR. A template holds up to 25 languages, and a send picks one of them.

Omit this to create an empty draft and add content later.

The example's {{ first_name }} is a parameter, filled from template.parameters at send time. A {{ bird.contact.<attribute> }} placeholder reads a contact attribute. Broadcasts resolve each audience contact during preparation; previews use the selected contact or sample values. Missing attributes use their configured fallbacks, while explicit empty strings remain empty.

Public API sends, including batch and scheduled sends, look up each To address in this workspace when the selected language reads contact attributes. A missing contact returns 422 (E04099), even if Liquid supplies a default. Each To receives its own personalized email, with Cc and Bcc copied on each. Resolved values are saved at acceptance for delivery and later content reads.

default_language
string

The language a send uses when it does not name one, and the last resort when a requested language is not available. It has to be one of the languages you supply. If you leave it out, we default to en, unless you supply exactly one language, in which case we use that one instead. So if you supply two or more languages and en is not among them, you have to set this yourself.

on_missing_language
string

What a send does when it asks for a language this template does not carry. Defaults to fallback on email.

Possible values: fallback, fail

language_source_required
boolean

Whether a send has to name a language. Set it to true to reject a send that names none instead of serving the default language. Pair it with on_missing_language: fail when every send must pick a language deliberately: on its own, fail is bypassed by naming no language at all. A broadcast must select a template language when this is set. Defaults to false.

Response Payload

editor_url
string

Optional link to open the saved draft in the browser editor, in its default language. Absent for built-in templates.

id
string
required

Template ID.

workspace_id
nullable string
required

The workspace that owns the template. Null for a built-in system template, which no workspace owns.

slug
string
required

The name you send the template by. You can use either the slug or the id when you send. It never changes after the template is created. A built-in system template's slug always starts with bird_.

name
string
required

The template's display name, shown wherever the template is listed. You can change it any time. It defaults to the slug if you do not set one.

description
nullable string
required

What the template is for, in your own words. Null if you have not set one.

scope
string
required

Whether the template is one of our built-in templates (system) or one your workspace created (workspace).

Possible values: system, workspace

status
string
required

Where the template stands as a whole. The same five states on every channel.

  • draft: nothing has ever gone live.
  • pending: nothing is live and at least one language is in review.
  • active: at least one language is live, so something can be sent.
  • rejected: it was reviewed and every language was refused.
  • inactive: nothing is live and nothing is in review, so content was withdrawn or was blocked before anything went live.

A template with one language live is active even while another is still drafted or refused. Read languages for the state of each language and its reason.

Which values a channel reports follows its review model. A channel whose content a third party reviews uses all five. On email and SMS, where content goes live on publish, a template is draft, active or inactive, and pending and rejected are reserved for the review stage coming to both, so a template reaching either is not a breaking change.

Possible values: draft, pending, active, rejected, inactive

category
string
required

Whether the template is for transactional email or marketing email.

Possible values: transactional, marketing

source
string
required

The authoring format the template is written in, fixed at creation. html is finished markup you provide, optionally personalized with Liquid.

Possible values (may grow over time): html

theme
nullable string
required

The visual theme a built-in template is designed in, or null for a template your workspace authored (which has no theme).

draft_version_id
nullable string
required

The current editable draft version. Null for a built-in system template, which has no draft.

live_version_id
nullable string
required

The version a send resolves to, or null if the template has never been published.

published_version_id
nullable string
required

Deprecated: use live_version_id instead, which carries the same value.

revision
nullable integer
required

The draft's revision counter. Send it back on the next update to detect concurrent edits. Null for a built-in system template, which is unversioned.

languages
object
required

Every language this template has, keyed by language tag in BCP-47 form such as en or pt-BR, each with its state. One read tells you which languages are live and which have unpublished edits, without fetching any content.

Content is not here: read a version's languages for that, one language at a time.

default_language
string
required

The language the draft defaults to. This is the language languages is keyed against while you edit, and it becomes the live version's default when you submit this draft.

Until then the live version keeps its own default, reported in live_default_language, so the two can differ. available_languages describes the live version for the same reason.

live_default_language
nullable string
required

The live version's default language. While language_source_required is false, a send that names no language gets it, and a broadcast that selects no language gets the default of the published version it sends. A single send that names a language also gets it when on_missing_language is fallback and the live version stocks no form of that language, even one only the draft has added: a request for pt-BR is served by the live version's pt-BR, then by its pt or another pt variant, and only then by this default. Null if the template has never been published. A built-in system template is always published and reports the same language as default_language.

available_languages
array of string
required

The languages this template currently supports for sending, as BCP-47 tags. Empty until the template is published, because sends serve published content. The set may shrink for reasons other than editing, so read it rather than assuming it matches what was published. A built-in system template has no publish step and always reports its one language.

on_missing_language
string
required

What a send does when it asks for a language this template does not carry. Defaults to fallback on email.

Possible values: fallback, fail

language_source_required
boolean
required

Whether a send has to name a language. When true, a send that names none is rejected instead of being served the default language. A broadcast must select a template language when this is set.

last_submitted_at
nullable string
required

When this template was last submitted. Null if it never has been. Submitting is the only thing that moves this timestamp: rolling back changes which version is live without counting as a submit, so this keeps reporting the last real submit. Read it alongside languages, which says where each language stands.

created_at
nullable string
required

When the template was created. Null for a built-in system template.

updated_at
nullable string
required

When the template was last modified. Null for a built-in system template.