Skip to main content

Squillify development is paused indefinitely. Read why, and discover what this experiment achieved. Read the story

MicroSkills Reference

Message Template Merge

communication.template_merge

ProductionBounded custom (Class B)Deterministicstrictinstantv1.0.0
No network

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

Note
Substitutes supplied values into a template's placeholders in one pass and returns the rendered text.

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

IdempotentRetry safeStreaming supportedFile input supportedRequires external service

Capabilities

formattingtext-processing

Cost & billing

Flat rate per invocation

MetercallsBillable onsuccessCost per call1 credit

File handling

Accepts file_refProduces file_refMax input2 MB

How we handle your data

Sensitive output possible
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.

Read the Microskill Data Handling Policy

Input schema

FieldTypeRequiredDescription
templatestringYesTemplate text containing supported literal placeholders.
variablesobjectYesPurpose-bound flat variable map; names are literal placeholder identifiers — unconstrained RFC 8259 JSON items.
missingPolicystringNoMissing-variable behavior; omit it for FAIL.
missingTextstringNoReplacement text used only by replace policy; omit it for empty.
syntaxstringNoPlaceholder syntax; omit it for MUSTACHE.
caseSensitivebooleanNoWhether names are case-sensitive; omit it for true.
trimValuesbooleanNoWhether string values are trimmed; omit it for false.
stringifyStructuredbooleanNoWhether arrays and objects serialize as JSON; omit it for false.
maxOutputCharsintegerNoRendered output ceiling; omit it for one million.

Output schema

FieldTypeRequiredDescription
textstringYesRendered bounded template text.
missingarrayYesBounded 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.
substitutedarrayYesBounded 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.
summaryobjectYesClosed placeholder and output-size summary. Its documented communication properties are bounded to 256 entries — unconstrained RFC 8259 JSON items.

API contract

POST /v1/execute
curl
curl -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"}}'
Integrations
TypeScript SDK
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

CodeHTTPRetryableDescription
VALIDATION_ERROR400NoThe request payload failed schema validation.
UNAUTHORIZED401NoMissing or invalid API key.
SKILL_NOT_ACCESSIBLE403NoThe API key is not granted access to this skill.
SKILL_NOT_AVAILABLE_FOR_PLAN403NoThis skill is not included in your current plan.
PLAN_ELIGIBILITY_UNAVAILABLE503YesPlan eligibility could not be decided. Retry after backoff.
SKILL_NOT_FOUND404NoThe requested skill or version does not exist.
TENANT_RATE_LIMITED429YesRequest rate limit exceeded. Retry after backoff.
QUOTA_EXCEEDED429YesPlan quota exhausted. Retryable once the quota period resets.
EXECUTION_TIMEOUT504YesExecution exceeded the allowed time budget.
TEMPLATE_INVALID400NoThe request payload failed this skill's input validation.
TEMPLATE_TOO_LARGE429NoA size, quota, rate, or time limit was exceeded.
TEMPLATE_PLACEHOLDER_LIMIT_EXCEEDED429NoA size, quota, rate, or time limit was exceeded.
TEMPLATE_SYNTAX_UNSUPPORTED400NoThe skill does not support the requested variant.
TEMPLATE_VARIABLES_INVALID400NoThe request payload failed this skill's input validation.
TEMPLATE_VARIABLE_MISSING412NoThe input is valid but cannot be processed as-is.
TEMPLATE_VALUE_UNSUPPORTED_TYPE400NoThe request payload failed this skill's input validation.
TEMPLATE_OUTPUT_TOO_LARGE429NoA size, quota, rate, or time limit was exceeded.

Other limits

LimitValue
Recommended inline bytes32 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)

Request bodyjson
{
  "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

Inputjson
{
  "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"
}
Expected outputjson
{
  "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"
    }
  ]
}
Tip
Four distinct names across five occurrences: order_id appears twice and is counted once in substituted with occurrences two. The number 3 is written as 3 rather than 3.0, and summary carries the before and after lengths so you can see what the values cost you.

A value carrying markup and a placeholder, passed through untouched

Inputjson
{
  "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"
}
Expected outputjson
{
  "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"
    }
  ]
}
Tip
This is the pair of guarantees in one call. The script tag arrives in the output exactly as it was sent, because nothing is escaped here and pretending otherwise would be the dangerous part — sanitise at the boundary you are writing to. The literal {{order_id}} inside the comment stays as those characters instead of becoming 4417, because the pass never revisits what it wrote. And ref_code, which has no value, keeps its placeholder under KEEP_PLACEHOLDER and is listed in missing.
  • 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

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.

Tags

messagingemailtexttransformation