Docyrus Email Template Design
Design a reusable email template with docyrus studio create-email-template, then validate its shape and test that it renders/sends with real record data. An email template is a row in tenant_email_template: a subject + body, optionally bound to a data source, with {{field}} placeholders filled at send time. Templates are consumed by the automation send-email action node (and other send paths) — they don't send themselves.
Workflow
-
Confirm app + auth, and the source record shape. A template's placeholders must match real record fields.
docyrus auth who --json docyrus apps list --json docyrus studio list-data-sources --appSlug crm --json # the data source whose records will fill the email (optional binding) docyrus studio list-fields --appSlug crm --dataSourceSlug deals --json # the field slugs you can interpolate -
Design the subject + body. Decide the merge fields (
{{slug}}), what comes from expanded relations/enums ({{field.name}}), and any conditional/looping sections. Keep the body as HTML. See references/templating.md for the Handlebars context and helpers. -
Create the template (
create-email-template).--nameand--subjectare required;--bodyis the HTML; optionally bind a data source with--dataSourceSlug/--dataSourceId. See Create. -
Validate — read the template back and confirm subject/body/binding landed. See Validate.
-
Test — render/send it against a real record (via an automation
send-emailnode, or a manualmessaging email send) and confirm placeholders resolve. See Test.
A worked example (a "deal won" notification email bound to Deals) with validation and a send test is in references/templating.md.
Command cheat-sheet
Selectors: --appId | --appSlug (only used to resolve a slug), --dataSourceId | --dataSourceSlug (the optional binding), --templateId (the template itself). Write commands take camelCase flags or --data/--from-file (flags merge over JSON; API keys snake_case). Append --json.
Create an email template
docyrus studio create-email-template --appSlug crm --dataSourceSlug deals \
--name "Deal won notification" \
--subject "🎉 {{name}} is closed-won" \
--body '<h1>Nice work!</h1><p>{{name}} closed at {{amount}} ({{stage.name}}).</p>' --json
# → capture data.id as TEMPLATE_ID
--nameand--subjectare required. Omitting either → HTTP 422.--bodyis the email body (HTML). It's optional at the API level, but a template with no body isn't useful.- Binding to a data source is optional (
--dataSourceSlug/--dataSourceId→tenant_data_source_id). Bind it when the template is for one entity's records (recommended — it documents intent and powers field validation in the UI). Leave unbound for generic emails. - Ownership is always
CUSTOM— it's set server-side, not a flag, and isn't returned. (Same as print/PDF templates.)
Manage / inspect
docyrus studio list-email-templates --appSlug crm --dataSourceSlug deals --json # filter by bound data source; omit to list all tenant templates
docyrus studio get-email-template --templateId TEMPLATE_ID --json # full body + __body_html
docyrus studio update-email-template --templateId TEMPLATE_ID --subject "New subject" --json # PUT, but partial
docyrus studio delete-email-template --templateId TEMPLATE_ID --json # soft delete (archived=true), 204
getreturns the full record incl.bodyand__body_html;listomits the body fields (metadata only).updateis HTTP PUT but behaves as a partial update — only fields you pass are changed.deleteis a soft delete (setsarchived=true) returning 204; the CLI prints a deletion envelope.
Critical rules
name+subjectare the only required fields. Everything else is optional.- Ownership is not settable — every template is
CUSTOM(server-set, not exposed as a flag or in responses). This mirrors print/PDF templates. - Data-source binding is optional for email templates (unlike print/PDF templates, where it's required).
- There is no
from/to/cc/reply_to/fromNameon the template. Sender and recipients are set by the send path (the automationsend-emailnode, ormessaging email send), not the template. The template owns onlysubject+body. - Don't write
__body_htmldirectly. It's a separate stored HTML column the CRUD services never write — the server/UI derives it. Authorbody; treat__body_htmlas read-only onget. - Unknown JSON keys are silently ignored (
whitelist:false). Puttingfrom,html,to,type, etc. in--dataneither errors nor does anything — they're dropped. Read the template back to confirm what stuck. - Placeholders are Handlebars
{{field}}over the expanded record at send time: bare fields{{slug}}, built-ins{{name}}/{{autonumber_id}}, expanded enum/relation/user fields as objects{{stage.name}}, and child data source collections via{{#each child.{from}__{using}}}(auto-fetched from the body — see references/templating.md). Not JSONata, not${}. - The template doesn't send itself. Wire it into an automation
send-emailnode (data.template:{id}) or send viamessaging. Validate by reading back; test by rendering through a send path. - Validate then test, every time. Confirm placeholders match real field slugs, then send one against a real record. Delete throwaway templates you create.
Validate
docyrus studio get-email-template --templateId TEMPLATE_ID --json—name,subject,bodycorrect;tenant_data_source_idset if you bound one.- Cross-check every
{{placeholder}}in subject+body againstdocyrus studio list-fields --appSlug crm --dataSourceSlug <bound-ds> --json— each bare{{slug}}is a real field; each{{x.name}}is an expanded enum/relation/user field. A placeholder with no matching field renders empty (Handlebars doesn't error on missing keys). docyrus studio list-email-templates --appSlug crm --dataSourceSlug <ds> --json— the template appears under its bound data source.
Checklist detail in references/templating.md.
Test
Email templates have no standalone render endpoint — exercise them through a send path:
- Via automation (recommended): create a
send-emailnode referencing the template (--data '{"data":{"template":{"id":"TEMPLATE_ID"},"sender_type":"system"},"field_mapping":{"to":"<recipient field>"}}') on an automation whose trigger you can fire (docyrus-automation-design), then fire it against a real record and inspect the sent/preview email: every{{placeholder}}should be filled. - Via messaging:
docyrus messaging email send …against a real record context to confirm the rendered subject/body. - Placeholder boundary: include one
{{nonexistent_field}}in a throwaway copy and confirm it renders empty (not an error) — this is why validating slugs againstlist-fieldsmatters. - Clean up:
docyrus studio delete-email-template --templateId TEMPLATE_ID --jsonand remove any throwaway automation/records.
Full send-test playbook in references/templating.md.
References
- references/templating.md — The Handlebars context (record fields, expanded objects, built-ins, helpers), authoring patterns for email bodies, the worked example, validation checklist, and the send-test playbook.
- docyrus-automation-design — the
send-emailaction node that consumes the template. docyrus-print-pdf-template-design — HTML/PDF print/report templates (the siblingstudio *-html-templateflow). docyrus-cli-app — CLI command index (docyrus studio … --help,messaging email send). docyrus-platform →references/integrations-and-events.md(email system concepts).