mrml

A fast, memory-efficient Rust reimplementation of the MJML email markup renderer

Library
Cargo
v6.0.1
504stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
75/100Good
Development Activity80
Maintenance84
Community48
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
76/100Good
Architecture80
Code Quality78
Innovation74
Learning Curve70

mrml is a Rust implementation of MJML, the responsive email markup language originally built as a Node.js tool by Mailjet. It parses .mjml templates and renders them to cross-client-compatible HTML, exposing the same component model (mj-section, mj-column, mj-button, etc.) developers already know from MJML.

The project exists primarily for performance and footprint: the README reports the Rust version needs under 2MB of RAM versus around 20MB for the Node.js renderer, and renders roughly 170x faster in the authors’ benchmark. Beyond the core Rust crate, the workspace ships a CLI, a WebAssembly build (so MJML can be rendered directly in a browser, which the Node.js implementation cannot do), and Python bindings via PyO3, making it usable well outside the Rust ecosystem.

What You Get

  • A parse/render API covering the full MJML component set (mj-section, mj-column, mj-button, mj-image, mj-social, mj-carousel, mj-accordion, and more)
  • Pluggable include loaders (memory, local filesystem, HTTP — sync and async) for resolving mj-include partials
  • A WebAssembly build that renders MJML directly in the browser, something the original Node.js MJML cannot do
  • A standalone mrml-cli binary for rendering templates from the command line without writing any Rust
  • Python bindings (published as the mrml PyPI package) so non-Rust teams can call the same renderer
  • Feature flags to opt in/out of parsing, printing, rendering, async support, and specific HTTP loader backends, keeping the compiled binary lean

Common Use Cases

  • Rendering transactional or marketing email HTML server-side in a Rust backend without shelling out to Node.js
  • Embedding an MJML renderer in a browser-based email template editor via the WebAssembly build
  • Replacing a Node.js MJML rendering microservice with a lower-memory Rust or WASM equivalent under high request volume
  • Calling MJML rendering from Python services via the mrml Python bindings instead of maintaining a separate Node.js toolchain

Under The Hood

Architecture - The workspace splits into mrml-core (the parser/renderer library, one Rust module per MJML component such as mj_section, mj_column, mj_button), mrml-cli, mrml-wasm, and mrml-python, all depending on the core crate. Parsing goes through a prelude::parser layer that builds an AST of component structs, and rendering goes through a matching prelude::render layer that walks the same tree to emit table-based HTML; mj-include resolution is abstracted behind a loader trait with memory, local, and HTTP implementations (sync and async) so template inclusion isn’t tied to the filesystem. Tech Stack - Pure Rust with htmlparser for the base parsing, serde/serde_json behind the optional json feature, thiserror for typed errors, and enum_dispatch/enum-as-inner for the component enum dispatch; async HTTP loading is feature-gated behind reqwest or ureq backends and async-trait, keeping the default build free of an async runtime dependency. Code Quality - The crate is organized as one directory per MJML tag with colocated tests; the repo has 125+ files containing #[test], and CI badges (codecov, GitHub Actions) in the README indicate an established test and coverage pipeline. Feature flags (parse, render, print, json, async, per-loader flags) are used consistently to keep unused functionality compiled out. API Design - The top-level mrml::parse/parse_with_options functions plus element.render(&opts) give a minimal two-call happy path, and the same primitives are re-exposed nearly unchanged in the Python bindings (mrml.to_html), so the developer experience is consistent across the Rust, WASM, and Python entry points.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search