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.
Before you start
Section titled “Before you start”- An API key with the
render:createscope. See HTTP API. - curl or a client library, set up as in the quickstart.
Fill in a text template
Section titled “Fill in a text template”Write the text with {{placeholders}}. Each one reads a value from data, so
{{customer.name}} reads data.customer.name:
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" } } }'using Typefold.Client;
var client = new TypefoldClient( new Uri(Environment.GetEnvironmentVariable("PDF_URL")!), Environment.GetEnvironmentVariable("API_KEY")!);
string content = await client.RenderStringAsync(new RenderStringRequest{ Template = "Hello {{customer.name}}, your ticket is {{ticket.number}}.", Data = new { customer = new { name = "Marta" }, ticket = new { number = "T-123" } },});
Console.WriteLine(content);import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL!, apiKey: process.env.API_KEY!,});
const content = await client.renderString({ template: "Hello {{customer.name}}, your ticket is {{ticket.number}}.", data: { customer: { name: "Marta" }, ticket: { number: "T-123" } },});
console.log(content);import { TypefoldClient } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL, apiKey: process.env.API_KEY,});
const content = await client.renderString({ template: "Hello {{customer.name}}, your ticket is {{ticket.number}}.", data: { customer: { name: "Marta" }, ticket: { number: "T-123" } },});
console.log(content);import os
from typefold import TypefoldClient, RenderStringRequest
client = TypefoldClient(os.environ["PDF_URL"], os.environ["API_KEY"])
content = client.render_string( RenderStringRequest( template="Hello {{customer.name}}, your ticket is {{ticket.number}}.", data={"customer": {"name": "Marta"}, "ticket": {"number": "T-123"}}, ))
print(content)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.
Placeholders
Section titled “Placeholders”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 asfirst-nameorfirst namecannot be read; rename them in your data. - A numeric segment reads an array element:
0,1,12, but not01. 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.5andtrue. To get1,250.50or1 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 answers400 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 answer400 BAD_REQUEST. Use the Handlebars engine for them.
Write HTML safely
Section titled “Write HTML safely”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":
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" }'using Typefold.Client;
var client = new TypefoldClient( new Uri(Environment.GetEnvironmentVariable("PDF_URL")!), Environment.GetEnvironmentVariable("API_KEY")!);
string content = await client.RenderStringAsync(new RenderStringRequest{ Template = "<p>Hi <b>{{customer.name}}</b>, your order {{order.id}} has shipped.</p>", Data = new { customer = new { name = "Tom & Jerry" }, order = new { id = "A-1009" }, }, Format = StringFormat.Html,});
Console.WriteLine(content);import { TypefoldClient, StringFormat } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL!, apiKey: process.env.API_KEY!,});
const content = await client.renderString({ template: "<p>Hi <b>{{customer.name}}</b>, your order {{order.id}} has shipped.</p>", data: { customer: { name: "Tom & Jerry" }, order: { id: "A-1009" } }, format: StringFormat.Html,});
console.log(content);import { TypefoldClient, StringFormat } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL, apiKey: process.env.API_KEY,});
const content = await client.renderString({ template: "<p>Hi <b>{{customer.name}}</b>, your order {{order.id}} has shipped.</p>", data: { customer: { name: "Tom & Jerry" }, order: { id: "A-1009" } }, format: StringFormat.Html,});
console.log(content);import os
from typefold import TypefoldClient, RenderStringRequest, StringFormat
client = TypefoldClient(os.environ["PDF_URL"], os.environ["API_KEY"])
content = client.render_string( RenderStringRequest( template="<p>Hi <b>{{customer.name}}</b>, your order {{order.id}} has shipped.</p>", data={"customer": {"name": "Tom & Jerry"}, "order": {"id": "A-1009"}}, format=StringFormat.HTML, ))
print(content){ "content": "<p>Hi <b>Tom & 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.
Add conditions, loops and helpers
Section titled “Add conditions, loops and helpers”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.
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; }" }'using Typefold.Client;
var client = new TypefoldClient( new Uri(Environment.GetEnvironmentVariable("PDF_URL")!), Environment.GetEnvironmentVariable("API_KEY")!);
string content = await client.RenderStringAsync(new RenderStringRequest{ Template = "{{#if (isEqual status \"paid\")}}Paid{{else}}Due{{/if}}: " + "{{#each tickets}}{{number}}{{#unless @last}}, {{/unless}}{{/each}}", Data = new { status = "paid", tickets = new[] { new { number = "T-1" }, new { number = "T-2" } }, }, Engine = StringEngine.Handlebars, Helpers = "function isEqual(a, b) { return a === b; }",});
Console.WriteLine(content);import { TypefoldClient, StringEngine } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL!, apiKey: process.env.API_KEY!,});
const content = await client.renderString({ 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: StringEngine.Handlebars, helpers: "function isEqual(a, b) { return a === b; }",});
console.log(content);import { TypefoldClient, StringEngine } from "@typefold/client";
const client = new TypefoldClient({ baseUrl: process.env.PDF_URL, apiKey: process.env.API_KEY,});
const content = await client.renderString({ 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: StringEngine.Handlebars, helpers: "function isEqual(a, b) { return a === b; }",});
console.log(content);import os
from typefold import TypefoldClient, RenderStringRequest, StringEngine
client = TypefoldClient(os.environ["PDF_URL"], os.environ["API_KEY"])
content = client.render_string( RenderStringRequest( 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=StringEngine.HANDLEBARS, helpers="function isEqual(a, b) { return a === b; }", ))
print(content)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. Withtext, no value is escaped. helpersis accepted only with this engine; with the default engine it answers400 BAD_REQUEST.- There are no partials, translations or scripts.
Limits
Section titled “Limits”| 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 |
Errors
Section titled “Errors”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. |
Next steps
Section titled “Next steps”- Handlebars templates: helpers, scripts and partials in full templates.
- Render a report: when you need a PDF instead of text.