Developing and Building Transactional Email Templates (MJML)
MJML — a framework for creating email templates with responsive layout. You write components in MJML, get valid HTML with tables compatible with Outlook, Gmail, Apple Mail.
MJML Template Structure
<mjml>
<mj-head>
<mj-preview>Your order #{{ orderId }} is confirmed</mj-preview>
<mj-attributes>
<mj-all font-family="Inter, Arial, sans-serif" />
<mj-text font-size="16px" line-height="1.6" color="#374151" />
<mj-button background-color="#3b82f6" border-radius="8px"
font-size="16px" font-weight="600" />
</mj-attributes>
<mj-style>
.button-td { padding: 24px 0 !important; }
</mj-style>
</mj-head>
<mj-body background-color="#f9fafb">
<!-- Header -->
<mj-section background-color="#ffffff" padding="24px 32px 0">
<mj-column>
<mj-image src="{{ logoUrl }}" width="120px" align="left" />
</mj-column>
</mj-section>
<!-- Main content -->
<mj-section background-color="#ffffff" padding="24px 32px">
<mj-column>
<mj-text font-size="24px" font-weight="700" color="#111827">
Order #{{ orderId }} Confirmed ✓
</mj-text>
<mj-text>
Hi, {{ customerName }}! Your order has been received and sent for processing.
</mj-text>
<!-- Items -->
<mj-table>
{% for item in items %}
<tr>
<td style="padding: 8px 0; border-bottom: 1px solid #e5e7eb;">
{{ item.name }}
</td>
<td style="padding: 8px 0; border-bottom: 1px solid #e5e7eb; text-align: right; color: #6b7280;">
{{ item.quantity }} × {{ item.price }}
</td>
</tr>
{% endfor %}
<tr>
<td style="padding-top: 12px; font-weight: 700;">Total</td>
<td style="padding-top: 12px; font-weight: 700; text-align: right;">{{ total }}</td>
</tr>
</mj-table>
</mj-column>
</mj-section>
<!-- CTA -->
<mj-section background-color="#ffffff" padding="0 32px 32px">
<mj-column>
<mj-button href="{{ orderUrl }}">Track Order</mj-button>
</mj-column>
</mj-section>
<!-- Footer -->
<mj-section padding="16px 32px">
<mj-column>
<mj-text font-size="13px" color="#9ca3af" align="center">
© 2026 Example Inc. · <a href="{{ unsubscribeUrl }}">Unsubscribe</a>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
Compiling MJML to HTML
npm install mjml
import mjml2html from 'mjml';
import { readFileSync } from 'fs';
import Handlebars from 'handlebars';
function renderEmailTemplate(
templateName: string,
data: Record<string, unknown>
): string {
const mjmlTemplate = readFileSync(`templates/${templateName}.mjml`, 'utf-8');
// Compile Handlebars variables into MJML
const mjmlWithData = Handlebars.compile(mjmlTemplate)(data);
// Convert MJML → HTML
const { html, errors } = mjml2html(mjmlWithData, {
validationLevel: 'strict',
minify: true
});
if (errors.length > 0) {
throw new Error(`MJML errors: ${errors.map(e => e.formattedMessage).join('\n')}`);
}
return html;
}
// Usage
const html = renderEmailTemplate('order-confirmation', {
orderId: '12345',
customerName: 'John',
items: order.items,
total: '$100.00',
orderUrl: `https://example.com/orders/12345`
});
await sendEmail({ to: customer.email, subject: `Order #12345 Confirmed`, html });
Testing in Email Clients
- Email on Acid / Litmus — paid services for screenshots in 90+ clients
- MJML Preview (VS Code plugin) — live preview
- Mailtrap — send to test inbox
Timeline
A set of 3–5 transactional templates (order, reset password, welcome) — 3–5 days.







