Open-source project
premailer/premailer avatar
premailer/premailer

Premailer: inlining CSS for HTML email in Ruby

Preflight for HTML email

2,416 stars362 forksRubyNOASSERTION

At a glance

What is it?
Premailer is a Ruby gem that turns a stylesheet-driven HTML newsletter into an email-safe document by copying CSS into style attributes. It is a build-time step, not a mailer, and its behaviour depends heavily on which parser adapter you pick.
Who is it for?
Adopt Premailer if your email HTML is generated from templates with a stylesheet and you want the inlining done in Ruby before handing the string to your mailer. Do not adopt it if you need CSS variables resolved automatically, since the README states the gem does not replace var() calls with static values, or if you cannot accept a Nokogiri dependency in your build.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 9 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Premailer solves for email templates

Email clients are inconsistent about external and embedded stylesheets, so the practical answer has been to put declarations directly on elements as style attributes. Doing that by hand makes a newsletter template unmanageable: every table cell carries its own font, padding and colour, and a design change means editing hundreds of attributes.

Premailer exists to keep the stylesheet as the source of truth and move the declarations onto elements at build time. The README describes the conversion plainly: CSS styles are converted to inline style attributes, and the gem checks both style and link[rel=stylesheet] tags while preserving inline attributes that are already there. It is aimed at Ruby developers generating HTML email, typically from a template that a designer can edit in CSS rather than in table markup.

Two secondary jobs come with the same pass. Relative paths become absolute in href, src and CSS url('') references, which matters because an email client has no useful notion of your application's base path. And CSS properties are checked against email client capabilities, based on the Email Standards Project's guides, producing warnings rather than silently dropping the declaration. That warning list is the part most teams actually read on a first run.

How the inlining pass and adapters work

The gem parses the document, collects the applicable declarations, and writes them back as style attributes. The parsing is delegated to an adapter, and this is a real choice rather than an implementation detail. The README lists three: nokogiri (the default), nokogiri_fast, described as 20x speed with more memory, and nokogumbo. The hpricot adapter was removed, and the README points anyone who still needs it at the ~>1.9.0 version.

You select an adapter with a single assignment, which means the choice is global to the process rather than per document.

ruby
Premailer::Adapter.use = :nokogiri_fast

Beyond standard CSS, Premailer recognises its own properties for table markup. The README documents -premailer-width on table, th and td; -premailer-height on table, tr, th and td; -premailer-cellpadding, -premailer-cellspacing and -premailer-align on table; and data-premailer="ignore" on link and style elements, which makes Premailer skip those elements entirely. Each declaration is copied to the corresponding attribute, so a rule such as table { -premailer-cellspacing: 5; -premailer-width: 500; } produces a table tag with cellspacing='5' and width='500'. That is a convenience layer over attributes that email clients still honour, and it keeps those numbers in the stylesheet instead of in the markup.

The plain text version is generated from the same document. The README's example shows an anchor wrapping an image becoming the alt text followed by the URL in parentheses. Sections can be excluded by wrapping them in comments marked start text/html and end text/html, which the README suggests for removing headers and footers that make no sense in the text part.

Installing the gem and a first inlining run

Installation is a single gem command.

bash

gem install premailer

The README's example constructs a Premailer object from a URL with a warning level, writes the plain text output, then writes the inlined HTML, then prints the warnings. The order matters and the README says so in a comment: the plain text output must come before to_inline_css.

ruby
require 'premailer'

premailer = Premailer.new('http://example.com/myfile.html', warn_level: Premailer::Warnings::SAFE)

File.write "output.txt", premailer.to_plain_text
File.write "output.html", premailer.to_inline_css

premailer.warnings.each do |w|
  puts "#{w[:message]} (#{w[:level]}) may not render properly in #{w[:clients]}"
end

After running that, output.html is a copy of the source document with declarations moved into style attributes, and the terminal shows one line per property the gem considers risky, naming the clients involved. If you already hold the HTML as a string, the configuration example passes it with with_html_string: true, alongside an option such as drop_unmergeable_css_rules: true. The README links to the full option list on the generated documentation site rather than enumerating every key, so treat that page as the reference before you assume an option exists.

CSS variables are not resolved, and that is the sharp edge

The README is explicit that the gem does not automatically replace CSS variables with their static values. The example given is an h1 whose font-weight is set through a variable: the inlined result keeps font-weight:var(--bulma-content-heading-weight) rather than the resolved value. Since email clients are the least likely place for custom property support, an inlined document full of var() calls is worse than useless in exactly the environment Premailer targets.

The suggested workaround is to resolve variables before Premailer sees the CSS, using PostCSS with the postcss-css-variables plugin, installed through yarn add postcss postcss-cli postcss-css-variables, configured in postcss.config.js with preserve: false, and wired into a build:emails script in package.json. The README notes that an .scss source needs a conversion to .css first, and gives a combined sass and postcss command for that case, plus a Procfile.dev entry so the build runs under bin/dev. The stated caveat is that variables must be declared before use, otherwise their values resolve to undefined.

That is a meaningful constraint: a Ruby gem whose documented answer to a CSS feature is a Node toolchain. It is not wrong, but it means a Rails-only shop adopting Premailer for a modern stylesheet takes on a second build system. The other limitation worth naming is scope. Premailer rewrites and warns; it does not send mail, and it does not tell you how a given client rendered the result. The warnings are based on the Email Standards Project's guides, so they reflect that reference rather than live client testing.

Premailer against other inlining approaches

The related searches around this project include premailer python and premailer net, which is the honest way to frame the alternative: the same idea exists in other ecosystems, and the choice is usually made by the language of the service that renders the email rather than by feature comparison. A Python or .NET port keeps the inlining step inside the application that already owns the template, instead of adding a Ruby process or service to the path.

The closer comparison inside Ruby is doing nothing and writing style attributes by hand, or relying on a mail service's own inlining. Hand-written attributes give you exact control and no parser surprises, at the cost of the maintainability problem Premailer was built to remove. A service-side inliner keeps your repository free of the dependency but moves the transformation outside your test suite, so you cannot assert on the inlined output the way you can with a local to_inline_css call. Premailer's advantage is that the transformation is a method call you can test and diff. Its disadvantage is that you now own a Nokogiri dependency and an adapter choice in your build.

Maintenance, licence and upgrade surface

The repository is not archived, and the last push was on 2026-09-20, which is recent enough that the project is being touched. That said, the README's contribution section still reads as a call for help: it asks for improved test coverage and for work on moving un-repeated background images defined in CSS for Outlook, and it states that contributors should not increment version numbers. The Ruby compatibility answer points at .github/workflows/actions.yml for the tested versions, and the README says JRuby support is close and welcomes contributors. So the practical answer to which Ruby versions are supported is: read the workflow file, not the README.

The licence field on the repository is NOASSERTION, which means the automated detection did not reach a conclusion. The README directs readers to LICENSE.md for licence details and carries a copyright line for Alex Dunae covering 2007 to 2017. Anyone embedding this in a commercial product should read LICENSE.md directly rather than treating the repository's licence field as an answer.

Upgrade cost is mostly adapter and option drift. The hpricot adapter's removal is the precedent: a parser can disappear, and the README's remedy is pinning to an older gem version. Projects that set Premailer::Adapter.use explicitly will feel that sooner than projects on the default. The gem is a build-time dependency, so an upgrade is usually a test-suite run away from being safe, provided you have assertions on the inlined output rather than on the source template.

Editorial conclusion

Adopt Premailer if your email HTML is generated from templates with a stylesheet and you want the inlining done in Ruby before handing the string to your mailer. Do not adopt it if you need CSS variables resolved automatically, since the README states the gem does not replace var() calls with static values, or if you cannot accept a Nokogiri dependency in your build. Before wiring it in, verify which adapter you are running, because Premailer::Adapter.use = :nokogiri_fast changes both speed and memory, and check the licence text in LICENSE.md rather than trusting the repository's licence field.

Frequently asked questions

How do I install the Premailer gem?

Run gem install premailer. The README gives that as the installation step, and the gem is then required in Ruby with require 'premailer'.

Which parser adapters does Premailer support?

The README lists nokogiri as the default, nokogiri_fast described as 20x speed with more memory, and nokogumbo. The hpricot adapter was removed, and the README says to use the ~>1.9.0 version if you still need it.

Does Premailer resolve CSS variables into static values?

No. The README states the gem does not automatically replace CSS variables with their static values, and shows an inlined h1 keeping font-weight:var(--bulma-content-heading-weight). The documented workaround is to resolve the variables with PostCSS and the postcss-css-variables plugin before Premailer processes the CSS.

How does Premailer handle table attributes like cellpadding and width?

It recognises its own properties, including -premailer-width, -premailer-height, -premailer-cellpadding, -premailer-cellspacing and -premailer-align, and copies each declaration to the matching attribute on the element. For example, -premailer-cellspacing: 5 and -premailer-width: 500 on a table produce cellspacing='5' and width='500'.

Can I exclude part of the HTML from Premailer's plain text version?

Yes. The README says to wrap the content in comments marked start text/html and end text/html, and notes this is useful for removing email headers and footers that are not needed in the text version.

Official sources

  1. Issues
  2. premailer/premailer on GitHub
  3. Project website
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/premailer-premailer.svg)](https://hysenlabs.com/projects/premailer-premailer)