Designs

MJML Designs

Create responsive email designs for Clerk.io campaigns using MJML.

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.