mjml: Render MJML responsive email templates to HTML

[ email, library, mit, text, web ] [ Propose Tags ] [ Report a vulnerability ]

A Haskell implementation of the MJML email framework. It renders MJML documents to the same HTML that the reference JavaScript implementation produces, without Node.js at runtime.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0, 0.1.0.1
Change log CHANGELOG.md
Dependencies base (>=4.20 && <5), containers (>=0.6 && <0.9), text (>=2.0 && <2.2), text-builder-linear (>=0.1.3 && <0.2), transformers (>=0.6 && <0.7) [details]
Tested with ghc ==9.10.3 || ==9.12.4 || ==9.14.1
License MIT
Copyright 2026 Kronor
Author José Lorenzo Rodríguez
Maintainer lorenzo@kronor.io
Uploaded by lorenzo at 2026-10-31T09:16:22Z
Category Text, Web, Email
Distributions
Reverse Dependencies 1 direct, 0 indirect [details]
Downloads 6 total (6 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for mjml-0.1.0.1

[back to package description]

mjml

Render MJML responsive email templates to HTML from Haskell, without Node.js.

MJML describes an email with a few components (sections, columns, text, images, buttons) and compiles them to the nested tables, inline styles and Outlook conditional comments that render the same in every mail client, with columns that stack on phones. This package is a port of the reference JavaScript implementation and produces the same HTML as MJML 5. To write templates in Haskell with compile-time checks, see mjml-hamlet.

An order confirmation rendered by this package, on a desktop client and on a phone (the template is in the mjml-hamlet README):

The order confirmation in a desktop mail client

The same email on a phone: narrower, with item names and prices still side by side

import Data.Text.IO qualified as T
import Text.Mjml (defaultOptions, renderText)

main :: IO ()
main =
    either print T.putStrLn $
        renderText defaultOptions
            "<mjml>\
            \  <mj-body>\
            \    <mj-section>\
            \      <mj-column>\
            \        <mj-text font-size=\"20px\">Your order is on its way</mj-text>\
            \        <mj-button href=\"https://example.com/track\">Track it</mj-button>\
            \      </mj-column>\
            \    </mj-section>\
            \  </mj-body>\
            \</mjml>"

Escaping

Like MJML, the renderer copies attribute values and the content of ending tags (mj-text, mj-button, mj-raw, …) into the output as they are: they are HTML source, not text. Escape anything that comes from user data before putting it into a document.

Compatibility

The output is compared with MJML 5.4.1 for the 155 documents in test/fixtures. The comparison ignores whitespace, attribute order and the order of CSS declarations, and nothing else.

Not supported yet:

  • mj-include, which MJML also ignores unless asked to read files;
  • inlining <mj-style inline="inline"> (the style is kept in the head);
  • mj-html-attributes;
  • minification and beautification.

Updating the fixtures

The expected HTML is regenerated with the reference implementation:

npm ci --prefix scripts
node scripts/update-fixtures.mjs

License

MIT. The fixture inputs come from mrml, also MIT; see test/fixtures/NOTICE.md.