Message Template Merge
communication.template_merge
Overview
Nothing here is sent and nothing is connected to: you get the rendered text back and what you do with it is entirely your business. Two properties are the reason to use this rather than a string replace of your own, and both are tested rather than promised. The first is that substitution happens once. A value is written into the output and never looked at again, so a value containing {{other}} lands in the text literally instead of being expanded — which is what stops a small template and a small set of values from rendering into something enormous, and stops whoever supplied a value from introducing a placeholder the template's author never wrote. The second is that nothing is escaped. The result is plain text: no HTML escaping, no URL encoding, no quoting and no spreadsheet neutralisation, because the destination is unknown here and a half-applied escape is more dangerous than none since it looks safe. Escape at the boundary you are actually writing to, and there are skills for that. Four placeholder styles are understood, a value can be a string, a number or a true or false, and you decide what happens when a placeholder has no value: refuse the whole render, leave a blank, leave the placeholder as it was, or put your own text there. The rendered length is worked out before a single character is built, so a render that would pass your ceiling is refused rather than assembled and then thrown away.
Everybody writes their own merge, and everybody writes the same two bugs into it. The first is substituting in a loop until nothing changes, which feels thorough and means a value someone else supplied can introduce a placeholder your template never had, or expand until you run out of memory. The second is escaping helpfully — HTML-escaping a value on the way in, so the text is wrong in an SMS and still unsafe in a page because half the job was done in the wrong place. We do neither: one pass, no escaping, and both are held down by tests rather than by intent. Around that sit the boring things worth having — a policy for absent values instead of a silent blank, a length worked out before the string is built, and the same placeholder scanner our validation skill uses, so a template that passed review renders with exactly the placeholders you were told about.
Reliability
The same template and the same values always produce the same text, and no clock, randomness or network is involved anywhere. Neither the template nor the values nor the rendered text is logged; a refusal carries a name, a count and a limit only. Numbers are written plainly — 3 stays 3 and 2.50 becomes 2.5 — and true and false are written as those words. Eight things stop the call instead of answering it. TEMPLATE_INVALID means the template was empty, so there was nothing to render; send it and retry. TEMPLATE_TOO_LARGE means the template passed 1000000 characters — split it, because the same text will always be refused. TEMPLATE_PLACEHOLDER_LIMIT_EXCEEDED means more than 2000 placeholders and placeholder-shaped forms together, which usually means the syntax is wrong for the file; check that first. TEMPLATE_SYNTAX_UNSUPPORTED means syntax named something we do not implement, and we refuse rather than fall back, because rendering under the wrong delimiters substitutes nothing and looks like it worked — correct the value and retry. TEMPLATE_VARIABLES_INVALID means the variables object was unusable: more than 2000 entries, a name outside 1 to 256 characters, two names differing only in case while case is being ignored, or a missingPolicy that is not one of the four — correct it and retry. TEMPLATE_VARIABLE_MISSING means the template referenced a name your values do not carry while the policy is FAIL; supply the value, or choose EMPTY, KEEP_PLACEHOLDER or PLACEHOLDER_TEXT if a hole is acceptable to you. TEMPLATE_VALUE_UNSUPPORTED_TYPE means a value was an array or an object while stringifyStructured is off, was nested deeper than 16 levels, or was a number that is not finite — format it into a string yourself, or turn stringifyStructured on. TEMPLATE_OUTPUT_TOO_LARGE means the render would pass maxOutputChars; the length was measured rather than built, so nothing was wasted — shorten the values, raise the ceiling, or render in parts. Retrying any of these unchanged gives the same answer.
Context footprint
The response is the rendered text plus a short receipt, so it is about the size of the message you asked for and never larger than the maxOutputChars you set. Set that ceiling to what your channel actually accepts and an oversized render costs you a refusal instead of a payload. A successful call costs 1 execution credit.
When to use it
Whenever a reviewed template meets per-recipient data, and especially when some of that data came from outside your own systems.
When the output goes straight into a page or a link without a sanitising or encoding step after it, or when your template needs control syntax evaluated.
Scope
Non-goals
- Does not send, queue or deliver the rendered text, and opens no connection of any kind
- Does not escape anything for HTML — web.sanitize_html is what makes text safe to place in a page
- Does not URL-encode a value going into a link; web.url_encode owns that
- Does not neutralise a leading equals sign or plus for a spreadsheet; data.csv_escape_formula owns that
- Does not re-scan what it substituted, so a value containing a placeholder is never expanded
- Does not evaluate a section, a loop, a conditional or a filter
- Does not check whether a placeholder is permitted; communication.template_validate answers that
Properties
Capabilities
Cost & billing
Flat rate per invocation
File handling
How we handle your data
What this means
This skill may receive sensitive content and may return submitted, transformed, extracted, or reformatted content.
Squillify guarantees
- Runs inside Squillify
- This skill runs inside Squillify.
- Payloads are not sent to an external service by this skill.
- Squillify does not permanently store submitted payloads.
- Telemetry records metadata only, not submitted content.
- Submitted payloads are not used for training.
- Submitted payloads are not reviewed by humans.
- This skill does not target sensitive data.
- The response may contain submitted or transformed content.
Your responsibility
Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.
Input schema
| Field | Type | Required | Description |
|---|---|---|---|
| template | string | Yes | Template text containing supported literal placeholders. |
| variables | object | Yes | Purpose-bound flat variable map; names are literal placeholder identifiers — unconstrained RFC 8259 JSON items. |
| missingPolicy | string | No | Missing-variable behavior; omit it for FAIL. |
| missingText | string | No | Replacement text used only by replace policy; omit it for empty. |
| syntax | string | No | Placeholder syntax; omit it for MUSTACHE. |
| caseSensitive | boolean | No | Whether names are case-sensitive; omit it for true. |
| trimValues | boolean | No | Whether string values are trimmed; omit it for false. |
| stringifyStructured | boolean | No | Whether arrays and objects serialize as JSON; omit it for false. |
| maxOutputChars | integer | No | Rendered output ceiling; omit it for one million. |
Output schema
| Field | Type | Required | Description |
|---|---|---|---|
| text | string | Yes | Rendered bounded template text. |
| missing | array | Yes | Bounded unresolved variable names in first-use order. The item shape is defined by the nested schema and the array contains from 0 through 10000 items. |
| substituted | array | Yes | Bounded substituted-variable receipts in first-use order. The item shape is defined by the nested schema and the array contains from 0 through 10000 items. |
| summary | object | Yes | Closed placeholder and output-size summary. Its documented communication properties are bounded to 256 entries — unconstrained RFC 8259 JSON items. |
API contract
POST /v1/executecurl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"communication.template_merge","input":{"template":"Hi {{first_name}}, order {{order_id}} is on its way.","variables":{"first_name":"Alice","order_id":"4417"},"syntax":"MUSTACHE"}}'import { Squillify } from "@squillify/sdk";
const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });
const { output } = await sq.execute("communication.template_merge", {"syntax":"MUSTACHE","variables":{"first_name":"Alice","order_id":"4417","item_count":3,"tracking_url":"https://track.example/4417"},"template":"Hi {{first_name}}, order {{order_id}} is on its way with {{item_count}} items. Track it at {{tracking_url}}. — the {{order_id}} team"});Errors
| Code | HTTP | Retryable | Description |
|---|---|---|---|
| VALIDATION_ERROR | 400 | No | The request payload failed schema validation. |
| UNAUTHORIZED | 401 | No | Missing or invalid API key. |
| SKILL_NOT_ACCESSIBLE | 403 | No | The API key is not granted access to this skill. |
| SKILL_NOT_AVAILABLE_FOR_PLAN | 403 | No | This skill is not included in your current plan. |
| PLAN_ELIGIBILITY_UNAVAILABLE | 503 | Yes | Plan eligibility could not be decided. Retry after backoff. |
| SKILL_NOT_FOUND | 404 | No | The requested skill or version does not exist. |
| TENANT_RATE_LIMITED | 429 | Yes | Request rate limit exceeded. Retry after backoff. |
| QUOTA_EXCEEDED | 429 | Yes | Plan quota exhausted. Retryable once the quota period resets. |
| EXECUTION_TIMEOUT | 504 | Yes | Execution exceeded the allowed time budget. |
| TEMPLATE_INVALID | 400 | No | The request payload failed this skill's input validation. |
| TEMPLATE_TOO_LARGE | 429 | No | A size, quota, rate, or time limit was exceeded. |
| TEMPLATE_PLACEHOLDER_LIMIT_EXCEEDED | 429 | No | A size, quota, rate, or time limit was exceeded. |
| TEMPLATE_SYNTAX_UNSUPPORTED | 400 | No | The skill does not support the requested variant. |
| TEMPLATE_VARIABLES_INVALID | 400 | No | The request payload failed this skill's input validation. |
| TEMPLATE_VARIABLE_MISSING | 412 | No | The input is valid but cannot be processed as-is. |
| TEMPLATE_VALUE_UNSUPPORTED_TYPE | 400 | No | The request payload failed this skill's input validation. |
| TEMPLATE_OUTPUT_TOO_LARGE | 429 | No | A size, quota, rate, or time limit was exceeded. |
Other limits
| Limit | Value |
|---|---|
| Recommended inline bytes | 32 KiB |
Limitations
- Does not send, queue or deliver the rendered text, and opens no connection of any kind
- Does not escape anything for HTML — web.sanitize_html is what makes text safe to place in a page
- Does not URL-encode a value going into a link; web.url_encode owns that
- Does not neutralise a leading equals sign or plus for a spreadsheet; data.csv_escape_formula owns that
- Does not re-scan what it substituted, so a value containing a placeholder is never expanded
- Does not evaluate a section, a loop, a conditional or a filter
- Does not check whether a placeholder is permitted; communication.template_validate answers that
- Producing HTML from untrusted values without sanitising afterwards
- Templates that need conditionals, loops or filters evaluated
- Anything expecting the substituted values to be scanned again for further placeholders
How to call
POST /v1/execute (requires authentication)
{
"skillKey": "communication.template_merge",
"input": {
"syntax": "MUSTACHE",
"variables": {
"first_name": "Alice",
"order_id": "4417",
"item_count": 3,
"tracking_url": "https://track.example/4417"
},
"template": "Hi {{first_name}}, order {{order_id}} is on its way with {{item_count}} items. Track it at {{tracking_url}}. — the {{order_id}} team"
}
}Examples
A shipping notice rendered from four values
{
"syntax": "MUSTACHE",
"variables": {
"first_name": "Alice",
"order_id": "4417",
"item_count": 3,
"tracking_url": "https://track.example/4417"
},
"template": "Hi {{first_name}}, order {{order_id}} is on its way with {{item_count}} items. Track it at {{tracking_url}}. — the {{order_id}} team"
}{
"text": "Hi Alice, order 4417 is on its way with 3 items. Track it at https://track.example/4417. — the 4417 team",
"missing": [],
"summary": {
"outputChars": 104,
"placeholderCount": 5,
"syntax": "MUSTACHE",
"templateChars": 132,
"substitutedCount": 4,
"missingCount": 0,
"missingPolicy": "FAIL"
},
"substituted": [
{
"occurrences": 1,
"name": "first_name"
},
{
"occurrences": 2,
"name": "order_id"
},
{
"occurrences": 1,
"name": "item_count"
},
{
"occurrences": 1,
"name": "tracking_url"
}
]
}A value carrying markup and a placeholder, passed through untouched
{
"variables": {
"comment": "<script>alert(1)</script> and a literal {{order_id}}",
"order_id": "4417"
},
"missingPolicy": "KEEP_PLACEHOLDER",
"template": "Order {{order_id}} note: {{comment}} (ref {{ref_code}})",
"syntax": "MUSTACHE"
}{
"text": "Order 4417 note: <script>alert(1)</script> and a literal {{order_id}} (ref {{ref_code}})",
"missing": [
"ref_code"
],
"summary": {
"outputChars": 88,
"placeholderCount": 3,
"syntax": "MUSTACHE",
"templateChars": 55,
"substitutedCount": 2,
"missingCount": 1,
"missingPolicy": "KEEP_PLACEHOLDER"
},
"substituted": [
{
"occurrences": 1,
"name": "order_id"
},
{
"occurrences": 1,
"name": "comment"
}
]
}Recommended use cases
- Render a transactional message from a reviewed template and per-recipient values
- Fill the stable parts of a template now and leave the rest in place with KEEP_PLACEHOLDER for a later stage to complete
- Produce a subject line or an SMS body whose length you then check with communication.sms_segments
- Refuse a render outright when a required value is absent, rather than sending a message with a blank where a name should be
Avoid for
- Producing HTML from untrusted values without sanitising afterwards
- Templates that need conditionals, loops or filters evaluated
- Anything expecting the substituted values to be scanned again for further placeholders
Related skills
Composable with
Safety & privacy
- Runs without calling any external service.
- Your payloads are never stored permanently.
- Telemetry captures metadata only — never your payload contents.
- We never train models on your payloads.
- No human reviews your payloads.