Converting HTML Emails to MJML Components: Implementation Workflow
Why Migrate from Table-Based HTML to MJML
Legacy email markup depends on deeply nested <table> structures, client-specific conditional comments (<!--[if mso]>), and aggressive inline !important overrides. MJML replaces this with a declarative, component-driven abstraction layer that compiles to optimized, cross-client compatible HTML. Transitioning to Modern Email Templating Engines eliminates brittle layout inheritance, standardizes responsive breakpoints, and reduces maintenance overhead across transactional and marketing pipelines.
Pre-Conversion DOM Sanitization
Raw HTML must be normalized before MJML mapping. Unsanitized markup triggers parser failures and bloats final payloads.
- Strip Unsupported CSS: Remove
@mediaqueries targeting legacy clients (Outlook 2003/2007, Lotus Notes). MJML handles responsive breakpoints natively via<mj-column>width attributes. - Isolate Dynamic Logic: Extract template variables (
{{ }},{% %},#if) into a separate configuration file. MJML's compiler will strip unrecognized tags unless explicitly wrapped. - Flatten Table Depth: Reduce nesting to a maximum of 3 levels. Use a headless DOM parser (
cheerioorjsdom) to audit structural complexity:
node -e "const cheerio = require('cheerio'); const html = require('fs').readFileSync('legacy.html','utf8'); const $ = cheerio.load(html); console.log('Max table depth:', Math.max(...Array.from($('table')).map(el => $(el).parents('table').length)));"
- Remove Spacer Assets: Delete 1x1 transparent GIFs and empty
<td>cells used for spacing. MJML usespadding,margin, andmj-spacerfor predictable layout control.
Step-by-Step Implementation Pipeline
Step 1: Map Layout to MJML Primitives
Translate table semantics directly to MJML hierarchy. Maintain strict XML compliance.
<!-- Legacy HTML -->
<table width="100%" cellpadding="0" cellspacing="0" role="presentation" style="background:#f4f4f4;">
<tr>
<td align="center" style="padding: 24px 0;">
<table width="600" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;">
<tr><td style="padding: 20px; font-family: sans-serif;">Header Content</td></tr>
</table>
</td>
</tr>
</table>
<!-- MJML Equivalent -->
<mjml>
<mj-body background-color="#f4f4f4">
<mj-section padding="24px 0">
<mj-column width="600px" background-color="#ffffff">
<mj-text padding="20px" font-family="sans-serif">Header Content</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
Apply mj-class for reusable styling to enforce DRY principles and align with MJML Component Architecture standards.
Step 2: Handle Conditional Logic & Assets
MJML does not process server-side templating natively. Wrap dynamic blocks in <mj-raw> to bypass the compiler's XML parser:
<mj-raw>
{% if user.tier == 'enterprise' %}
</mj-raw>
<mj-section>
<mj-column>
<mj-text>Enterprise Feature Block</mj-text>
</mj-column>
</mj-section>
<mj-raw>
{% endif %}
</mj-raw>
- Images: Use absolute URLs only. Specify
widthandheightexplicitly to prevent layout shifts in Gmail/Apple Mail. - Backgrounds: Replace inline
background-imageCSS with<mj-image>or<mj-wrapper background-url="...">. MJML automatically generates VML fallbacks for Outlook.
Step 3: Compile & Optimize
Run the MJML CLI with production flags. Disable beautification to reduce whitespace overhead:
npx mjml@latest input.mjml -o output.html --config.beautify false --config.minify true
Debugging Rendering Discrepancies
Error Handling & Validation
- XML Syntax Errors: MJML compilation halts on unclosed tags or invalid attributes. Pre-validate with
xmllint:
xmllint --noout input.mjml 2>&1 | grep -i "error" && exit 1
- Parser Stripping: If dynamic tags disappear in output, verify they are strictly enclosed in
<mj-raw>. MJML v5+ enforces stricter XML parsing than v4. - CLI Version Mismatch: Pin dependencies in
package.json("mjml": "^4.15.0"). Breaking changes between v4 and v5 affectmj-styleinheritance andmj-rawplacement.
Payload & Rendering Optimization
- Size Audit: Transactional providers (SendGrid, SES, Postmark) enforce strict payload limits. Keep compiled HTML ≤ 102KB. Strip unused CSS classes post-compilation using
email-comb:
npx email-comb output.html -o optimized.html --keep-class="mj-raw"
- Outlook VML Conflicts: MJML auto-generates VML for backgrounds. If custom VML is required, disable MJML's auto-generation via
<mj-wrapper background-color="..." background-repeat="no-repeat">and inject raw VML manually inside<mj-raw>. - Regression Testing Pipeline:
# 1. Compile
mjml template.mjml -o template.html --config.minify true
# 2. Validate HTML5/Email spec
npx html-validate template.html
# 3. Diff against baseline (CI)
git diff --exit-code baseline.html template.html || echo "Rendering drift detected"
# 4. Push to rendering QA (Litmus/Email on Acid API)
curl -X POST https://api.litmus.com/v1/tests \
-H "Authorization: Bearer $LITMUS_TOKEN" \
-F "html_file=@template.html"
Implement automated snapshot testing in CI/CD to catch client-specific breaks before deployment. Maintain strict version control for .mjml source files and compiled .html outputs to ensure transactional delivery stability.