Skip to content

Render email and SMS text

Send a short text template, such as an email subject, an SMS or a simple HTML email body, together with your data. The filled-in text comes back in the response. Nothing is stored, no webhooks are sent, and string renders do not count against your render allowances.

  • An API key with the render:create scope. See HTTP API.
  • curl or a client library, set up as in the quickstart.

Write the text with {{placeholders}}. Each one reads a value from data, so {{customer.name}} reads data.customer.name:

Terminal window
curl "$PDF_URL/v1/renders/string" \
-H "x-api-key: $API_KEY" \
-H "content-type: application/json" \
-d '{
"template": "Hello {{customer.name}}, your ticket is {{ticket.number}}.",
"data": { "customer": { "name": "Marta" }, "ticket": { "number": "T-123" } }
}'

The response is 200 with the text in content. The client libraries return content directly.

{ "content": "Hello Marta, your ticket is T-123.", "format": "text" }

The request takes template and data, plus the optional format, engine and helpers described below. Any other field answers 400 BAD_REQUEST. The full schema is in the API reference.

A placeholder is a property name, or a dotted path to a nested value:

Placeholder Reads
{{name}} data.name
{{customer.name}} data.customer.name
{{invoices.0.number}} number of the first element of the invoices array
{{ customer.name }} The same as {{customer.name}}; outer spaces are ignored
  • Each name starts with a letter, _ or $, followed by letters, digits, _ or $. Keys such as first-name or first name cannot be read; rename them in your data.
  • A numeric segment reads an array element: 0, 1, 12, but not 01. The first segment must be a name. A path has at most 20 segments.
  • The value must be a string, a number or a boolean. Numbers and booleans are written as JavaScript prints them, such as 12.5 and true. To get 1,250.50 or 1 250,50, format the value in your application and send it as a string.
  • A value that is missing, null, an object or an array answers 400 BAD_REQUEST. Send "" for a value you want blank.
  • Text outside {{ }} is copied unchanged, including single braces and a stray }}. Filled-in values are never read as placeholders.
  • Every {{ starts a placeholder, so the result cannot contain a literal {{. Blocks such as {{#if}} and {{#each}}, helpers, partials, triple braces {{{ }}} and comments answer 400 BAD_REQUEST. Use the Handlebars engine for them.

By default ("format": "text"), values are inserted exactly as they are, so a value such as <script> stays markup. When the result goes into HTML, always send "format": "html":

Terminal window
curl "$PDF_URL/v1/renders/string" \
-H "x-api-key: $API_KEY" \
-H "content-type: application/json" \
-d '{
"template": "<p>Hi <b>{{customer.name}}</b>, your order {{order.id}} has shipped.</p>",
"data": { "customer": { "name": "Tom & Jerry" }, "order": { "id": "A-1009" } },
"format": "html"
}'
{
"content": "<p>Hi <b>Tom &amp; Jerry</b>, your order A-1009 has shipped.</p>",
"format": "html"
}

With html, the characters &, <, >, ", ', ` and = in values are escaped, so values are also safe inside quoted or unquoted attributes. Control characters below U+0020, other than tab and line breaks, become � (U+FFFD). The template’s own markup is kept as written.

The default engine only fills in values. For {{#if}}, {{#each}} and your own helpers, send "engine": "handlebars" and write the template in Handlebars. Put helpers in helpers as JavaScript: every top-level function becomes a helper, as in a template’s helpers.js.

Terminal window
curl "$PDF_URL/v1/renders/string" \
-H "x-api-key: $API_KEY" \
-H "content-type: application/json" \
-d '{
"template": "{{#if (isEqual status \"paid\")}}Paid{{else}}Due{{/if}}: {{#each tickets}}{{number}}{{#unless @last}}, {{/unless}}{{/each}}",
"data": { "status": "paid", "tickets": [{ "number": "T-1" }, { "number": "T-2" }] },
"engine": "handlebars",
"helpers": "function isEqual(a, b) { return a === b; }"
}'

This returns Paid: T-1, T-2. How the Handlebars engine differs from the default one:

  • A missing value prints as empty text instead of answering 400.
  • With html, values in {{ }} are escaped the Handlebars way and values in {{{ }}} are not. With text, no value is escaped.
  • helpers is accepted only with this engine; with the default engine it answers 400 BAD_REQUEST.
  • There are no partials, translations or scripts.
Limit Value Error
Request body 1,000,000 bytes 413 LIMIT_EXCEEDED
Input: template, helpers and data (as JSON) 1,000,000 bytes 400 LIMIT_EXCEEDED
Result content, in UTF-8 1,000,000 bytes 400 LIMIT_EXCEEDED
Nesting depth of data 100 levels 400 BAD_REQUEST
Segments in one placeholder path 20 400 BAD_REQUEST
helpers 500,000 characters 400 BAD_REQUEST
Handlebars run 100 ms CPU time, 16 MiB memory 400 LIMIT_EXCEEDED
Handlebars template size 50,000 syntax nodes, nested at most 100 deep 400 LIMIT_EXCEEDED

Fix the template or the data and send the request again. Only 429 is worth retrying unchanged. A request over a limit answers the error in its row, and the message names the limit. Authentication and license errors are the same as for every endpoint; see HTTP API.

Status and code Message Cause
400 BAD_REQUEST Use {{name}} or {{customer.name}} placeholders with at most 20 path segments. A placeholder is malformed or not closed.
400 BAD_REQUEST Each placeholder must resolve to a string, number, or boolean. A value is missing, null, an object or an array.
400 BAD_REQUEST Invalid Handlebars template: … Handlebars: the template does not compile.
400 BAD_REQUEST helpers.js: … Handlebars: helpers is not valid JavaScript.
400 BAD_REQUEST Handlebars render failed: … Handlebars: a helper threw, or a helper is missing.
429 RATE_LIMITED Template scripts are busy. Retry shortly. Handlebars: the server is busy. Retry shortly.