contentRevisionId, edit the LMX in Git, and send the update back with
revision protection.
Campaign content examples
Create campaigns, query themes and components, and update email messages with LMX.
Update email message API
Send serialized LMX through the Content API for campaign, workflow, and transactional email messages.
Create campaign API
Create a draft campaign and email message before adding LMX content.
Loops CLI
Use the CLI for local setup, authentication, and Content API workflows.
Themes API
Find theme IDs and style attributes for the LMX
<Style /> tag.Components API
Find reusable components for the LMX
<Component /> tag.LMX examples
Here are some examples of how to use LMX tags to create emails.Core rules
- LMX is XML, not HTML or Markdown. Tags are case-sensitive (for example,
<Paragraph>, not<paragraph>). - LMX is not MJML. Do not use MJML tags (such as
<mj-text>). - Do not use the editor’s dynamic tag syntax in LMX documents (for example
{EVENT_PROPERTY:orderId}or{DATA_VARIABLE:resetLink}or unprefixed{firstName}). Use{contact.*}for contact properties,{event.*}for workflow event properties, and{data.*}for transactional email data variables. - A document is a sequence of top-level block tags, optionally with one top-level
<Style />. - Text is not allowed at the top level. Wrap top-level text in a block tag like
<Paragraph>. - Top-level inline tags and variables are invalid.
- Required attributes must be present (for example,
<Image />requiressrc). - Self-closing tags must end with
/>(for example,<Image ... />,<Br />). - Unknown tags are rejected. Use only documented attributes in public LMX examples.
- Attribute values are quoted strings. Numbers and booleans are passed as strings.
- Whitespace in text content collapses like HTML, except inside
<CodeBlock>, where interior whitespace is preserved. Use<Br />for explicit line breaks. - Whitespace between block tags is ignored, so pretty-printing is safe.
- Escape text and attributes as needed (
<,&,").
Emails automatically have a footer appended, so you don’t need to include your address or unsubscribe link at the bottom of your LMX content.
Source control
You can keep LMX files alongside the rest of your application code (for example, in anemails/ directory in your repo) and sync them with Loops through the CLI or Content API. LMX is plain text, so it diffs and reviews cleanly in pull requests.
A typical Git-based workflow:
- Pull the latest content before editing. Run
loops email-messages get <id>to fetch the current LMX andcontentRevisionId, and save the LMX to a file in your repo. - Edit the
.lmxfile locally and commit your changes. - Push back to Loops with
loops email-messages update <id>, passing--expected-revision-idset to thecontentRevisionIdfrom step 1 and--lmx-file ./path/to/email.lmx. The update is rejected if the email was edited in Loops in the meantime, so you don’t overwrite concurrent changes. - Resolve conflicts by pulling again with
loops email-messages getand re-applying your edits. Use--forceinstead of--expected-revision-idonly when you intentionally want to overwrite the server copy.
Draft status for updates to apply. The same flow works through the Update email message API if you prefer to call the API directly from CI.
Tags
Top-level tags define the structure of an email.
Child-only structural tags are valid only inside their parent tags.
Tag reference
Document styles
Use the optional top-level<Style /> tag to style the email. Use this to apply a theme to your email and add custom styles, just like you would in the Style panel in the editor.
Use a combination of themeId and other attributes to bring in defaults while also overriding specific styles. You can also style individual elements in your email using tag attributes (for example, <Button bgColor="#000000">Open account</Button> overrides <Style buttonBodyColor="#CCCCCC" /> for that button).
Styles applied in <Style /> will apply to all content in the email. For example, defining buttonBodyColor in the <Style /> tag will apply to all buttons in the email.
Self-closing tag. At most one per document.
Headings
Use<H1>, <H2>, and <H3> for headings.
Paragraphs
Use<Paragraph> for paragraph text.
Quotes
Use<Quote> for blockquote content.
Code blocks
Use<CodeBlock> for code blocks.
Note: inline tags and variable parsing are disabled inside code blocks.
Buttons
Use<Button> for buttons.
Button content may contain plain text and dynamic content variables such as {contact.firstName}, {event.orderId}, or {data.firstName}. Inline formatting tags are not allowed inside buttons.
Images
Use<Image /> for images.
src must be a Loops-hosted image. Upload images using the Uploads API endpoints and use the returned URL as src.
For an externally hosted dynamic image, see Dynamic images below.
Dynamic or externally hosted images
If you want a per-recipient image URL, setdynamicSrc to a URL containing a variable (for example, {contact.avatarUrl}, {event.avatarUrl}, or {data.avatarUrl}). The image URL should come from a contact property, event property, or data variable.
You must also set src to a Loops-hosted static placeholder (this image renders in the Loops editor). When the email is generated for each recipient, the resolved dynamicSrc value is injected as the image src.
For images hosted on an external host, set src to a Loops-hosted static placeholder, then set dynamicSrc to the external image URL.
Dynamic image URLs must be publicly accessible and use an email-safe image extension like .jpg or .png.
More about dynamic images.
Dividers
Use<Divider /> for divider lines.
Self-closing tag.
Line breaks
Use<Br /> for line breaks in inline contexts. It is not valid at the top level.
Self-closing tag.
Lists
Use<OrderedList> or <UnorderedList> with <ListItem> children only.
These tags must contain at least one <ListItem>.
List items
Use<ListItem> for list items.
Only valid inside <OrderedList> and <UnorderedList> tags.
Can only contain inline content.
Columns
Use<Columns> for columns.
Must contain between two and four <ColumnItem> children. No other tags are allowed.
Nested <Columns> tags are not supported.
Use
<ColumnItem> for column content containers.
Only valid inside a <Columns> tag.
No attributes.
Components
Use<Component> to add a component.
Can be self-closing, or can contain block elements, which will override the component’s default content.
Children are block tags. Nested <Component> tags are not supported.
Sections
Use<Section> to group related block content in a shared clickable or styled container.
Children are block tags. Nested <Section> tags are not supported.
Conditional sections
Use theif attribute to show a section only for some recipients. if must be a single, prefixed variable reference, for example if="{contact.plan}", if="{event.orderId}", or if="{data.isPromo}".
By default the section shows when that variable has a value (not_empty). Add ifOperation to compare it, and ifValue when the operator needs something to compare against:
ifOperation values (which apply depends on the variable’s type; data variables are always text):
equal, not_equal, contains, not_contains, numeric_equal, numeric_not_equal, greater_than, and less_than require an ifValue. The presence operators (empty, not_empty) and boolean operators (true, false) ignore it.
The section is included when the condition is true. A missing condition variable never fails the send, it just hides the section, so condition variables are optional at send time.
Notes:
ifmust be a single variable. Expressions and multiple variables (for exampleif="{data.plan} == 'pro'") are not supported; useifOperation/ifValueinstead.- Unprefixed values such as
if="{plan}"are invalid. Use{contact.},{event.}, or{data.}. ifOperationandifValueare ignored without anifvariable, and validation rejects an unsupported operator or a value-requiring operator with noifValue.- Content inside a hidden section is dropped entirely, including any variables it contains.
Icons
Use<Icons> for lists of icons.
Children must be <Icon /> with no other tags allowed. At least one <Icon /> child is required (maximum 100).
Use
<Icon /> for individual icons.
Self-closing tag. Only valid inside an <Icons> tag.
Inline tags
Inline tags format text content inside block elements.
Inline tags are valid only inside inline-content blocks such as
<Paragraph>, headings, <Quote>, and <ListItem>.
Inline tag attributes
Dynamic content
You can add dynamic content by using a prefixed tag syntax, for example{contact.firstName} or {event.orderTotal}.
Dynamic tags can be added to email message lmx bodies and also in other fields like subject, previewText, fromName, fromEmail and replyToEmail.
Campaign content uses contact properties. Workflow email content uses contact and event properties. Transactional email content uses data variables.
Contact properties
Use{contact.<apiName>} for campaign and workflow personalization, for example {contact.firstName}.
Default contact properties include firstName, lastName, email, notes, source, userGroup, userId, subscribed, and createdAt.
Event properties
Use{event.<apiName>} in workflow emails. Event properties come from the event that triggered the workflow.
Property names must match the API names defined in Events settings. You must configure fallbacks for event properties in your emails, either with eventPropertiesFallbacks when using the Content API, or in the editor side panel.
Send event payload
- Only valid in workflow email LMX. Using
{event.*}in campaign or transactional emails fails validation. - Names are case-sensitive and must match the event property API name.
- Configure fallback values in the Loops editor or via
eventPropertiesFallbacksin the Content API.
Data variables
Use{data.<variableName>} in transactional email LMX. The name after data. must match a key in the dataVariables object when sending the email.
LMX content
Send transactional email payload
- Names are case-sensitive.
{data.resetLink}and{data.ResetLink}are different variables. - Names may contain only letters, numbers, underscores, and dashes.
- API values may be
stringornumber. - Non-optional variables must be present in
dataVariablesor the send fails. - Optional variables may be omitted from the request or sent as an empty string
""(nulldoes not work). Mark a data variable as optional in the Loops editor.
Where variables are allowed
Variables are allowed in:- Inline content (headings, paragraphs, quotes, list items, and inline tags)
- Button text
- Supported dynamic attributes:
Buttonhref,Linkhref,Imagealt,Imagehref,ImagedynamicSrc, andSectionhref Sectionif, which takes a whole variable reference and no surrounding text (see conditional sections)
- At the top level
- Inside
<CodeBlock>(braces are literal text) - In unsupported attributes such as
Imagesrc
Variables in attributes
Only these attribute values may include variables:ButtonhrefImagealt,href, anddynamicSrcLinkhrefSectionhrefSectionif(the whole value must be a single prefixed variable)
Image src must be a Loops-hosted static URL with no variables. User-hosted URLs are not supported in src. Putting a variable like {contact.avatarUrl} in src is not supported and validation returns unsupported_dynamic_attr. When the image URL should vary per recipient (including externally hosted URLs), set dynamicSrc to a variable such as {contact.avatarUrl}, {event.avatarUrl}, or {data.avatarUrl} and keep src as a Loops-hosted placeholder; at send time, the resolved dynamicSrc value replaces src.
Fallback values
LMX does not support inline fallback syntax in variable references. These forms are not valid in LMX:{contact.firstName|there}, {event.orderTotal|0}, {contact.firstName:there}, {contact.firstName ?? "there"}, and fallback="there" on variable attributes.
Use plain variable references in LMX (for example, {contact.firstName}, {event.orderTotal}, or {data.firstName}). For contact and event properties, configure fallback values through the Loops editor or Content API. For transactional emails, mark a data variable as optional in the editor instead of inventing inline fallback syntax.
Nesting rules
LMX validates structure, not just syntax. A few key rules:- Root-level content must use supported top-level block tags (
H1,H2,H3,Paragraph,Quote,CodeBlock,Button,Image,Divider,OrderedList,UnorderedList,Columns,Component,Section,Icons,Style). - Inline tags cannot appear at the top level.
<ListItem>and<Quote>only accept inline content.<Columns>can only contain<ColumnItem>tags.<ColumnItem>can contain block content, but not nested<Columns>or other<ColumnItem>tags.<Style />is metadata and is only allowed at the top level.

