SEND_EMAIL node sends. You can author
them in the app’s builder, or from an agent or script with five tools (each is
also a REST endpoint):
A template is written one of two ways, and you send exactly one:
document— the builder’s blocks:heading,text,button,image,divider,spacer,html,social,footer, pluscontainer(a framed card) andcolumns(side by side). Styling is optional; what you leave out takes the builder’s defaults.html— your own markup. It is sanitized on save: scripts, event handlers and external stylesheets are removed, and the response lists what was removed inremoved.
{{path}} grammar in the subject and the body — {{customer.name}},
{{person.<attribute>}}, {{engagement.chatLink}} — and resolve at send; see
Template variables for what a path can be. A template’s
variables lists each path once, even when it is written both {{x}} and
{{ x }}; both spellings render.
{ "name": …, "subject": …, "html": "<table>…</table>" }.
Sending one
A template sends through a journey. Build it on an email initiative with aSEND_EMAIL step that points at the PUBLISHED template (journeys_email_templates
lists those, along with whether your domain and sender are ready), then launch
the initiative, which publishes the journey. Then enroll people:
- Directly:
initiatives_participants_addwith anemailfor each person. - On behavior: give the journey a
cdp_eventtrigger, and every time that event is recorded for a person with an email (cdp_events_record), they are enrolled.
Rules worth knowing
- New templates are drafts. A journey can only send a
PUBLISHEDtemplate; publish withemail_templates_updateand"status": "PUBLISHED". - Changing a published template needs
"confirm": true. Journeys reference the template itself, so an edit changes what they send from their next send, and setting it back toDRAFTmakes those sends fail. Call withoutconfirmfirst to see the refusal. - Unknown fields are rejected, not ignored, so a typo such as
backgroundfor a button’sbgfails with a validation error instead of quietly taking the default. - Blocks → HTML is one way. Sending
htmlto a block template converts it; an HTML template does not accept adocumentagain. - A footer block goes at the top level of the email, not inside a container or a
column. Omit
unsubscribeLabelfor the document language’s default link text, or send""for no unsubscribe link (transactional email). - Not available here yet: test sends, image upload (use an image URL you host, or one from the builder’s library), and deleting a template.
Preview with a real event
email_templates_preview renders a template the way a journey triggered by an
event would send it, without sending anything. Name the event one way:
latestEventName— the most recent event with that name, e.g."payment_due"; addpersonExternalIdfor that person’s most recent one.eventExternalId— the id your backend sent the event with.eventId— Boom’s id, fromcdp_events_list.
event.* (and engagement.workflowState.*),
including inside Liquid tags, so loops and conditions render as they will in
the real email. When the event names a person, customer.* and person.*
come from that person, as in the email the journey would send; fields your
organization marked sensitive stay hidden from an assistant. The response has
the subject, html and text, plus:
unresolved— the paths of variables that came out blank. A blankevent.*variable usually means the event doesn’t carry that property.liquidError— set when the Liquid failed to render.redactedKeys— event properties your organization marked sensitive. An assistant (MCP) always sees them masked; an API key sees real values.event— which event was used (nullwithout one, when sample values are used instead).
values overrides any path with fixed text ({ "customer.name": "Ana" }), and
bindings previews the email as a journey Send email step that remaps its
variables.