MJML Designs
MJML is a markup language designed specifically for building responsive emails. Instead of wrestling with HTML tables and inline styles to get a consistent result across Gmail, Outlook, and Apple Mail, you write clean semantic markup and MJML compiles it into battle-tested HTML that works everywhere.
Clerk.io’s email campaign editor includes a built-in MJML editor, making it the recommended approach for creating email designs that look great on any device or client.
When to Use #
Use MJML when creating or editing designs for Newsletters or Triggers in Clerk.io Email.
For on-site designs — Search, Recommendations, and the display layer of Email Embeds — use the Design Editor or Code Designs instead. MJML is only relevant for the email sending layer.
Basic Structure #
Every MJML document wraps content in <mjml>, with an optional <mj-head> for global styles and an <mj-body> for the visible layout:
<mjml>
<mj-head>
<!-- Global styles and attributes -->
</mj-head>
<mj-body>
<mj-section>
<mj-column>
<!-- Your content here -->
</mj-column>
</mj-section>
</mj-body>
</mjml>
The key building blocks are:
<mj-section>— a full-width row<mj-column>— a column within a row; use multiple for side-by-side layouts<mj-text>— a block of text<mj-image>— a responsive image<mj-button>— a styled call-to-action button<mj-divider>— a horizontal rule<mj-spacer>— adds vertical space between sections
The MJML documentation covers the full list of available components.
Product Variables #
Clerk.io’s
Liquid template language works inside MJML the same way it does in any other design. Wrap product data in {{ }} to render it.
<mj-section>
<mj-column width="35%">
<mj-image src="{{ product.image }}" href="{{ product.url }}" />
</mj-column>
<mj-column width="65%">
<mj-text font-size="16px" font-weight="bold">
{{ product.name }}
</mj-text>
<mj-text font-size="14px" color="#666666">
{{ product.price | money }}
</mj-text>
<mj-button href="{{ product.url }}" background-color="#000000">
Shop now
</mj-button>
</mj-column>
</mj-section>
Product Loops #
Use a {% for %} loop to repeat a layout block for each recommended product. The loop renders one section per product in the list returned by Clerk.io.
{% for product in products %}
<mj-section background-color="#ffffff" padding="10px 20px">
<mj-column width="35%">
<mj-image src="{{ product.image }}" href="{{ product.url }}" border-radius="4px" />
</mj-column>
<mj-column width="65%">
<mj-text font-size="16px" font-weight="bold">
{{ product.name }}
</mj-text>
<mj-text font-size="18px" font-weight="bold">
{{ product.price | money }}
</mj-text>
<mj-button href="{{ product.url }}" background-color="#000000">
Shop now
</mj-button>
</mj-column>
</mj-section>
{% endfor %}
Conditional Logic #
Use {% if %} to show or hide parts of the design based on product data. A common use case is showing the original price when a product is on sale:
{% for product in products %}
<mj-section background-color="#ffffff">
<mj-column>
<mj-image src="{{ product.image }}" href="{{ product.url }}" />
<mj-text font-size="15px" font-weight="bold">
{{ product.name }}
</mj-text>
{% if product.price < product.list_price %}
<mj-text font-size="13px" color="#999999">
<s>{{ product.list_price | money }}</s>
</mj-text>
{% endif %}
<mj-text font-size="18px" font-weight="bold" color="#cc0000">
{{ product.price | money }}
</mj-text>
</mj-column>
</mj-section>
{% endfor %}
You can also use {% else %} to show a fallback when the condition is not met:
{% if product.in_stock %}
<mj-button href="{{ product.url }}" background-color="#000000">
Shop now
</mj-button>
{% else %}
<mj-text font-size="13px" color="#999999" align="center">
Out of stock
</mj-text>
{% endif %}
Global Styles #
Use <mj-attributes> inside <mj-head> to set default styles that apply across the entire design. This avoids repeating the same font or colour on every component.
<mj-head>
<mj-attributes>
<mj-all font-family="Arial, sans-serif" />
<mj-text font-size="14px" color="#333333" line-height="1.6" />
<mj-button background-color="#000000" color="#ffffff" border-radius="4px" />
</mj-attributes>
</mj-head>
Any attribute set here can still be overridden on individual components.
Multi-Column Layout #
Use multiple <mj-column> elements inside a single <mj-section> to create side-by-side layouts. Set explicit width values to control the proportions.
A two-column product grid:
{% for product in products %}
<mj-section background-color="#ffffff" padding="10px">
<mj-column width="50%">
<mj-image src="{{ product.image }}" href="{{ product.url }}" />
<mj-text font-size="14px" font-weight="bold" align="center">
{{ product.name }}
</mj-text>
<mj-text font-size="13px" color="#666666" align="center">
{{ product.price | money }}
</mj-text>
<mj-button href="{{ product.url }}">Shop now</mj-button>
</mj-column>
</mj-section>
{% endfor %}
For a true two-per-row grid, loop with loop.index and group products manually, or use a single section with two hardcoded columns referencing products[0] and products[1].
Complete Example #
Below is a full newsletter design with global styles, a header, product rows with sale price logic, and a footer.
<mjml>
<mj-head>
<mj-attributes>
<mj-all font-family="Arial, sans-serif" />
<mj-text font-size="14px" color="#333333" line-height="1.6" />
<mj-button background-color="#000000" color="#ffffff" border-radius="4px" font-size="13px" />
</mj-attributes>
</mj-head>
<mj-body background-color="#f4f4f4">
<!-- Header -->
<mj-section background-color="#ffffff" padding="24px 20px 12px">
<mj-column>
<mj-text font-size="24px" font-weight="bold" align="center">
Picked for you
</mj-text>
<mj-divider border-color="#eeeeee" padding="12px 0 0" />
</mj-column>
</mj-section>
<!-- Product rows -->
{% for product in products %}
<mj-section background-color="#ffffff" padding="10px 20px">
<mj-column width="35%">
<mj-image
src="{{ product.image }}"
href="{{ product.url }}"
border-radius="4px" />
</mj-column>
<mj-column width="65%">
<mj-text font-size="15px" font-weight="bold">
<a href="{{ product.url }}" style="color:#333333; text-decoration:none;">
{{ product.name }}
</a>
</mj-text>
{% if product.price < product.list_price %}
<mj-text font-size="13px" color="#999999">
<s>{{ product.list_price | money }}</s>
</mj-text>
{% endif %}
<mj-text font-size="18px" font-weight="bold" color="#000000">
{{ product.price | money }}
</mj-text>
{% if product.in_stock %}
<mj-button href="{{ product.url }}" padding="10px 0 0">
Shop now
</mj-button>
{% else %}
<mj-text font-size="13px" color="#999999">
Out of stock
</mj-text>
{% endif %}
</mj-column>
</mj-section>
<mj-section background-color="#ffffff" padding="0 20px">
<mj-column>
<mj-divider border-color="#eeeeee" padding="0" />
</mj-column>
</mj-section>
{% endfor %}
<!-- Footer -->
<mj-section background-color="#f4f4f4" padding="20px">
<mj-column>
<mj-text font-size="12px" color="#999999" align="center">
You are receiving this email because you signed up for updates.
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
Previewing and Testing #
Clerk.io’s MJML editor compiles the markup and shows a live preview as you edit. To test across email clients before sending, paste the compiled HTML into a tool like Litmus or Email on Acid.
For the full component reference, see the MJML documentation.