content.markup, without the editor’s own JSON.
AhaML is XML. Each tag is one part of the design, such as a paragraph, a button or a bold word, and each attribute is one of its settings.
How reads and writes work
The design is stored in the editor’s own form. A read prints it as AhaML, and a write turns the AhaML back into that form, so the editor opens a design written as AhaML as it opens any other.- A read prints one fixed form: the same design always gives the same AhaML, byte for byte.
- AhaML that a read gave, sent back unchanged, keeps the stored design as it is.
- A write sets the whole design. What the AhaML does not give takes its default; nothing of the stored design is kept.
- A write checks the whole AhaML and refuses it with the problems it finds, up to the first 100, each with its line, column and path when it has a place in the AhaML. A syntax error, and a few other problems, stop the check and come back alone (see Errors).
- A write is checked by the rules of this page also when it sends back a design it read: a design made in the editor that breaks a rule, such as one with an image on
http:, must be fixed before it can be written as AhaML.
The document
Emailis the root. It takesversionanddir.Themeis optional. When it is there, it is the first child ofEmail, and there is at most one. A write withoutThemegives the default theme, as an empty<Theme/>does. A read prints noThemewhen every setting is at its default.- The other children of
Emailare blocks. - Tag names are case-sensitive.
Versions
<Email version="1"> names the version of AhaML. A write may leave version out, which means the current version. A read always prints it.
A new tag, attribute or value is added to the current version. Only a change that breaks AhaML that works today makes a new version. When a new version comes, a write in the old version still works.
Tags
“Blocks” areParagraph, Heading, BulletList, OrderedList, Quote, Divider, Button, Image, Spacer, Section, Columns, Repeat and Footer. They, ListItem and Column each stand on their own line in the AhaML a read prints. “Text and inline tags” are text, Var, InlineImage, Br and the mark tags: Link, Color, Highlight, Bold, Italic, Underline, Strike and Code.
A
Repeat may not hold another Repeat, at any depth.
A tag that must hold content refuses to be empty. Any other tag may be empty, as an empty paragraph is: <Paragraph/>.
Attributes
An attribute that is not given takes its default. A read leaves out each attribute that is at its default, apart from a required one, which it always prints:<Heading level="1"> keeps its level. “Not set” means that the tag gives no value of its own: the theme’s value applies where the theme has one, as for a font or a button color, and otherwise there is none, as for an alt or a fallback.
The dir of a block is ltr, rtl or auto, and its default is the dir of Email. So a right-to-left design gives dir="rtl" on Email, and a read prints dir on another tag only when its direction differs.
Email
Paragraph
Paragraph also takes the rule attributes showIf, hideIf, operator and value (see Show-if and hide-if).
Heading
Heading also takes the rule attributes showIf, hideIf, operator and value (see Show-if and hide-if).
BulletList
BulletList also takes the rule attributes showIf, hideIf, operator and value (see Show-if and hide-if).
OrderedList
OrderedList also takes the rule attributes showIf, hideIf, operator and value (see Show-if and hide-if).
ListItem
Quote
Divider
Divider also takes the rule attributes showIf, hideIf, operator and value (see Show-if and hide-if).
Button
Button also takes the rule attributes showIf, hideIf, operator and value (see Show-if and hide-if).
Image
Image also takes the rule attributes showIf, hideIf, operator and value (see Show-if and hide-if).
Spacer
Spacer also takes the rule attributes showIf, hideIf, operator and value (see Show-if and hide-if).
Section
Section also takes the rule attributes showIf, hideIf, operator and value (see Show-if and hide-if).
Columns
Columns also takes the rule attributes showIf, hideIf, operator and value (see Show-if and hide-if).
Column
Repeat
Repeat also takes the rule attributes showIf, hideIf, operator and value (see Show-if and hide-if).
Footer
Var
InlineImage
Br
Link
Color
Highlight
Bold
Bold takes no attributes.
Italic
Italic takes no attributes.
Underline
Underline takes no attributes.
Strike
Strike takes no attributes.
Code
Code takes no attributes.
The theme
Theme sets the look of the whole email. A setting it does not give takes its default.
fontFamily, fontFallback, webFontUrl and webFontFormat are one group:
- A
Themewith none of them has the default font. - A
Themewith any of them needsfontFamilyandfontFallback, andwebFontUrlandwebFontFormatcome together or not at all. - A font with no web font file uses the font installed on the reader’s device, or the fallback.
Text and whitespace
Text follows the whitespace rules of HTML:- Each run of spaces, tabs and newlines is one space, also across the edge of a mark tag.
- Spaces at the start and the end of a block’s text are removed, and so are spaces just before and just after a
<Br/>. <Br/>is the only line break.
Link, Color, Highlight, Bold, Italic, Underline, Strike, Code, from outer to inner. A mark may not stand inside the same mark, such as Bold inside Bold. Code takes no other mark. A mark tag with no text adds nothing.
Var, an InlineImage or a <Br/> are kept in the design, as the editor keeps them, but the email draws the value, the image or the line break without them. To make a value bold or a link, mark the text around it. To link an inline image, give it externalLink.
Variables
<Var name="first_name" fallback="there"/> is the only way to write a variable in text. It is filled in when the email is sent, and shows its fallback when the variable has no value.
{{ name }} written as plain text stays plain text, and a write gives the warning jinja_in_text. The editor shows it as text. It is still filled in when the email is sent, unless it holds a character that HTML escapes (', ", <, > or &), which breaks it.
These are refused as text_not_allowed_here, because they would break the email when it is sent:
- text that ends with
{just before aVar. Put a space between them. - a whole
{{ }}or{% %}pair across a change of marks, anInlineImageor a<Br/>. - a
{{or{%that is not closed in its own text, before the end of its block and before the nextVar.
fallback may not hold {{, {% or {#, and may not end with {.
URLs take variables as {{ name }} pieces, as in https://example.com/orders/{{ order_id }} (see URLs).
Show-if and hide-if
The blocks that take a rule show or hide by the value of a variable when the email is sent:- A tag takes
showIforhideIf, not both. valueis required for every operator exceptis_trueandis_false, and refused for those two. An emptyvalue=""is a value.- For
greater_thanandless_than,valueis a number, such as5or1.5. An empty value is 0. operatororvaluewithoutshowIforhideIfis refused.
Repeated blocks
<Repeat each="items"> repeats its blocks once for each item of the list items.
Inside a Repeat, a name such as <Var name="title"/> reads the item’s title first, and the top-level title when the item has none. In a list of plain values, not objects, <Var name="value"/> is the item.
Values
Names
Var name, showIf, hideIf, each and the names in URL pieces start with an ASCII letter (a to z, A to Z) or _, and hold only ASCII letters, the digits 0 to 9 and _: first_name, order_id, _total. Letters of other languages are refused in names.
URLs
Linkhref,ButtonurlandexternalLinktakehttp:,https:,mailto:andtel:URLs.Buttonurlmay also be empty, which draws the button with an empty link, as the editor does for a button with no URL.externalLinkmay be empty, for no link.ImagesrcandInlineImagesrctakehttps:URLs only. They may be empty, for the editor’s image placeholder.webFontUrltakes anhttps:URL with none of',",(,),<,>,\,;,{,}, spaces or control characters.- A URL may hold
{{ name }}pieces, one name in each, or be one{{ name }}alone.{{ }}holds no filters or other template syntax. - Relative URLs, other schemes such as
javascript:anddata:, a URL with a space at its start or end, and a URL that holds a control character, also inside a{{ }}piece, are refused. So is a{{ }}piece that holds a line or paragraph separator (U+2028, U+2029). - Write
&in a URL as&, as everywhere in XML.
Colors
A color is#rgb, #rgba, #rrggbb, #rrggbbaa, rgb(r, g, b), rgba(r, g, b, a) or transparent. Section backgroundColor may also be empty, for no background. A read prints a color as it is stored.
Sizes
Sizes are plain numbers with no unit, and mean pixels.Column width is a percent of the row. Image width takes a percent such as 50%, a number of pixels or auto.
Font names
Unlike a name, afontFamily takes letters of any language. It holds letters, digits, spaces, - and _ only, and starts with a letter or digit, as in Open Sans or Noto Sans JP. A fontFallback is one of the listed fonts.
Escaping, entities and comments
- In text, write
&as&and<as<. In attribute values, write&as&,<as<and"as". - The five XML entities (
<,>,&,",') and numeric references such as work. Other named entities, such as , and<!DOCTYPE>are refused. - An XML declaration,
<?xml version="1.0"?>, may stand at the start, and is ignored. A declaration of another version, such as 1.1, issyntax. Other processing instructions are refused. - AhaML that holds a lone UTF-16 surrogate, half of a pair with no other half, is
syntaxat that place. - Comments are allowed, and dropped.
What a read prints
A read prints the stored design as the email draws it. So some designs come back in another form, which draws the same email, apart from the margins and the boxes noted below:- Text comes back by the rules of Text and whitespace.
- A
Colormark with no color comes back as the paragraph color,#374151. AHighlightwith no color, an empty color and an image with no source draw nothing, and are left out. - A theme setting the stored design lacks comes back at the value the email is drawn with.
- A section padding or margin, or a border width or radius, that the editor keeps as one value for all four sides comes back with all four sides at that value.
- A setting an older editor stored that the email does not use, such as a color on a list item, is left out.
- A paragraph, heading or footer whose text draws nothing, such as spaces alone, is left out. An empty paragraph, which draws a blank line, stays. When the only text is characters XML cannot hold and one of them draws a line, the block comes back as one line: an empty paragraph, or a heading or footer holding a
<Br/>. - A rule whose result is known before the email is sent, such as one on a name that is not valid, is left out when it always shows its block, and the block is left out with it when it always hides it. A
Repeatwhose list name is not a valid name is left out with its blocks. - A rule whose action is neither show nor hide is left out, as the email always shows its block. A
valuestored withis_trueoris_falseis left out, as those operators do not read it. - Characters XML cannot hold, such as U+000B, are left out of text and attribute values. Most of them draw a small box in the email, which the read-back email lacks. U+0000 draws nothing, and U+000B and U+000C draw one only after text in a mark.
- A
{{ }}piece of a URL that the editor stored with the variable’s label comes back with the name alone, as{{ name }}.
Spacer, a Columns or a Section with no bottom margin.
A design made by an older editor comes back with every setting it lacks at its default, as the editor fills them when it opens the design. This is the one case where an edit can change the sent email beyond what the edit itself changes: a write stores the filled settings, as the next save in the dashboard would. AhaML sent back unchanged keeps the stored design.
A design AhaML cannot show reads with markup null and markup_error set:
no_design: the design has no blocks yet. A write withmarkupmakes one.html_block: the design holds an HTML block. Remove it in the dashboard to work on the design as AhaML.unmapped_block: the design holds a block that AhaML has no tag for.bad_stored_value: the design holds a value that AhaML cannot write. The message names the tag and the attribute. A section whose padding or border width the editor keeps as one value, but whose left and right sides still differ from it, gives this too: those sides size the section’s content.renderer_unavailable: AhaML could not be made just now. Try the read again.rate_limited: the account is over the limit of AhaML reads and writes. Try the read again later.
Limits
Each
Link, Color and Highlight is copied onto every text and line break inside it, so a long href around many lines makes a large design.
Errors
A refused write answers 400 with the problems in document order, up to the first 100, and a message that says how many there are:codeandmessageare always there.line,columnandpathare there when the problem has a place in the AhaML:too_large,rendered_too_largeandbroken_template_syntaxhave none.lineandcolumnstart at 1 and count characters. They give the start of the problem: the<of a tag, the first letter of an attribute or the first character of a text.pathnames each tag with its place among the siblings of the same tag.hintis there when a fix is known.- AhaML that is not well-formed XML gives one problem, where reading stopped. A problem with a limit, a
<!DOCTYPE>, an entity or a processing instruction also comes back alone.
A write that is taken can still give warnings, in
markup_warnings, in the same form with no hint:

