# Get an email template preview

`POST /v1/email/templates/{template_ref}/preview`

Renders a template with the sample values you supply and returns the resulting subject, HTML, and plain-text bodies: the personalized email as it will look once sent. By default it renders the current draft, so you can check your changes before you submit it. Pass `version` to preview a specific published version instead; built-in `system` templates have no versions, so `version` on a `bird_` template returns a validation error. Pass `contact` to see the email the way one of your contacts would receive it. Works for your workspace templates and built-in `system` templates. Sample `parameters` are capped at 16 KB once serialized, and personalization that is not valid or not supported returns a validation error naming what to fix.
The response also reports what the HTML uses that mail clients remove, ignore, or render inconsistently. Each finding in `compatibility` names the pattern, the line and column it sits on, and what to use instead, and `compatibility_severity` reduces them to one word for the whole body. It is advisory: the preview renders either way.

## Code samples

### CLI

```sh
bird email templates preview <template-ref> --body-file - <<'JSON'
{
  "content": {
    "subject": "Welcome to Acme, {{ bird.contact.first_name }}!",
    "preview_text": "{{ bird.contact.first_name }}, your order is on its way",
    "html": "<h1>Hi {{ bird.contact.first_name }}</h1>"
  },
  "parameters": {
    "bird": {
      "contact": {
        "first_name": "Ada"
      }
    },
    "animal": "otter"
  },
  "contact": "con_01krdgeqcxet5s7t44vh8rt9mg",
  "language": "pt-BR",
  "version": "emv_01krdgeqcxet5s7t44vh8rt9mg"
}
JSON
```

### cURL

```sh
curl -X POST "https://us1.platform.bird.com/v1/email/templates/{template_ref}/preview" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "content": {
      "subject": "Welcome to Acme, {{ bird.contact.first_name }}!",
      "preview_text": "{{ bird.contact.first_name }}, your order is on its way",
      "html": "<h1>Hi {{ bird.contact.first_name }}</h1>"
    },
    "parameters": {
      "bird": {
        "contact": {
          "first_name": "Ada"
        }
      },
      "animal": "otter"
    },
    "contact": "con_01krdgeqcxet5s7t44vh8rt9mg",
    "language": "pt-BR",
    "version": "emv_01krdgeqcxet5s7t44vh8rt9mg"
  }'
```

## Example response `200`

```json
{
  "language": "pt-BR",
  "variables": [
    {
      "type": "code"
    }
  ],
  "compatibility_severity": "warning",
  "compatibility": [
    {
      "rule_id": "css_display_flex_grid",
      "severity": "warning",
      "language": "pt-BR",
      "field": "html",
      "message": "`display: flex` and `grid` don't work in Outlook (Windows/Windows Mail).",
      "fix": "Use tables for layout.",
      "partial": "Partial support for `display: flex` and `grid` in Gmail (iOS/Android).",
      "line": 14,
      "column": 6,
      "match": "display: flex",
      "unsupported_clients": [
        {
          "family": "outlook",
          "platforms": [
            "windows",
            "windows_mail"
          ]
        }
      ],
      "partial_clients": [
        {
          "family": "outlook",
          "platforms": [
            "windows",
            "windows_mail"
          ]
        }
      ]
    }
  ]
}
```

## Path parameters

- `template_ref` (string): The template to preview: a workspace template's id (`emt_…`) or slug, or a built-in `system` template's `bird_` slug.

## Request body

- `content` (object)

  Render this content rather than the template's stored draft. It is what an editor uses to show a change as it is made, since nothing has to be saved first.

  The content is treated exactly as a draft would be: personalization is filled in the same way, a plain-text body is derived from the HTML when you omit it, and content that could not be published is refused with the same error. `version` asks for a published version's own content, so the two cannot be combined.
- `content.subject` (string): The subject line to render.
- `content.preview_text` (string): The preview text to render. It is folded into the top of the HTML the same way publishing folds it, so the rendered body carries the hidden preheader a recipient's inbox would read.
- `content.html` (string): The HTML body to render.
- `content.text` (string): The plain-text body to render. Omit it and a plain-text alternative is derived from the HTML, the same way it is derived when you publish.
- `parameters` (object)

  Sample values for the variables the template uses, for this one preview only. A variable takes its value under its own name. A `bird.` value nests to match the token, so `{"bird": {"contact": {"first_name": "Ada"}}}` fills `{{ bird.contact.first_name }}`.

  A preview is more forgiving than a send: a parameter you leave out renders as empty here rather than being rejected. `parameters` is capped at 16 KB once serialized.
- `contact` (string)

  Render the template the way this contact would receive it. Every `{{ bird.contact.… }}` token takes its value from the contact's record, narrowed to the attributes the template reads and filled from each property's `fallback_value` where the contact holds no value: the same values a broadcast to this contact would send.

  Values are read as the contact stands right now, so a preview reflects an edit to their record as soon as you make it. A `bird.contact.…` value you also pass in `parameters` wins for that one attribute, so you can preview a contact with one field changed without editing them.
- `language` (string): Which of the template's languages to render. Omit it to render the default language. When the template does not have the language you ask for, its own `on_missing_language` setting decides whether a close match is rendered instead or the request is rejected. It is the same choice the send makes.
- `version` (string): Preview a specific published version by its id, instead of the current draft.

## Response body

- `subject` (nullable string, required): The rendered subject line. Null when the template has no subject.
- `html` (nullable string, required): The rendered HTML body. Null when the template has no HTML body.
- `text` (nullable string, required): The rendered plain-text body. Derived from the HTML when the template has no separate plain-text body, and null when it has neither.
- `language` (string, required): The language this preview rendered. It differs from the language you asked for when the template does not have that one and a close match was served instead.
- `variables` (array of object, required)

  The variables you can fill in with `parameters`. This list covers only the
  language named by `language`. A version read combines the variables from
  every language the version holds. Preview each language separately to see
  its own variables.

  Variables under the reserved `bird.` namespace are not listed here. We
  supply those values, but you can nest sample values under `bird` in
  `parameters` to preview them.
- `variables.key` (string, required): The key this slot is filled by. On email and SMS it is the key you set in the send's `parameters` object. On WhatsApp it is the `name` you repeat on the matching parameter inside `components`, or, for a template whose placeholders are positional, the position itself as `1`, `2` and so on.
- `variables.type` (string, required)

  The value type this slot accepts. SMS templates use the typed slots (`code`, `amount` and the rest), each of which rejects a value that does not match its `constraint`. Email and WhatsApp templates use `text`, which accepts any value. Open enum: treat an unrecognized value as a future type rather than an error.

  Possible values (may grow over time): `code`, `ttl`, `count`, `ref`, `date`, `date_time`, `amount`, `currency`, `text`
- `variables.required` (boolean, required): Whether the send must supply this variable. Omitting a required value returns `422` on email, SMS, and WhatsApp sends.
- `variables.constraint` (string, required): A plain-language description of what values this variable accepts.
- `variables.sensitive` (boolean): Whether this slot's value is kept out of durable storage. A sensitive slot's rendered value never appears in message content read back through the API: a stand-in placeholder is stored instead.
- `compatibility_severity` (string, required)

  The worst severity across every finding the response was computed from, which
  is the authoritative reading: a response that caps how many findings it lists
  still accounts here for the ones it left out. Each response's `compatibility`
  says which content it covered.

  - `problem`: at least one finding is a `problem`.
  - `warning`: every finding is a `warning`.
  - `none`: there are no findings.

  Possible values: `problem`, `warning`, `none`
- `compatibility` (array of object, required): What the previewed HTML uses that mail clients remove, ignore, or render inconsistently, in the order the patterns appear. Empty when nothing is worth reporting. Line and column count in the `content.html` you supplied, or in the template's own HTML when you supplied none, so they address the source rather than the rendered output. Previewing a published `version` is the exception: where the stored version keeps no authored copy of a language the publish step rewrote, the positions count in that rewritten body, which no response returns. A preview renders either way. At most 200 findings come back, the first 200 in source order; `compatibility_severity` is derived from every finding the HTML produced, including any beyond those 200.
- `compatibility.rule_id` (string, required)

  The rule that produced this finding.

  Possible values (may grow over time): `html_script`, `html_event_handlers`, `html_embedded_content`, `html_linked_stylesheet`, `css_at_import`, `html_form`, `html_svg`, `html_media`, `css_display_flex_grid`, `css_position_fixed_sticky`, `css_variables_no_fallback`, `css_viewport_units`, `html_button`, `css_math_functions`, `css_modern_color`, `html_web_page_markup`
- `compatibility.severity` (string, required)

  What a finding costs you.

  - `problem`: the pattern does nothing at all. The client removes the markup, never loads the stylesheet carrying it, or will not operate the control. Where a finding names clients, that is what happens in those clients.
  - `warning`: it does something, but not what you wrote.

  Neither one refuses a save, a submit, or a send.

  Possible values: `problem`, `warning`
- `compatibility.language` (nullable string, required): Which language's content this finding is in. Null when the call covered a single language.
- `compatibility.field` (string, required): Which field of that language the finding is in. Always `html`; the subject and the plain-text body are not checked.
- `compatibility.message` (string, required): What is wrong and which clients it affects, worded to show to whoever is authoring the template. It covers the rule's whole category rather than the exact text that matched, so a rule covering `<video>` and `<audio>` names both whichever one is on the line. Show `fix` and then `partial` after it.
- `compatibility.fix` (nullable string, required): What to use instead. Null when there is no drop-in alternative and the fix is a restructure.
- `compatibility.partial` (nullable string, required): Which clients support the feature only partly. Null when no client's support is partial.
- `compatibility.line` (integer, required): The 1-based line the pattern is on, in the HTML the containing response's `compatibility` says it covered.
- `compatibility.column` (integer, required): The 1-based column the pattern starts at, on that line.
- `compatibility.match` (string, required): The source text that matched, starting at `line` and `column`: the smallest span that identifies what is wrong. Never the enclosing line. For a finding on a whole element, the span runs from the opening tag through the close tag, because the client drops the element's content along with its markup. Cut at 256 characters, so an element holding a long body is quoted from its start rather than in full.
- `compatibility.unsupported_clients` (array of object, required): Every client family that does not support the feature at all, in alphabetical order by each entry's `family`. Empty on a finding whose `message`, `fix`, and `partial` name no client. `message` names at most four families; this names all of them.
- `compatibility.unsupported_clients.family` (string, required)

  Which mail client a finding applies to. A finding's `message` names at most
  Apple Mail, Gmail, Outlook, and Yahoo; its `unsupported_clients` and
  `partial_clients` name every client affected.

  - `gmail`: Gmail
  - `outlook`: Outlook
  - `yahoo`: Yahoo
  - `apple_mail`: Apple Mail
  - `aol`: AOL
  - `thunderbird`: Mozilla Thunderbird
  - `samsung_email`: Samsung Email
  - `sfr`: SFR
  - `orange`: Orange
  - `protonmail`: ProtonMail
  - `hey`: HEY
  - `mail_ru`: Mail.ru
  - `fastmail`: Fastmail
  - `laposte`: LaPoste.net
  - `gmx`: GMX
  - `web_de`: WEB.DE
  - `ionos_1and1`: 1&1
  - `wp_pl`: WP.pl

  Possible values (may grow over time): `gmail`, `outlook`, `yahoo`, `apple_mail`, `aol`, `thunderbird`, `samsung_email`, `sfr`, `orange`, `protonmail`, `hey`, `mail_ru`, `fastmail`, `laposte`, `gmx`, `web_de`, `ionos_1and1`, `wp_pl`
- `compatibility.unsupported_clients.platforms` (array of string, required): Which of the family's platforms this applies to, in alphabetical order.
- `compatibility.partial_clients` (array of object, required): Every client family that renders something other than what you wrote, in alphabetical order by each entry's `family`. Empty when no client's support is partial, which is also when `partial` is null. `partial` names at most four families; this names all of them.
- `compatibility.partial_clients.family` (string, required)

  Which mail client a finding applies to. A finding's `message` names at most
  Apple Mail, Gmail, Outlook, and Yahoo; its `unsupported_clients` and
  `partial_clients` name every client affected.

  - `gmail`: Gmail
  - `outlook`: Outlook
  - `yahoo`: Yahoo
  - `apple_mail`: Apple Mail
  - `aol`: AOL
  - `thunderbird`: Mozilla Thunderbird
  - `samsung_email`: Samsung Email
  - `sfr`: SFR
  - `orange`: Orange
  - `protonmail`: ProtonMail
  - `hey`: HEY
  - `mail_ru`: Mail.ru
  - `fastmail`: Fastmail
  - `laposte`: LaPoste.net
  - `gmx`: GMX
  - `web_de`: WEB.DE
  - `ionos_1and1`: 1&1
  - `wp_pl`: WP.pl

  Possible values (may grow over time): `gmail`, `outlook`, `yahoo`, `apple_mail`, `aol`, `thunderbird`, `samsung_email`, `sfr`, `orange`, `protonmail`, `hey`, `mail_ru`, `fastmail`, `laposte`, `gmx`, `web_de`, `ionos_1and1`, `wp_pl`
- `compatibility.partial_clients.platforms` (array of string, required): Which of the family's platforms this applies to, in alphabetical order.

## Related resources

- [How to build an email template](/learn/email/how-to-build-an-email-template) (video)
- [Email templates](/products/email/templates) (product)
- [Build your first integration](/learn/paths/integration) (course)
- [Email templates](/docs/guides/email/templates) (docs)

[Get an implementation brief](/learn/workspace?topic=email-templates)
