emjay
A pure-Ruby implementation of MJML, the email markup language. Converts MJML templates to responsive HTML emails — no Node.js, no native extensions, no shelling out.
Feature parity with MJML
emjay implements the full set of standard MJML components:
Head components: mj-head, mj-attributes, mj-style (including inline), mj-font, mj-title, mj-preview, mj-breakpoint, mj-html-attributes
Body components: mj-body, mj-section, mj-wrapper, mj-column, mj-group, mj-text, mj-image, mj-button, mj-divider, mj-spacer, mj-table, mj-raw, mj-hero, mj-social / mj-social-element, mj-navbar / mj-navbar-link, mj-accordion / mj-accordion-element / mj-accordion-title / mj-accordion-text, mj-carousel / mj-carousel-image
Features: CSS inlining via <mj-style inline="inline">, global default attributes, mj-class, mj-html-attributes with CSS selectors, custom fonts, responsive breakpoints, Outlook conditionals, lang/dir attributes.
All components and features from the MJML documentation should work as described. The MJML templates and examples are a good starting point for building your own emails. Output is tested against the upstream MJML 4 JavaScript implementation using fixture-based comparison and backported behavioral tests. If you find a case where emjay produces different output from the reference MJML implementation, please open an issue.
Installation
Add to your Gemfile:
gem "emjay"
Runtime dependencies: nokogiri for XML/HTML parsing, premailer for CSS inlining (<mj-style inline="inline">).
Usage
Standalone
require "emjay"
mjml = <<~MJML
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>Hello World</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
MJML
html = Emjay.to_html(mjml)
Rails
emjay includes a Railtie that registers an ActionView template handler automatically. Create templates with the .html.mjml extension:
app/views/user_mailer/welcome.html.mjml
ERB tags work inside .mjml templates — the handler always chains through ERB before compiling MJML:
<mjml>
<mj-body>
<mj-section>
<mj-column>
<mj-text>Welcome, <%= @user.name %>!</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
Your mailer needs no special setup:
class UserMailer < ApplicationMailer
def welcome(user)
@user = user
mail(to: @user.email)
end
end
Rails resolves the template automatically: .mjml selects the handler, .html sets the MIME type. If you also provide welcome.text.erb, ActionMailer sends a multipart email with both HTML and plain text parts.
Layouts
MJML layouts work just like regular Rails mailer layouts. Create a layout that wraps yield with the MJML boilerplate:
<%# app/views/layouts/mailer.html.mjml %>
<mjml>
<mj-head>
<mj-attributes>
<mj-all font-family="Helvetica, Arial, sans-serif" />
</mj-attributes>
</mj-head>
<mj-body>
<%= yield %>
</mj-body>
</mjml>
Then your templates contain only the content sections:
<%# app/views/user_mailer/welcome.html.mjml %>
<mj-section>
<mj-column>
<mj-text>Welcome, <%= @user.name %>!</mj-text>
</mj-column>
</mj-section>
Set the layout in your mailer as usual:
class UserMailer < ApplicationMailer
layout "mailer"
end
Partials
MJML partials work with the standard render helper. Use them to share common sections across emails:
<%# app/views/shared/_header.html.mjml %>
<mj-section background-color="#f0f0f0">
<mj-column>
<mj-image src="<%= @logo_url %>" width="150px" />
</mj-column>
</mj-section>
<%# app/views/user_mailer/welcome.html.mjml %>
<%= render "shared/header" %>
<mj-section>
<mj-column>
<mj-text>Welcome!</mj-text>
</mj-column>
</mj-section>
How it works
The .mjml template handler is an ERB passthrough — it does not compile MJML itself. Instead, an ActionMailer interceptor (Emjay::Rails::MailInterceptor) compiles the assembled MJML to HTML after Rails has applied layouts and partials. This means yield in a layout receives MJML (not HTML), and the full document is compiled in one pass.
Other Ruby MJML implementations
- mjml-rails — Rails integration that shells out to the MJML Node.js binary. Requires Node.js at runtime.
- mjml-ruby — Ruby wrapper around the MJML Node.js parser. Also requires Node.js.
- mrml-ruby — Ruby bindings to MRML, a Rust reimplementation of MJML. Requires a compiled native extension.
emjay differs from all of the above by being pure Ruby — it has no dependency on Node.js or native extensions.
License
MIT