MJML
Build responsive emails with MJML and Keplars Mail Service
MJML is a markup language designed to simplify responsive email coding. It uses semantic tags that automatically compile to responsive HTML that works across all email clients.
Why MJML?
- Responsive by Default: Components automatically adapt to mobile and desktop screens
- Semantic Syntax: Clean, intuitive tags that describe what you're building
- Email Client Compatibility: Generated HTML works on Gmail, Outlook, Apple Mail, and more
- Component Library: Pre-built components for common email patterns
- Simple Learning Curve: If you know HTML, you can learn MJML in minutes
Installation
Install MJML in your Node.js project:
npm install mjmlBasic Structure
Every MJML email follows this structure:
<mjml>
<mj-head>
<!-- Metadata and styles -->
</mj-head>
<mj-body>
<!-- Email content -->
</mj-body>
</mjml>Core Components
mj-section
Represents a row in your email layout. Sections stack vertically.
<mj-section>
<!-- Columns go here -->
</mj-section>mj-column
Creates columns within a section. Columns automatically stack on mobile devices.
<mj-section>
<mj-column>
<!-- Content goes here -->
</mj-column>
<mj-column>
<!-- Content goes here -->
</mj-column>
</mj-section>mj-text
Displays text content with HTML formatting support.
<mj-text font-size="20px" color="#333">
Hello World
</mj-text>mj-button
Creates a clickable call-to-action button.
<mj-button href="https://example.com" background-color="#007bff">
Get Started
</mj-button>mj-image
Displays responsive images that scale properly.
<mj-image width="200px" src="https://example.com/logo.png" />Basic Example
Here's a simple welcome email:
<mjml>
<mj-body>
<mj-section background-color="#f0f0f0" padding="20px">
<mj-column>
<mj-image width="150px" src="https://example.com/logo.png" />
</mj-column>
</mj-section>
<mj-section background-color="#ffffff" padding="20px">
<mj-column>
<mj-text font-size="24px" font-weight="bold" color="#333">
Welcome to Our Service!
</mj-text>
<mj-text font-size="16px" color="#666" line-height="24px">
Thanks for signing up. We're excited to have you on board.
</mj-text>
<mj-button href="https://example.com/get-started" background-color="#007bff">
Get Started
</mj-button>
</mj-column>
</mj-section>
</mj-body>
</mjml>Using with Node.js
Compile MJML to HTML and send with Keplars Mail Service:
import mjml2html from 'mjml';
const mjmlTemplate = `
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text font-size="20px">
Hello {{name}}!
</mj-text>
<mj-text>
Your order #{{orderId}} has been confirmed.
</mj-text>
<mj-button href="{{trackingUrl}}">
Track Your Order
</mj-button>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`;
// Replace variables
const personalizedMjml = mjmlTemplate
.replace('{{name}}', 'John')
.replace('{{orderId}}', '12345')
.replace('{{trackingUrl}}', 'https://example.com/track/12345');
// Compile MJML to HTML
const { html } = mjml2html(personalizedMjml);
// Send with Keplars Mail Service
await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: ['[email protected]'],
subject: 'Order Confirmation',
body: html
})
});Using with Template Files
Store MJML templates as files and load them dynamically:
import { readFile } from 'fs/promises';
import mjml2html from 'mjml';
async function sendOrderConfirmation(orderData: any) {
// Load MJML template
const mjmlTemplate = await readFile('./templates/order-confirmation.mjml', 'utf-8');
// Replace variables
let personalizedMjml = mjmlTemplate;
for (const [key, value] of Object.entries(orderData)) {
personalizedMjml = personalizedMjml.replace(
new RegExp(`{{${key}}}`, 'g'),
String(value)
);
}
// Compile to HTML
const { html } = mjml2html(personalizedMjml);
// Send email
const response = await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: [orderData.email],
subject: `Order #${orderData.orderId} Confirmed`,
body: html
})
});
return response.json();
}Express.js Integration
Create an email service with MJML templates:
import express from 'express';
import mjml2html from 'mjml';
const app = express();
app.post('/send-welcome-email', async (req, res) => {
const { email, name } = req.body;
const mjmlTemplate = `
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text font-size="24px">Welcome ${name}!</mj-text>
<mj-text>Thanks for joining our community.</mj-text>
<mj-button href="https://example.com/dashboard">
Go to Dashboard
</mj-button>
</mj-column>
</mj-section>
</mj-body>
</mjml>
`;
const { html } = mjml2html(mjmlTemplate);
const emailResponse = await fetch('https://api.keplars.com/api/v1/send-email/async', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.KEPLERS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: [email],
subject: 'Welcome!',
body: html
})
});
const result = await emailResponse.json();
res.json({ success: true, messageId: result.messageId });
});Advanced Features
Custom Fonts
Add custom fonts in the mj-head section:
<mjml>
<mj-head>
<mj-font name="Roboto" href="https://fonts.googleapis.com/css?family=Roboto" />
</mj-head>
<mj-body>
<mj-section>
<mj-column>
<mj-text font-family="Roboto">
Text with custom font
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>Styling
Use mj-attributes for global styles:
<mjml>
<mj-head>
<mj-attributes>
<mj-text font-size="14px" color="#333" />
<mj-button background-color="#007bff" color="#fff" />
</mj-attributes>
</mj-head>
<mj-body>
<!-- Your content inherits these styles -->
</mj-body>
</mjml>Conditional Content
Use mj-include for reusable components:
<!-- header.mjml -->
<mj-section>
<mj-column>
<mj-image src="logo.png" />
</mj-column>
</mj-section>
<!-- main email -->
<mjml>
<mj-body>
<mj-include path="./header.mjml" />
<!-- Rest of email -->
</mj-body>
</mjml>Development Workflow
CLI Preview
Preview MJML files in the browser:
mjml watch template.mjml --output output.htmlVS Code Extension
Install the MJML extension for syntax highlighting and live preview in VS Code.
Common Email Patterns
Two-Column Layout
<mj-section>
<mj-column width="50%">
<mj-image src="product1.jpg" />
<mj-text>Product 1</mj-text>
</mj-column>
<mj-column width="50%">
<mj-image src="product2.jpg" />
<mj-text>Product 2</mj-text>
</mj-column>
</mj-section>Hero Section
<mj-section background-url="hero.jpg" background-size="cover" padding="100px 0">
<mj-column>
<mj-text font-size="40px" color="#fff" align="center">
Big Announcement
</mj-text>
<mj-button background-color="#fff" color="#000">
Learn More
</mj-button>
</mj-column>
</mj-section>Footer
<mj-section background-color="#f0f0f0" padding="20px">
<mj-column>
<mj-text font-size="12px" color="#666" align="center">
© 2025 Your Company. All rights reserved.
</mj-text>
<mj-text font-size="12px" align="center">
<a href="{{unsubscribeUrl}}">Unsubscribe</a>
</mj-text>
</mj-column>
</mj-section>MJML automatically handles responsive design. Two-column layouts stack vertically on mobile devices without any extra code.
Best Practices
Use Semantic Components: Use mj-button instead of styled mj-text links for better email client compatibility.
Mobile-First Thinking: Test how your layout stacks on mobile. MJML handles responsiveness automatically, but preview your emails.
Keep It Simple: Email clients have limited CSS support. MJML abstracts this away, but avoid overly complex layouts.
Test Across Clients: Use services like Litmus or Email on Acid to test your emails across different email clients.
Resources
Official Documentation: https://documentation.mjml.io
Component Reference: https://documentation.mjml.io/#components
MJML Playground: https://mjml.io/try-it-live
VS Code Extension: Search "MJML" in VS Code Extensions
Next Steps
- React Email - Component-based email templates with React
- Handlebars - Simple dynamic email templates
- Send Emails - Learn more about sending emails with Keplars