MOLTED EMAIL
CLI Reference

molted templates

Manage email templates

CommandDescription
molted templates createCreate a new template
molted templates listList all templates
molted templates getGet a template by ID or slug
molted templates add-versionAdd a new version to a template
molted templates publishPublish the latest version of a template
molted templates deleteDelete a template (by id or slug). Its slug becomes free again. Refused for _default and while an active or paused journey uses it; sent emails keep their history.
molted templates approveApprove a pending template version. Human-only (D3, epic #1353): on a tenant that enforces the human gate this returns 403 human_decision_required with a decisionUrl.
molted templates rejectReject a pending template version. Human-only (D3, epic #1353): on a tenant that enforces the human gate this returns 403 human_decision_required with a decisionUrl.
molted templates renderTest-render a template with sample data

Agent context

Templates define the structure and content of emails. Every send references a template by slug or ID. Templates support {{variable}} placeholders (e.g. {{name}}; plain names only, not Handlebars helpers or dot paths) that are filled from the --payload at send time.

The _default template

  • Auto-created with every tenant — always available
  • Accepts: { "subject": "...", "html": "...", "text": "..." } as payload
  • Use it for one-off emails; create named templates for recurring sends

Create and publish a template in one step. Variables used in --subject/--body must be declared with --variables, and a marketing template (the default when --type is omitted) needs an unsubscribe link or it fails to lint:

molted templates create --name "Welcome" --slug welcome \
  --subject "Welcome, {{name}}!" \
  --body "<h1>Hello {{name}}</h1><p><a href='{{unsubscribe_url}}'>Unsubscribe</a> · {{physical_address}}</p>" \
  --text "Hello {{name}}" \
  --variables '[{"name": "name", "type": "string", "required": true}]' \
  --auto-publish
# → { "template": {...}, "version": { ..., "lintPassed": true }, ... }

Then send with it

molted send --to alice@example.com --template welcome \
  --dedupe-key "welcome-alice-1" --payload '{"name": "Alice"}'

Template versioning (without --auto-publish)

molted templates create --name "Promo" --slug promo
molted templates add-version <id> --subject "{{offer}}" \
  --body "<p>{{offer}}</p><p><a href='{{unsubscribe_url}}'>Unsubscribe</a> · {{physical_address}}</p>" \
  --variables '[{"name": "offer", "type": "string", "required": true}]'
molted templates publish <id>

Approval workflow (for regulated sends)

molted templates create --name "Legal" --slug legal-notice --type transactional --approval-required
molted templates add-version <id> --subject "Notice" --body "<p>Your account was updated.</p>"
molted templates publish <id>
# → returns an approvalId (version is pending, not live yet)
molted templates approve <approvalId> --reviewed-by "alice"
# → version is now live and can be sent
# Approving is a human decision: once the account's human gate enforces, an
# agent key gets 403 human_decision_required (a person approves in the portal).

Test rendering before sending

molted templates render <id> --payload '{"name": "Alice", "offer": "20% off"}'
# → { "subject": "...", "html": "...", "text": "...", "errors": [], "warnings": [] }
# Renders the published version (422 if the template has none published yet).

Delete a template you no longer need (e.g. a test one)

molted templates delete <id-or-slug>
# → { "deleted": true, "id": "...", "slug": "...", "rejectedApprovals": 0 }

The slug is free again right away, so you can recreate a template with it. Refused for _default, and while an active or paused journey has a step that uses the template (the error names those journeys: archive or delete them, or point their steps at another template, first). Versions waiting for approval are rejected. Emails already sent with it keep their history.

Template types

  • "transactional" - receipts, confirmations, password resets; exempt from the unsubscribe-link and physical-address lint rules
  • "marketing" - newsletters, promos (subject to the consent gate); needs an unsubscribe link to pass lint ({{unsubscribe_url}} is filled at send time). A marketing send is blocked until the account has a physical address on file, which Molted appends as a footer; a {{physical_address}} placeholder only satisfies the lint warning and renders empty.

--type defaults to "marketing" when omitted -- pass --type transactional if that's not what you want. Omitting --type prints a stderr note as a reminder.

Related: molted send, molted mailboxes

Commands

molted templates create

Create a new template

molted templates create [options]
OptionDescriptionDefault
--name <name>Template name (required) (required)
--slug <slug>Template slug (required) (required)
--type <type>Template type (defaults to marketing if omitted) (one of: marketing, transactional)
--approval-requiredRequire approval before publishing
--subject <subject>Subject template
--body <html>HTML body template
--text <text>Plain text body template
--variables <json>Template variables (JSON array)
--auto-publishAuto-publish the initial version

molted templates list

List all templates

molted templates list

molted templates get

Get a template by ID or slug

molted templates get <id>
ArgumentDescriptionRequired
<id>yes

molted templates add-version

Add a new version to a template

molted templates add-version [options] <id>
ArgumentDescriptionRequired
<id>yes
OptionDescriptionDefault
--subject <subject>Subject template (required) (required)
--body <html>HTML body template (required) (required)
--text <text>Plain text body template
--variables <json>Template variables (JSON array)

molted templates publish

Publish the latest version of a template

molted templates publish [options] <id>
ArgumentDescriptionRequired
<id>yes
OptionDescriptionDefault
--requested-by <name>Who requested the publishcli

molted templates delete

Delete a template (by id or slug). Its slug becomes free again. Refused for _default and while an active or paused journey uses it; sent emails keep their history.

molted templates delete <id>
ArgumentDescriptionRequired
<id>yes

molted templates approve

Approve a pending template version. Human-only (D3, epic #1353): on a tenant that enforces the human gate this returns 403 human_decision_required with a decisionUrl.

molted templates approve [options] <approvalId>
ArgumentDescriptionRequired
<approvalId>yes
OptionDescriptionDefault
--reviewed-by <name>Who reviewed the templatecli
--comment <comment>Review comment

molted templates reject

Reject a pending template version. Human-only (D3, epic #1353): on a tenant that enforces the human gate this returns 403 human_decision_required with a decisionUrl.

molted templates reject [options] <approvalId>
ArgumentDescriptionRequired
<approvalId>yes
OptionDescriptionDefault
--reviewed-by <name>Who reviewed the templatecli
--reason <reason>Rejection reason

molted templates render

Test-render a template with sample data

molted templates render [options] <id>
ArgumentDescriptionRequired
<id>yes
OptionDescriptionDefault
--payload <json>Template payload as JSON object (required) (required)

Note: --payload is required. Pass a JSON object matching the template's variables.

Example

molted templates render <id> --payload '{"name": "Alice", "offer": "20% off"}'
# → returns rendered HTML and text with variables substituted

Use this to verify rendering before sending. The _default template expects:

{ "subject": "...", "html": "...", "text": "..." }

See CLI overview for global options, credentials and output format.