POST /mail/v2/validate

Prev Next

Check a template's Liquid syntax, and optionally render it with sample data, without sending any email. Use this endpoint to test a template before it goes live, so mistakes are caught up front rather than surfacing in a real send.

Endpoint and headers

POST https://email.useinsider.com/mail/v2/validate

Header

Value

Purpose

X-INS-AUTH-KEY

API token, format INS.{id}.{secret}

Authentication

Content-Type

application/json

Request format

Body parameters

Below are the parameters for this request.

Parameter

Description

Data type

Required

subject

Subject template to validate.

String

No*

content

Content blocks to validate (type, value).

Array

No*

fields

Optional sample variables used to render valid templates.

Object

No

* Provide at least one of subject or content.

Request example

Below is a sample body for this request.

{
  "subject": "Hello {{ first_name }}",
  "content": [
    {
      "type": "text/html",
      "value": "<p>Hi {{ first_name }}</p>"
    }
  ],
  "fields": {
    "first_name": "Alice"
  }
}

Code samples

These samples are provided as is, as a starting point for your integration. Insider One does not guarantee that they work in every environment, so test them in your own environment before you use them in production.

Each sample validates a subject and an HTML block with sample fields, then prints the rendered output or each syntax error.

  1. Set your API key in the INSIDER_API_KEY environment variable.

  2. Copy the sample for your language into a file named validate.py or validate.mjs.

  3. Run the file. A valid template prints each field with its rendered output, such as subject: Hello Alice.

Python

Requires Python 3.8 or later and the requests package (pip install requests). Run python validate.py.

"""Check a template's Liquid syntax with POST /mail/v2/validate, without sending.

Sample code, provided as is. Test it in your own environment before you use it in production.
Requires Python 3.8+ and the requests package: pip install requests
"""
import os
import time

import requests

URL = "https://email.useinsider.com/mail/v2/validate"
# OAuth 2.0: call https://gw.useinsider.com/api/em/v2/tx/validate with "Authorization: Bearer {token}" instead.
HEADERS = {"X-INS-AUTH-KEY": os.environ["INSIDER_API_KEY"], "Content-Type": "application/json"}


def call(method, url, payload=None, retries=3):
    """Send the request. On 429 or 5xx, wait and retry up to `retries` times."""
    for attempt in range(retries + 1):
        response = requests.request(method, url, json=payload, headers=HEADERS, timeout=30)
        if (response.status_code != 429 and response.status_code < 500) or attempt == retries:
            return response
        wait = float(response.headers.get("Retry-After", 2 ** attempt))  # 1, 2, 4 s without Retry-After
        print(f"HTTP {response.status_code}: retrying in {wait:.0f}s")
        time.sleep(wait)


payload = {
    "subject": "Hello {{ first_name }}",
    "content": [{"type": "text/html", "value": "<p>Hi {{ first_name }}</p>"}],
    "fields": {"first_name": "Alice"},  # Optional: returns the rendered output
}

response = call("POST", URL, payload)
if response.status_code != 200:
    raise SystemExit(f"Validation request failed ({response.status_code}): {response.text[:300]}")
result = response.json()
for item in result.get("rendered", []):
    print(f"{item['field']}: {item['output']}")
for error in result.get("errors", []):
    print(f"Invalid {error['field']}: {error['message']}")

Node.js

Requires Node.js 18 or later. No packages are needed. Run node validate.mjs.

// Check a template's Liquid syntax with POST /mail/v2/validate, without sending.
// Sample code, provided as is. Test it in your own environment before you use it in production.
// Requires Node.js 18+ (built-in fetch). No dependencies.

const URL = "https://email.useinsider.com/mail/v2/validate";
// OAuth 2.0: call https://gw.useinsider.com/api/em/v2/tx/validate with "Authorization: Bearer {token}" instead.
const HEADERS = { "X-INS-AUTH-KEY": process.env.INSIDER_API_KEY, "Content-Type": "application/json" };
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// Send the request. On 429 or 5xx, wait and retry up to `retries` times.
async function call(method, url, payload, retries = 3) {
  for (let attempt = 0; ; attempt++) {
    const response = await fetch(url, {
      method,
      headers: HEADERS,
      body: payload === undefined ? undefined : JSON.stringify(payload),
      signal: AbortSignal.timeout(30000),
    });
    if ((response.status !== 429 && response.status < 500) || attempt === retries) return response;
    const wait = Number(response.headers.get("Retry-After") ?? 2 ** attempt); // 1, 2, 4 s without Retry-After
    console.warn(`HTTP ${response.status}: retrying in ${wait}s`);
    await sleep(wait * 1000);
  }
}

const payload = {
  subject: "Hello {{ first_name }}",
  content: [{ type: "text/html", value: "<p>Hi {{ first_name }}</p>" }],
  fields: { first_name: "Alice" }, // Optional: returns the rendered output
};

const response = await call("POST", URL, payload);
if (response.status !== 200) {
  throw new Error(`Validation request failed (${response.status}): ${(await response.text()).slice(0, 300)}`);
}
const result = await response.json();
for (const item of result.rendered ?? []) console.log(`${item.field}: ${item.output}`);
for (const error of result.errors ?? []) console.log(`Invalid ${error.field}: ${error.message}`);

Sample responses

The endpoint always returns 200; the outcome is in the body. When the syntax is valid and sample fields are provided, the rendered output is returned.

{
  "valid": true,
  "rendered": [
    {
      "field": "subject",
      "output": "Hello Alice"
    },
    {
      "field": "content[0]",
      "output": "<p>Hi Alice</p>"
    }
  ]
}

When the syntax is invalid, each problem is listed with the field it came from.

{
  "valid": false,
  "errors": [
    {
      "field": "content[0]",
      "message": "unterminated \"for\" block"
    }
  ]
}

Keep in mind

  • Validation checks Liquid syntax. Fields appear in a stable order: subject first, then content[0], content[1].

  • Run validation as the last step before a template goes live. It is the intended way to confirm a template is well-formed without sending a real request.

For the full outcome shapes, refer to the Error Codes.

Suggested Next

Once validation succeeds and you have reviewed any rendered output, see POST /mail/v2/send to prepare your transactional email send request.