MjmlProcessor 1.0.2

dotnet add package MjmlProcessor --version 1.0.2
                    
NuGet\Install-Package MjmlProcessor -Version 1.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MjmlProcessor" Version="1.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MjmlProcessor" Version="1.0.2" />
                    
Directory.Packages.props
<PackageReference Include="MjmlProcessor" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add MjmlProcessor --version 1.0.2
                    
#r "nuget: MjmlProcessor, 1.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package MjmlProcessor@1.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=MjmlProcessor&version=1.0.2
                    
Install as a Cake Addin
#tool nuget:?package=MjmlProcessor&version=1.0.2
                    
Install as a Cake Tool

MjmlProcessor

A dependency-free MJML to HTML converter for .NET.

Give it MJML markup, get back responsive, Outlook-compatible email HTML. There is no Node.js process to install, no mjml binary to shell out to, and no call to the hosted MJML API — the whole renderer is C#, so it runs anywhere your app runs.

using MjmlProcessor;

var html = Mjml.ToHtml("""
    <mjml>
      <mj-body>
        <mj-section>
          <mj-column>
            <mj-text>Hello world</mj-text>
          </mj-column>
        </mj-section>
      </mj-body>
    </mjml>
    """);

Install

dotnet add package MjmlProcessor

Targets netstandard2.0 and net8.0, so it works on .NET Framework 4.6.1+, .NET Core, and current .NET.

Usage

One-off conversion

string html = Mjml.ToHtml(mjmlSource);

Conversion with validation warnings

Render returns the HTML plus anything questionable the converter found — an unknown tag, an mj-class that was never declared, an image with no src.

MjmlResult result = Mjml.Render(mjmlSource);

Console.WriteLine(result.Html);

foreach (MjmlWarning warning in result.Warnings)
{
    // "Line 12, column 9 (mj-image): mj-image requires a src attribute."
    Console.WriteLine(warning);
}

Reuse a converter

MjmlConverter snapshots its options at construction and holds no per-call state, so a single instance can be registered as a singleton and shared across threads.

services.AddSingleton(new MjmlConverter(new MjmlOptions { Minify = true }));

// ...
string html = converter.ConvertToHtml(mjmlSource);

Rendering from a file

RenderFile resolves mj-include paths relative to the file being rendered.

string html = Mjml.FileToHtml("templates/welcome.mjml");

Options

Option Default What it does
Beautify true Indents and line-breaks the generated markup.
Minify false Collapses the whitespace between tags. Text content is left alone.
IncludeDocumentSkeleton true Set to false to emit only the body markup, with no <!doctype> or <head>. CSS that needs a <style> block is then dropped, with a warning.
Language "und" The lang attribute of <html>.
Direction "auto" The dir attribute of <html>.
ValidationLevel Soft Skip ignores problems, Soft collects them as warnings, Strict throws.
FileLoader null Resolves mj-include paths. See below.
Fonts Google's 5 Font family to stylesheet URL, imported only when the family is actually used.
var options = new MjmlOptions
{
    Beautify = false,
    Minify = true,
    ValidationLevel = MjmlValidationLevel.Strict,
};

options.Fonts["Inter"] = "https://fonts.googleapis.com/css?family=Inter:400,700";

string html = Mjml.ToHtml(source, options);

Includes

mj-include needs a loader, because the library never touches the filesystem on its own. Mjml.RenderFile wires one up automatically; supply your own to load partials from anywhere else — embedded resources, a database, blob storage.

var options = new MjmlOptions { FileLoader = new DirectoryFileLoader("templates/partials") };

string html = Mjml.ToHtml(source, options);

DirectoryFileLoader refuses to read outside its configured root, appends .mjml when the path has no extension, and supports type="html" and type="css" includes.

public sealed class EmbeddedLoader : IMjmlFileLoader
{
    public string? Load(string path) => /* return the partial, or null when not found */;
}

Supported elements

Head: mj-head, mj-title, mj-preview, mj-attributes (mj-all, per-tag defaults, and named mj-class sets), mj-style (including inline="inline"), mj-font, mj-breakpoint, mj-raw, mj-include.

Layout: mj-body, mj-wrapper, mj-section (including full-width and background images with a VML fallback), mj-group, mj-column, mj-hero.

Content: mj-text, mj-button, mj-image, mj-divider, mj-spacer, mj-table, mj-raw, mj-social / mj-social-element, mj-navbar / mj-navbar-link (including the hamburger menu), mj-accordion / mj-accordion-element / -title / -text.

CSS inlining

Gmail and several other clients strip <style> blocks, so CSS often has to live in style attributes. mj-style inline="inline" does that for you — the rules are merged into the matching elements and the block itself disappears:

<mj-head>
  <mj-style inline="inline">
    .card p { margin: 0 0 12px 0; font-family: Arial, sans-serif; }
    .card p:last-child { margin-bottom: 0; }
  </mj-style>
</mj-head>

Inlining runs over the finished document, so it reaches your own HTML inside mj-text and mj-raw — which is usually the point — as well as elements the renderer generates and the <body> element itself. The normal cascade applies: specificity, then source order, with a style attribute that is already on the element beating the stylesheet unless the rule is !important.

Supported selectors: type, *, .class, #id, attribute selectors ([a], [a=b], ~=, |=, ^=, $=, *=), the descendant, >, + and ~ combinators, and the structural pseudo-classes :first-child, :last-child, :only-child, :first-of-type, :last-of-type, :nth-child(), :nth-last-child() and :not().

Anything with no static equivalent — @media, @font-face, :hover, ::before — cannot be inlined and is kept in a <style> block instead of being dropped. A selector group is split, so p, a:hover { ... } inlines the p half and preserves the a:hover half.

Two things worth knowing:

  • CSS is inlined, not shortened. Declarations are merged per element; no shorthand collapsing.
  • css-class on a component lands where MJML puts it, which is not always the element you expect. On mj-button it goes on the wrapping <td>, so target the link with .cta a { ... } rather than a.cta { ... }.

Not implemented

  • mj-carousel — the gallery component. Documents using it render without it and get a warning.
  • mj-html-attributes — ignored, with a warning.

Anything unrecognised is skipped and reported as a warning rather than failing the render, unless ValidationLevel is Strict.

What the output looks like

The renderer produces the same shape of markup the reference MJML implementation does:

  • a centred, fixed-width table layout (600px by default, set with <mj-body width="...">)
  • Outlook and IE ghost tables in <!--[if mso | IE]> conditionals, so columns sit side by side in Word-based clients and stack everywhere else
  • @media rules keyed to the breakpoint (<mj-breakpoint>), duplicated outside the media query for Thunderbird's .moz-text-html mode
  • VML fallbacks for section and hero background images
  • the usual client resets: #outlook a, mso-table-lspace, -ms-interpolation-mode, and so on

It is not a byte-for-byte match with the JavaScript implementation, so don't diff the two — compare how they render.

Error handling

  • Syntactically broken markup throws MjmlParseException, carrying Line and Column.
  • With ValidationLevel.Strict, the first semantic problem throws MjmlException.
  • ConvertFile throws FileNotFoundException when the template is missing.
try
{
    var html = Mjml.ToHtml(source);
}
catch (MjmlParseException ex)
{
    Console.WriteLine($"Bad template at line {ex.Line}, column {ex.Column}: {ex.Message}");
}

Building

dotnet build
dotnet test
dotnet pack src/MjmlProcessor -c Release

Package icon

assets/icon.svg is the source artwork; assets/icon.png is the 128×128 render that ships in the package as PackageIcon. Regenerate the PNG from the SVG after changing the artwork — any SVG rasteriser will do, for example:

rsvg-convert -w 128 -h 128 -b white assets/icon.svg -o assets/icon.png

License

MIT

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.2 95 9/1/2026
1.0.1 96 9/1/2026
1.0.0 111 8/31/2026