Email Step
Email step for detailed content, formal communications, receipts.Schema Requirements (choose one editorType)
Option 1: Block Editor Format (recommended for simple email layouts)
Required properties:subject, editorType, body.
| Field | Value |
|---|---|
subject | string — email subject line |
editorType | "block" |
body | object — email body in Maily TipTap JSON format |
Option 2: HTML Format (recommended for complex email layouts)
Required properties:subject, editorType, body.
| Field | Value |
|---|---|
subject | string — email subject line |
editorType | "html" |
body | string — email body always in HTML format. Use semantic HTML with inline styles. Structure with headings, paragraphs, and styled buttons. |
Email Content Requirements
- Subject lines should be compelling and under 60 characters.
- Keep paragraphs short and scannable.
- Include clear call-to-action buttons when necessary.
HTML Format Requirements
bodymust be valid HTML with inline styles for email client compatibility.- Use semantic HTML:
<h1>,<h2>,<p>,<a>,<table>for layout. - Add inline styles for colors, spacing, fonts (e.g.
style="color: #333; margin: 16px 0;"). - Make sure that the content has enough whitespace between the elements and around the content to be readable.
- Use tables for layout to ensure compatibility across email clients. Avoid flexbox or grid; apply inline styles to table cells only when needed for spacing or typography.
- Include variables using Liquid syntax:
{{ subscriber.firstName }},{{ payload.variableName }}. - Full LiquidJS syntax (loops, conditionals, filters) is only supported in the HTML editor.
- When the payload or available variables contain an array (e.g.
payload.items,payload.providers), use a LiquidJS{% for %}loop to iterate over it. Never hardcode or list individual array items as separate HTML elements.
Example button (HTML format only)
Block Editor Format Guideline
- Use
headingnodes for titles (level 1 for main, level 2 for sections). - Use
textnode for body text. - Use
spacernodes between sections (height: 16or24). - Use
buttonnodes for CTAs with good contrast colors.
Block Editor nodes must follow these requirements
- Maily TipTap JSON format with proper node structure is required.
-
Variable names in node attributes must NEVER use curly braces
{{and}}. This applies to ALL attributes across ALL node types (url,text,src,id,each,href,externalLink, etc.).- Always use the bare variable name directly, without any templating syntax.
- Correct:
"payload.actionUrl" - Wrong:
"{{ payload.actionUrl }}"
-
Text variables should be defined using
variablenodes with anidattribute like"id": "subscriber.firstName"or"id": "payload.variableName". ThealiasForattribute is optional and should be used only when the variable is accessed inside arepeatnode. -
The
repeatnode must always have theeachattribute, for example"each": "payload.items". To access the items in the array, you must use thevariablenode with:idattribute (required)aliasForattribute (required)
variablenode only when used inside arepeatnode:-
The
idattribute must use the special prefixcurrent., for example"id": "current.variableName". -
The
aliasForattribute must consist of:<each value>+.+<variable name>, for example"aliasFor": "payload.items.variableName". -
Never use any other prefix than
current.in thevariablenodeidattribute when accessing array items. -
Example:
payload.items,payload.providers), always use arepeatnode to iterate over it. Never hardcode or list individual array items as separate nodes. Never use thecurrent.*variable outside of therepeatnode. -
buttonnodes: whenurlortextholds a variable, use the bare variable name and set the corresponding boolean flag.- Correct:
{ "type": "button", "attrs": { "url": "payload.actionUrl", "isUrlVariable": true } } - Wrong:
{ "type": "button", "attrs": { "url": "{{ payload.actionUrl }}", "isUrlVariable": true } } - Correct:
{ "type": "button", "attrs": { "text": "payload.label", "isTextVariable": true } }
- Correct:
-
imagenodes: same rule — bare variable name insrcorexternalLinkwith the matching boolean flag.- Correct:
{ "type": "image", "attrs": { "src": "payload.imageUrl", "isSrcVariable": true } } - Wrong:
{ "type": "image", "attrs": { "src": "{{ payload.imageUrl }}", "isSrcVariable": true } }
- Correct:
-
inlineImagenodes: same rule forsrcwithisSrcVariableandexternalLinkwithisExternalLinkVariable.- Correct:
{ "type": "inlineImage", "attrs": { "src": "payload.imageUrl", "isSrcVariable": true } } - Correct:
{ "type": "inlineImage", "attrs": { "externalLink": "payload.imageUrl", "isExternalLinkVariable": true } }
- Correct:
Digest Step Special Variables
-
steps.<digest-step-id>.events- Available only for steps that come after a digest step.
- The variable name is dynamic and depends on the digest step ID, for example:
steps.digest-step.eventssteps.digest-step-2.events
- It must be used with the
repeatnode only to iterate over the digested events payload. - To access the digested events
payloaddata, use thevariablenode with attributes:-
idattribute (required):- Must start with the
current.payloadprefix. - Format:
"current.payload.<variableName>". Example:"id": "current.payload.variableName". - Never use any prefix other than
current.payloadin thevariablenodeidattribute when accessing digested events.
- Must start with the
-
aliasForattribute (required):- Format:
"aliasFor": "steps.<digest-step-id>.events.payload.<variableName>". - Example:
"aliasFor": "steps.digest-step.events.payload.variableName".
- Format:
-
Example:
-
-
steps.<digest-step-id>.eventCount- Available only for steps that come after a digest step.
- The variable name is dynamic and depends on the digest step ID, for example:
steps.digest-step.eventCountsteps.digest-step-2.eventCount
- Used to access the number of digested events.
HTTP Request Step Response Variables
Available only for steps that come after an HTTP Request step that defines aresponseBodySchema. Each property from the schema becomes a variable at steps.<http-step-id>.<property>.
- Block Editor: create a
variablenode:id:"steps.<http-step-id>.<property>"- Example:
{ "type": "variable", "attrs": { "id": "steps.fetch-user.name" } }
- HTML format: use Liquid syntax:
{{ steps.fetch-user.name }}.
Only properties declared in the HTTP step’s responseBodySchema are available — do not reference arbitrary response fields.
See Also
http-request-step.md— declareresponseBodySchemaso an email step can read response datadigest-step.md— when an email step is preceded by a digest, use the special variables abovestep-conditions.md— gate the email on subscriber state or previous step results