Skip to content

MJML Email Templates — Responsive Email Design Without Node.js

Granit.Templating.Mjml

MJML is a markup language that compiles to email-client-safe HTML. Instead of writing table-based layouts with inline styles by hand, template authors write semantic MJML components (<mj-section>, <mj-column>, <mj-text>, <mj-button>) and the compiler handles the rest.

Granit.Templating.Mjml integrates Mjml.Net — a native .NET MJML compiler with 100% feature parity and 10x performance vs the Node.js original. No Node.js runtime required.

[DependsOn(
typeof(GranitTemplatingScribanModule),
typeof(GranitTemplatingMjmlModule))] // Add this
public class AppModule : GranitModule { }

Or without the module system:

services.AddGranitTemplatingWithScriban();
services.AddGranitTemplatingWithMjml();

The MjmlTransformer (Order=100) runs in the post-render pipeline. It auto-detects MJML content by checking if the rendered output starts with <mjml>. Plain HTML templates pass through unchanged — backward compatibility is guaranteed.

Scriban render → "<mjml>..{{ model.name }}..</mjml>" → MjmlTransformer → email-safe HTML
Scriban render → "<html>..{{ model.name }}..</html>" → MjmlTransformer → unchanged (passthrough)
<mjml>
<mj-body>
<mj-section background-color="#1a1a2e" padding="24px 32px">
<mj-column>
<mj-text align="center" color="#ffffff" font-size="20px">
{{ app.name }}
</mj-text>
</mj-column>
</mj-section>
<mj-section background-color="#ffffff" padding="32px">
<mj-column>
<mj-text>Hello {{ model.patient_name }},</mj-text>
<mj-text>Your appointment is scheduled for
{{ model.appointment_date | to_user_time }}.</mj-text>
<mj-button href="{{ app.base_url }}/appointments/{{ model.id }}">
View appointment
</mj-button>
</mj-column>
</mj-section>
<mj-section background-color="#f3f4f6" padding="20px 32px">
<mj-column>
<mj-text align="center" font-size="13px" color="#4b5563">
&copy; {{ now.year }} {{ app.name }}
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>

The MJML compiler produces:

  • Table-based layout (compatible with all email clients)
  • Inline CSS styles (Gmail strips <style> blocks)
  • Outlook conditional comments (<!--[if mso]>)
  • Responsive design with @media queries in <style> (for clients that support it)

Scriban variables ({{ model.* }}, {{ app.* }}, {{ now.* }}) are preserved through the MJML compilation — Scriban runs first, MJML compiles the result.