| name | templatical-email |
| description | Generate and validate Templatical email templates as JSON (content blocks + settings) that load into the Templatical drag-and-drop editor. This skill should be used whenever the user wants to create, design, draft, mock up, or edit a marketing, transactional, or newsletter email — for example "make a product-launch email", "design a welcome email", "build an event invite", "draft an order-confirmation email", or "turn this copy into an email template" — even when they don't say "Templatical" or "JSON". Also use it when producing or editing Templatical template JSON that must validate against the block schema. It can also open a live preview of the template in the real Templatical editor (loaded from the CDN) and update it as the user prompts, reconciling any in-browser hand-edits — triggered by natural-language intent such as "show it live", "preview it live", "open it in the editor", or "build this in live mode". |
Templatical Email Templates
Generate and validate email templates for the Templatical
editor. A template is a single JSON document — an array of content blocks plus
global settings — that loads straight into the editor for a human to refine.
The workflow is simple: read the brief, emit valid template JSON, validate it
with the bundled script, then hand it back. No API key or server is involved —
the agent running this skill is the inference.
This skill has two modes, one install:
- Build mode (default) — generate and validate template JSON. Fully offline,
cross-agent, nothing to install. This is everything below up to "Live mode".
- Live mode (optional) — open the template in the real
Templatical editor in a browser, update it live as the user prompts, and
reconcile their in-browser hand-edits. Local, needs no npm dependencies,
and runs on any local-shell agent that supports this skill (not just Claude
Code — see Live mode). Entered by intent; build mode is otherwise
unchanged.
Within a session both modes operate on one working template file in the
user's .templatical/ folder — so building in JSON and then saying "show it
live" picks up the current template seamlessly. Each template gets its own
uniquely named file and each new session starts a new template by default —
see Working files.
Talking to the user
Communicate about the email, not the machinery. The mechanical steps — reading
the references, picking an example, validating, generating file names, managing
.templatical/ — are for you, not the user; do them silently.
- Lead with intent and result. A sentence of what you're building is plenty
("Building an event invitation with clean neutral defaults"), then hand over
the template. Skip the play-by-play of your own file reads and checks.
- Surface real choices, not internals. Worth saying: "there's a template
from a previous session — start fresh or continue it?" Not worth saying:
dependency names (
ajv), file paths, example filenames, or "reading the
schema / validating" — keep those out of user-facing messages.
- Mention setup only on action or failure — say something when a
prerequisite is genuinely missing (see Requirements) or a
step actually fails, not to confirm that routine state is fine.
- Report real problems plainly when they happen (a validation error you
couldn't resolve, a missing dependency) with the fix — that's signal, not noise.
Requirements
Build mode needs Node.js 20 or newer, and nothing else. scripts/validate.mjs
runs on a clean install and offline — its two dependencies ship vendored in
vendor/ (ajv for structural validation, @templatical/quality for the
accessibility / structure / link lint that catches what structural validation
can't). Never npm install anything to make validation work; if it errors, that
is a bug to report, not a missing package.
Each optional mode adds one thing:
- Live mode — a machine where you can keep a background
process alive across turns and reach
localhost, plus the user's browser and
an internet connection (the editor and the MJML compiler load from the CDN).
Browser floor: Chrome/Edge 80+, Firefox 101+, Safari 16.4+.
- Import mode —
npm, to fetch the
converter for the source format on demand. scripts/import.mjs prints the
exact command when it's missing.
When a prerequisite is absent, say so plainly with the fix and fall back to a
mode that works — no Node or too old a version (node -v, then
https://nodejs.org) blocks everything; no background process or reachable port
(a hosted, server-side sandbox) blocks only live mode, and build mode is
unchanged.
Workflow
- Understand the brief — purpose (sale, newsletter, welcome…), audience,
tone, brand colors/fonts, and any copy or links supplied. Ask only if a hard
blocker is missing; otherwise choose sensible defaults.
- Read the references in
reference/:
reference/schema.json — the authoritative JSON Schema for the whole
document. When unsure about a field, this is the source of truth.
reference/block-guide.md — a concise description of every block type and
its fields.
reference/examples/*.json — complete, valid templates to model your
output on.
- Generate the JSON — a complete
{ "blocks": [...], "settings": {...} }
document, following the schema exactly (see Rules).
- Validate before returning — write the JSON to the session's working file
.templatical/<name>.json (see Working files — generate a
fresh three-word name for a new template; create the folder if needed) and run:
node scripts/validate.mjs .templatical/<name>.json
Fix every structural error reported and re-run until it passes, and resolve
the reported accessibility / structure / link warnings too. Writing to that file is what lets a later "show it
live" pick up the current template with no extra step.
- Hand off — return the validated JSON. What the user does with it depends on
who they are: a developer loads it into their editor integration
(
editor.setContent(json)); many others just want a finished email — for them,
preview it live and export MJML/HTML to send (see Live mode)
through any provider (SES, Postmark, …), no integration needed. Frame the
hand-off for what they're actually doing.
Working files
Every template lives in the user's .templatical/ folder as its own file with a
random three-word name, like a Claude plan file — e.g.
.templatical/misty-copper-otter.json. This keeps finished work around as a
browsable history instead of one file that silently carries state between
unrelated sessions.
- New by default. When the user asks for a template, treat it as a new
one: generate a fresh three-word kebab-case name (playful is fine), confirm no
file of that name already exists in
.templatical/ (regenerate if it does —
never overwrite an existing template), and write there. A fresh session thus
starts a fresh template; never silently resume an earlier one.
- Resume only on request. If the user asks to continue a previous template
("keep working on the welcome email", "open misty-copper-otter"), list
.templatical/*.json — using each file's first title/heading block as a hint —
and use the one they mean.
- One template per session. Track the active file name for the whole session
so build mode and live mode operate on the same template — validate, preview,
and reload all target that file. The
.templatical/ folder is a working area;
it's fine to leave old templates there (suggest the user gitignore it), and the
user can clear it whenever they like.
Importing an existing template
If the user has a template from another editor in their project — Unlayer,
BeeFree, or an HTML email — convert it to Templatical instead of building
from scratch. scripts/import.mjs runs the matching @templatical/import-*
converter:
node <skill>/scripts/import.mjs <source-file> [--format unlayer|beefree|html]
It auto-detects the format (Unlayer / BeeFree JSON, or .html), writes the
result to a working file .templatical/<name>.json (same as a generated
template — --out <name> overrides the default, which is the source file's
name), and prints a conversion report: how many blocks converted cleanly vs.
fell back to html vs. were skipped, plus warnings.
- Optional, install-on-demand. Each format needs its converter installed; the
script prints the exact
npm install @templatical/import-<format> if it's
missing. Build mode stays ajv-only.
- Import is lossy — unmapped constructs become
html blocks or get dropped.
So after importing: validate, then open it in live mode and refine the
html-fallback / skipped blocks into native blocks. That convert → preview →
refine loop is the whole point of importing through the skill rather than using
the converter package raw.
Rules
- Emit these block types:
section, title, paragraph, image,
button, divider, spacer, social, video, menu, table, html.
Prefer native blocks — reach for html only when nothing else fits, since raw
HTML is not visually editable afterward.
- Never emit
countdown or custom blocks (even though the schema allows
them): countdown needs the Templatical Cloud backend to render its
animated GIF — the open-source renderer can't, so it would break — and custom
blocks are consumer-registered runtime extensions that can't be produced from a
prompt. If the user asks for a countdown, say it's a Cloud feature and offer a
static stand-in instead — a title/paragraph with the date/time, or a "X days
to go" line (optionally a {{merge_tag}}).
- Every block needs
id (unique, e.g. "title_1"), type, and
styles.padding ({ top, right, bottom, left } in px).
- Structure content in sections. A
section has children: an array of
columns, each column an array of blocks. columns is "1", "2", "3",
"2-1", or "1-2", and the column count in children must match. Don't nest
a section inside another section — MJML has no equivalent, so the renderer
drops it on export.
- Rich text (
title.content, paragraph.content) is HTML — use inline tags
(<b>, <i>, <a href>, <br>, <ul>). Use blocks, not HTML, for layout.
Table cell content is the exception — plain text, no inline HTML (tags
render literally); for emphasis use hasHeaderRow, or a 2-column section of
blocks for a label/value layout.
Composing with project context
Layer the user's own context on top of these rules — brand guidelines, a
house system prompt, tone of voice, preferred fonts/palette, a mandatory
footer or links. When brand settings are provided, use them for colors, fonts,
and copy voice instead of generic defaults. This skill defines the format; the
user's context defines the taste.
Design defaults (when the brief is thin)
- 600px width, generous side padding (~24px), clear hierarchy (one lead
title, supporting paragraphs).
- One primary call-to-action button with a high-contrast background.
- Readable body text (14–16px), sufficient contrast, alt text on every image.
- A footer section (divider + social/menu + an unsubscribe line) for anything
campaign-like.
Live mode
Live mode opens the template in the real Templatical editor in a browser and
keeps it in sync as the user prompts, so they watch the email take shape and can
also drag-edit it directly. It's optional.
Where it runs. Live mode runs on the user's own machine — it keeps a
background process (the bridge) alive across turns and reaches localhost. If
your environment can't do that (e.g. a hosted or server-side sandbox with no
local filesystem or reachable port), tell the user live mode isn't available here
and stay in build mode. Build mode itself is unchanged.
It adds no npm dependencies. The bridge (scripts/live-server.mjs) uses only
Node built-ins; the editor and mjml-browser load from the CDN. These assets are
inert until live mode is started.
Entering live mode
Enter it whenever the user expresses the intent — "show it live", "preview it
live", "open it in the editor", "build this in live mode", or similar — mid-session
is fine. Natural-language intent is the portable trigger and works on every
harness. (In Claude Code you can also pass it as an argument with a space:
/templatical-email live — use a space, not a colon; /templatical-email:live is
plugin-command-namespace syntax that silently starts build mode instead.)
Live mode serves the session's working template (.templatical/<name>.json, see
Working files); if the user hasn't built one yet, create a new
template first. A mid-session switch just points the bridge at that file.
Working directory matters. The working file and the server's pidfile live
under the user's project directory (<project>/.templatical/), not this
skill's folder. Run every command below — live-server.mjs (start/reload/
stop) and validate.mjs — with the project as the current directory,
referencing the skill script by its path (node <skill>/scripts/live-server.mjs).
If you can't control the cwd, pass --cwd <project> (and optionally
--file <path>) so start/reload/stop all agree on the same location.
- Ensure the session's
.templatical/<name>.json exists and is valid (run the
validator; build a new template first if there isn't one yet).
- Start the bridge in the background (from the project root), pointing it at the
session's template with
--file:
node <skill>/scripts/live-server.mjs --file .templatical/<name>.json
It prints the URL it's serving, opens it in the user's default browser,
and prints the working-file path. It prefers port 4747 but falls back to a free
port if that's taken, so read the actual URL from its output (the port is
also recorded in .templatical/live-server.pid as port) — don't assume a
fixed port. It is single-instance via the pidfile guard; a second start just
reports the running one. Other flags: --port <n>, --cwd <project>,
--no-open (skip the auto-open).
- Share the URL in your reply so the user has it (to reopen, or open on another
device). The bridge already opened it in their default browser on start, so
you don't need to open it yourself — and never with a browser-automation/testing
tool (e.g. Playwright). If the auto-open didn't fire (a headless or sandboxed
environment), just point the user to the URL. The page shows the current
template in the real editor.
The prompt → live-update loop
When the user asks for a change:
- Check for divergence first. Read the editor's latest state from the
bridge's
GET /content endpoint (at the URL from step 2) → { divergent, content }.
divergent: false → no in-browser hand-edits since your last write. Proceed.
divergent: true → the user hand-edited in the browser. Ask before
overwriting: "You've edited the template in the browser since I last
updated it. Build on your browser version, or replace it with what I have?"
- Browser version → take the returned
content as your new base, apply
the requested change on top of it, and continue.
- Replace with mine → apply to your own version; say explicitly that
this discards their browser edits.
- Validate, always, before writing:
node <skill>/scripts/validate.mjs .templatical/<name>.json
(write your candidate first). Never push invalid content to the editor.
- Write the validated JSON to the session's
.templatical/<name>.json, then push it:
node <skill>/scripts/live-server.mjs reload
The page updates live (over Server-Sent Events) — no refresh.
Export
The page's Export button opens a modal with JSON / MJML / HTML tabs (HTML
compiles in-browser via mjml-browser, loaded on demand). You can also hand the
user JSON directly, or MJML/HTML via @templatical/renderer in build mode.
Ending live mode
Stop the bridge when the user is done (or the session ends) so no process or port
is orphaned:
node <skill>/scripts/live-server.mjs stop
Notes & limits
.templatical/ is a working directory — it holds the shared template and
the bridge's pidfile. Suggest the user add .templatical/ to their project's
.gitignore (don't edit their .gitignore without asking).
- Local and single-user. This is an ephemeral local bridge, not the Cloud
realtime/collaboration path (no accounts, no persistence, no multi-user).
html blocks run as-is in the preview (the editor sanitizes rich text, but
raw html blocks are not sanitized). It's the user's own local content, but be
aware the preview executes it.
- The CDN editor version is pinned to this skill's schema version, so the
live editor's block model matches
reference/schema.json.