Skip to Content
  • Website
Codsen
  • Home
  • Open Source
  • Articles
  • About

prevOpen Source→email-combnext

email-comb7.2.1

Remove unused CSS from email templates

Downloads per monthChangelogMIT LicensePlayground
  • the top
  • Installation
  • Quick Take
  • Examples
  • API — COMB()
  • API — DEFAULTS
  • API — VERSION
  • API — TYPES
  • OPTS — WHITELIST
  • OPTS — BACKEND
  • TAPPING THE…
  • Changelog

Installation

Quick Take

Examples

  • Keep classes inside backend template markers
  • Keep HTML comments containing a given string
  • Keep CSS comments
  • Keep HTML comments
  • Keep id's referenced by for attributes
  • Minifies and uglifies
  • Report progress within a custom range
  • Remove media queries left empty
  • Returns all extracted and deleted classes and id's
  • Works even if <style> is within <body> and there's no <head>
  • Whitelist a selector from removal
  • Whitelist using wildcards
Open email-comb playground

API — comb()

It is a function which takes two arguments: a string and an optional options object:

function comb(str: string, opts?: Partial<Opts>): Res;

The optional options object has the following shape:

{
  whitelist: string[];
  backend: HeadsAndTailsObj[];
  uglify: boolean;
  removeHTMLComments: boolean;
  removeCSSComments: boolean;
  doNotRemoveHTMLCommentsWhoseOpeningTagContains: string[];
  htmlCrushOpts: {
    lineLengthLimit: number;
    removeIndentations: boolean;
    removeLineBreaks: boolean;
    removeHTMLComments: boolean | 0 | 1 | 2;
    removeCSSComments: boolean;
    reportProgressFunc: null | ((percDone: number) => void);
    reportProgressFuncFrom: number;
    reportProgressFuncTo: number;
    breakToTheLeftOf: string[];
    mindTheInlineTags: string[];
  };
  reportProgressFunc: null | ((percDone: number) => void);
  reportProgressFuncFrom: number;
  reportProgressFuncTo: number;
}
KeyTypeDefaultExampleDescription
whitelist
Type: Array
Default: []
Example: [".class-1", "#id-1", ".module-*"]
whitelistArray[][".class-1", "#id-1", ".module-*"]List all classes or id’s you want this library to ignore. You can use all matcheropens in a new tab patterns.
backend
Type: Array
Default: []
Example: [{ heads: "{{", tails: "}}" }, { heads: "{{%", tails: "%}}" }]
backendArray[][{ heads: "{{", tails: "}}" }, { heads: "{{%", tails: "%}}" }]If your code has back-end code within class or id values, for example, class="{{ red }} main-box" you can stop {{, red and }} to be treated as class names
uglify
Type: Boolean
Default: false
Example: n/a
uglifyBooleanfalsen/aWill rename all class and id names to be few characters-long. This might reduce your file size by another kilobyte.
removeHTMLComments
Type: Boolean
Default: true
Example: n/a
removeHTMLCommentsBooleantruen/aWhen enabled, all HTML comments (<!-- to -->) will be removed
removeCSSComments
Type: Boolean
Default: true
Example: n/a
removeCSSCommentsBooleantruen/aWhen enabled, all CSS comments (/* to */) will be removed
doNotRemoveHTMLCommentsWhoseOpeningTagContains
Type: Array of zero or more insensitive strings
Default: ["[if", "[endif"]
Example: n/a
doNotRemoveHTMLCommentsWhoseOpeningTagContainsArray of zero or more insensitive strings["[if", "[endif"]n/aEmail code often contains Outlook or IE conditional comments which you probably don’t want to remove. Whatever strings you list here, if comment’s opening tag will contain these, that tag will not be removed.
htmlCrushOpts
Type: Plain object
Default: see below
Example: n/a
htmlCrushOptsPlain objectsee belown/aAllows to customise the minification settings. See more on html-crush documentation
reportProgressFunc
Type: Function or something falsy
Default: null
Example: n/a
reportProgressFuncFunction or something falsynulln/aIf supplied, it will ping the function you assign passing current percentage done (natural number) as an input argument
reportProgressFuncFrom
Type: Natural number
Default: 0
Example: n/a
reportProgressFuncFromNatural number0n/aBy default, percentages are reported from 0 to 100. This value overrides this starting percentage value.
reportProgressFuncTo
Type: Natural number
Default: 100
Example: n/a
reportProgressFuncToNatural number100n/aBy default, percentages are reported from 0 to 100. This value overrides this ending percentage value.

Here are all defaults in one place for copying:

The function returns a plain object (marked as type Res):

API — defaults

You can import defaults:

It's a plain object:

The main function calculates the options to be used by merging the options you passed with these defaults.

API — version

You can import version:

API — types

This package is written in TypeScript and exports the following types:

TypeDescription
HeadsAndTailsObj
Type: HeadsAndTailsObj
HeadsAndTailsObjOne templating-marker pair for opts.backend — its heads and tails.
Opts
Type: Opts
OptsThe fully resolved Options Object, after your input has been merged with the defaults.
InputOpts
Type: InputOpts
InputOptsThe looser shape comb() actually accepts — every key is optional and several accept a string where Opts requires an array.
Res
Type: Res
ResWhat comb() returns — the result, the class/id tallies and the log.
import type { HeadsAndTailsObj, InputOpts, Opts, Res } from "email-comb";

opts.whitelist

Since the main purpose of this library is to clean email HTML, it needs to cater for email code specifics. One of them is that CSS styles will contain fix or hack styles, meant for email software. For example, here are few of them:

#outlook a {
  padding: 0;
}
.ReadMsgBody {
  width: 100%;
}

You will not be using these classes within the <body> of your HTML code, so they would get removed as “unused” because they are present in <head> only. To avoid that, pass the classes, and id’s in the whitelist key’s value, as an array. For example:

var html = "<!DOCTYPE html>...";
comb(html, {
  whitelist: ["#outlook", ".ExternalClass", ".ReadMsgBody"],
});

It’s a common practice to name each email template module (the outermost table) using a class. For example, <table class="module-header"..., <table class="module-promo1"... and so on. To prevent these classes from being removed, use a wildcard:

var html = "<!DOCTYPE html>...";
comb(html, {
  whitelist: [".module-*"],
});

opts.backend

Email template source code typically contains some templating language literals, for example Jinja/Nunjucks:

<td class="mt10 {{ module.on }} module-box blackbg">

How do you prevent {{, module.on and }} from being treated as class names?

You need to let email-comb know about these markings. We call opening-ones “heads” and closing-ones “tails”.

For example, in Mailchimp piece of code *|tralala|*, we’d call the *| bit “heads” and |* “tails”.

You need to pass pairs of “heads” and “tails” to email-comb, for example, here’s Nunjucks/Jinja code:

import { comb } from "email-comb";

const res = comb(
  `<!doctype html>
<html>
<head>
<style>
.aaa {
color:  black;
}
</style></head>
<body class="{% var1 %}">
<div class="{{ var2 }}">
</div>
</body>
</html>
`,
  {
    backend: [
      {
        heads: "{{",
        tails: "}}",
      },
      {
        heads: "{%",
        tails: "%}",
      },
    ],
  },
).result;

console.log("res =\n" + res);

In templating languages, it’s also possible to have IF-ELSE clauses. For example, in Nunjucks, you can have:

<td class="db{% if module_on || oodles %}on{% else %}off{% endif %} pt10"></td>

db and pt10 are normal CSS class names, but everything else between {% and %} is Nunjucks code.

Now, in those cases, “lift the logic up”, pull it outside the HTML tag:

{% set switch = 'off' %}
{% if module_on || oodles %}
  {% set switch = 'on' %}
{% endif %}
<td class="db {{ switch }} pt10"></td>

Now you’re back on track and can use opts.backend to ignore {{ and }} pairs.

Tapping the stream in Gulp

In Gulp, everything flows as vinyl Buffer streams. You could tapopens in a new tab the stream, convert it to string, perform the operations (like remove unused CSS), then convert it back to Buffer and place the stream back. We wanted to come up with a visual analogy example using waste pipes but thought we’d rather won’t.

Code-wise, here’s the idea:

import tap from "gulp-tap";
import { comb } from "email-comb";
import util from "gulp-util";
const whitelist = [".External*", ".ReadMsgBody", ".yshortcuts", ".Mso*", "#outlook", ".module*"];

gulp.task("build", () => {
  return gulp.src("emails/*.html").pipe(
    tap((file) => {
      const cleanedHtmlResult = comb(file.contents.toString(), {
        whitelist,
      });
      util.log(util.colors.green(`\nremoved ${cleanedHtmlResult.deletedFromHead.length} from head: ${cleanedHtmlResult.deletedFromHead.join(" ")}`));
      util.log(util.colors.green(`\nremoved ${cleanedHtmlResult.deletedFromBody.length} from body: ${cleanedHtmlResult.deletedFromBody.join(" ")}`));
      file.contents = Buffer.from(cleanedHtmlResult.result);
    }),
  );
});

Changelog

Open Changelog
↑ back to top
prev next

Copyright

All rights reserved © Roy Revelt 2026
All our open source packages are under MIT licenceopens in a new tab

Activities

🐛 See a bug? Raise an issueopens in a new tab
💘 Check out the Indiewebopens in a new tab and Libera manifestoopens in a new tab