mustachio

Write correct Mustachio template syntax for Postmark email templates. Use this skill whenever the task involves Postmark templates, Mustachio syntax, or email template HTML that uses {{ }} curly-brace placeholders destined for Postmark. Mustachio looks like Mustache but is a different engine with different rules — this skill prevents you from writing Mustache syntax that silently fails in Mustachio.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "mustachio" with this command: npx skills add orbit-logistics/mustachio/orbit-logistics-mustachio-mustachio

Mustachio Templating for Postmark

Why this skill exists

Mustachio is the templating engine that powers Postmark email templates. It is visually similar to Mustache (both use {{ }} curly braces) but it is a separate engine with different supported features. Because Mustache dominates training data and Mustachio is comparatively obscure, LLMs consistently generate Mustache syntax when asked to write Mustachio — and the errors are subtle because the two look almost identical. The wrong syntax doesn't throw errors; it silently produces broken output.

This skill is the authoritative reference. When writing or modifying anything that will be processed by Postmark's template engine, follow these rules exactly.


Step Zero: Get the Layout First

Postmark uses a Layout + Template architecture. The Layout is a separate Mustachio template that wraps every inner template. It contains:

  • The <html>, <head>, and <body> tags
  • All shared CSS in <style> blocks (Postmark auto-inlines these at send time)
  • Common header and footer HTML
  • The {{{ @content }}} token — this is where the inner template gets injected

The inner template (the part you write) is just the body content that gets inserted at {{{ @content }}}. It has no <html>, <head>, <style>, or <body> tags of its own — those all live in the Layout.

Why this matters: The inner template uses CSS classes that are defined in the Layout's <style> block. If you don't know what classes exist, you'll either invent class names that don't exist (unstyled output) or add inline styles that conflict with the Layout's design system.

Before writing any template, always:

  1. Ask the user to paste their Postmark Layout (the HTML of the Layout template their server uses). Say something like: "Please paste the Postmark Layout HTML that this template will use. I need to see which CSS classes are available so the template works correctly with your styling."
  2. Read the Layout's <style> block to identify all available CSS classes
  3. Write the inner template using only those CSS classes
  4. If the user doesn't have a Layout or wants a standalone template, explicitly confirm this — then you may include full HTML with <style> tags, but flag that it won't benefit from Layout-based reuse

Rules for inner templates (when a Layout is used):

  • Never include <!DOCTYPE>, <html>, <head>, <style>, or <body> — the Layout provides these
  • Only use CSS classes defined in the Layout's stylesheet
  • If you need a style not in the Layout, add it as an inline style="" attribute and note it to the user as something they may want to add to the Layout for reuse
  • The template's HTML starts directly with content elements (e.g., <div>, <table>, <h1>)

The Complete Mustachio Feature Set

Mustachio supports exactly these features and nothing else. If a syntax construct is not listed here, it does not work.

1. Variable interpolation (HTML-escaped)

{{ name }}
{{ user.address.city }}

All output is HTML-encoded by default to prevent XSS.

2. Unescaped (raw) output

{{{ rawHtml }}}
{{& rawHtml }}

Both forms are equivalent. Use sparingly — raw output creates XSS risk in browser contexts.

3. Sections (conditional blocks)

{{#propertyName}}
  Rendered when propertyName is truthy.
{{/propertyName}}

A section renders when its value is present and truthy. Scoping behavior depends on the value type — see "Section Scoping" below.

4. Inverted sections (render when falsy/absent)

{{^propertyName}}
  Rendered when propertyName is missing, null, false, empty string, or empty array.
{{/propertyName}}

5. {{#each}} loops — the key difference from Mustache

{{#each items}}
  {{ name }} — {{ price }}
{{/each}}

Arrays are iterated with {{#each array}}. This is Mustachio-specific syntax that does not exist in standard Mustache.

In standard Mustache, {{#array}}...{{/array}} iterates over the array. In Mustachio, {{#array}} does NOT iterate — it only performs a truthiness check (non-empty array = truthy). You must use {{#each array}} to iterate.

6. Dot notation for nested paths

{{ order.shipping.address.city }}

7. Parent scope navigation with ../

{{#each items}}
  {{ ../companyName }}
  {{#each tags}}
    {{ ../../companyName }}
  {{/each}}
{{/each}}

Each ../ traverses one scope level up. Works inside {{#each}} and inside section blocks.

8. Current context self-reference with .

{{#title}}{{ . }}{{/title}}

The dot operator outputs the current scoped value itself. Primarily used inside scalar sections.

9. Postmark-specific tokens

{{{ @content }}}          — Layout content insertion point
{{{ pm:unsubscribe }}}    — Unsubscribe link (required for Broadcast streams)

That is the complete list. Everything below this line is something Mustachio does NOT support.


What Mustachio Does NOT Support

These are features from Mustache, Handlebars, or other templating engines that look plausible but do not work in Mustachio. Every one of these is a mistake LLMs commonly make.

SyntaxEngine it belongs toWhat to do instead
{{#if condition}}HandlebarsUse {{#property}} truthiness section
{{else}}Mustache/HandlebarsUse {{^property}} inverted section
{{#unless condition}}HandlebarsUse {{^property}}
{{> partialName}}MustacheNot available in Postmark
{{#helper}}{{val}}{{/helper}}HandlebarsNo lambdas or helper functions
{{ val | filter }}Liquid/NunjucksNo filters or pipes
{{! comment }}MustacheNot documented as supported — do not use
{{=<% %>=}}MustacheCannot change delimiters
{{$block}}...{{/block}}Mustache inheritanceNot supported
{{ a + b }}Expression enginesNo arithmetic in templates
{{#if x > 5}}HandlebarsNo comparison operators

The golden rule: Mustachio handles display and conditional show/hide only. All data transformation, formatting, arithmetic, and comparison logic must happen server-side before reaching the template.


Section Scoping — The Most Important Concept

Sections ({{# }}) behave differently depending on the data type of the value. Getting this wrong produces silent bugs. There are two cases:

Scalar sections (value is a string, number, or boolean)

When a section opens on a scalar value, the scope changes to that scalar. Inside the section:

  • {{ . }} outputs the scalar value itself
  • All other variable references require ../ to navigate back to the parent scope — they will NOT auto-resolve

This is the #1 source of silent bugs. It is tempting to write {{#isActive}}{{ name }}{{/isActive}} thinking "it's just a boolean gate, scope doesn't change." It does. {{ name }} resolves to nothing. You must write {{ ../name }}.

Model:   { orderId: "ORD-5521", isUrgent: "YES", customer: "Acme" }

-- Access the scalar value itself with {{ . }}
Template: Order{{#orderId}} {{ . }}{{/orderId}}
Output:   Order ORD-5521

-- Access sibling data: MUST use ../
Template: {{#isUrgent}}URGENT: Order {{ ../orderId }} for {{ ../customer }}{{/isUrgent}}
Output:   URGENT: Order ORD-5521 for Acme

-- WRONG: missing ../ — silently outputs nothing for orderId
Template: {{#isUrgent}}URGENT: Order {{ orderId }}{{/isUrgent}}
Output:   URGENT: Order

Rule: Inside ANY section (scalar or object), always use ../ to access properties from the parent scope.

Object sections (value is an object)

When a section opens on an object, the scope changes INTO the object. Inside the section, you access child properties by name — without the parent path prefix.

Model:   { shipping: { method: "Express", cost: "12.50" } }

Template: {{#shipping}}{{ method }} — {{ cost }} EUR{{/shipping}}
Output:   Express — 12.50 EUR

To reach properties outside the current scope, use ../:

{{#shipping}}
  {{ method }} for order {{ ../orderId }}
{{/shipping}}

Choosing the right pattern

SituationTemplate pattern
Show value only if present{{#discount}}{{ . }}{{/discount}}
Show value with surrounding text{{#discount}}You save {{ . }}{{/discount}}
Show section with multiple child values{{#shipping}}{{ method }}: {{ cost }}{{/shipping}}
Show/hide block (no value needed){{#isPremium}}Premium Member{{/isPremium}}
Show/hide block, access sibling data{{#isPremium}}{{ ../memberName }}{{/isPremium}}

If/Else Logic Without if or else

Mustachio has no if/else keywords. Use a section immediately followed by its inverted counterpart:

{{#hasAccount}}
  Welcome back!
{{/hasAccount}}
{{^hasAccount}}
  Create an account to get started.
{{/hasAccount}}

This is the only way to implement either/or logic. The {{#prop}} block renders when truthy; the {{^prop}} block renders when falsy. Together they cover both cases.


Truthiness Rules

All conditional logic in Mustachio works through truthiness checks. There is no other conditional mechanism.

ValueTruthy?{{#x}} renders?{{^x}} renders?
"hello" (non-empty string)YesYesNo
"" (empty string)NoNoYes
trueYesYesNo
falseNoNoYes
nullNoNoYes
Property absent from modelNoNoYes
{ key: "val" } (object)YesYes (scopes in)No
{} (empty object)YesYes (scopes in, no children)No
[item1, item2] (non-empty array)YesYes (does NOT iterate)No
[] (empty array)NoNoYes

Key subtlety: {} (empty object) is truthy — it will cause a {{#section}} to render. And non-empty arrays are truthy but {{#array}} does NOT iterate — always use {{#each array}} to loop.


Long Values Breaking Layout Width

Postmark Layouts typically constrain the email body to a fixed width (e.g. 570px). Long unbroken strings — URLs, API keys, file paths, hashes — contain no spaces or hyphens, so the browser/email client cannot word-wrap them. The string forces its table cell wider than the Layout container, breaking the entire email layout.

Fix: Add word-break: break-all to any <td> that may contain long unbroken values:

<tr>
  <td class="attributes_item" style="word-break: break-all;">
    <strong>Target URL:</strong> {{ targetUrl }}
  </td>
</tr>

Apply this proactively to cells displaying URLs, tokens, file paths, or any value likely to exceed ~60 characters without whitespace. It is better to add word-break: break-all unnecessarily than to ship a layout-breaking email.


Quick Self-Check

Before considering any template complete, verify:

  1. Layout compatibility: If a Layout is in use, the template has no <!DOCTYPE>, <html>, <head>, <style>, or <body> tags — and only uses CSS classes from the Layout
  2. No {{#if, {{else}}, {{#unless anywhere
  3. Every array uses {{#each arrayName}}, never {{#arrayName}}
  4. No {{> partial}} references
  5. No helpers, filters, pipes, or functions in template expressions
  6. Inside scalar sections, values are accessed with {{ . }}
  7. Inside object sections, child properties are accessed by name (not .)
  8. Every variable inside a {{#section}} that refers to a sibling/parent property uses ../ — this applies to BOTH scalar and object sections
  9. All formatting is done server-side, not in the template
  10. Cells containing URLs, tokens, or other long unbroken strings have style="word-break: break-all;" to prevent layout overflow

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

ll-feishu-audio

飞书语音交互技能。支持语音消息自动识别、AI 处理、语音回复全流程。需要配置 FEISHU_APP_ID 和 FEISHU_APP_SECRET 环境变量。使用 faster-whisper 进行语音识别,Edge TTS 进行语音合成,自动转换 OPUS 格式并通过飞书发送。适用于飞书平台的语音对话场景。

Archived SourceRecently Updated
General

test_skill

import json import tkinter as tk from tkinter import messagebox, simpledialog

Archived SourceRecently Updated
General

51mee-resume-profile

简历画像。触发场景:用户要求生成候选人画像;用户想了解候选人的多维度标签和能力评估。

Archived SourceRecently Updated
General

51mee-resume-parse

简历解析。触发场景:用户上传简历文件要求解析、提取结构化信息。

Archived SourceRecently Updated