Library / SDK
milkshakesoftware/PreMailer.Net avatar
milkshakesoftware/PreMailer.Net

PreMailer.Net moves your stylesheets into inline style attributes

C# library that moves your stylesheets to inline style attributes, for maximum compatibility with E-mail clients.

689 stars116 forksC#MIT

At a glance

What is it?
PreMailer.Net is a C# library that rewrites CSS as inline style attributes so e-mail clients render it, and it comes with hooks for external stylesheets, media queries and analytics tags. The trade-off is that inlining is not a full CSS engine, and anything it cannot resolve shows up in a warnings list.
Who is it for?
PreMailer.Net fits teams that send HTML e-mail from .NET and want the CSS reduced to inline attributes before the message leaves the process. It does not fit teams that rely on CSS the library cannot inline, such as pseudo classes it cannot resolve, because those failures are only reported as warnings rather than errors.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

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

Editorial analysis

The rendering problem PreMailer.Net was written to solve

E-mail clients are far less capable than browsers. A stylesheet in a style element that a browser applies without complaint can be ignored entirely by an e-mail client, leaving a message unstyled or badly laid out. PreMailer.Net works around that by reading the document, resolving the cascade itself, and rewriting the matched rules into style attributes on the elements themselves. The result is markup that carries its own presentation, so the client has very little left to decide.

The README frames the library plainly: a C# library for moving CSS to inline style attributes, to gain maximum e-mail client compatibility. The word compatibility is doing specific work here. Inline attributes sit at the element level and avoid the whole question of whether a client honours a style block at all.

That approach also has a ceiling, which the project states in its own notes. Pseudo classes and pseudo elements that external dependencies do not support, or that make no sense in e-mail, are ignored. They are not silently dropped either: the library logs them to the InlineResult.Warnings collection so a template author can see which rules were skipped.

Two ways in: a static call or a reusable PreMailer instance

The simplest entry point is a static method on the PreMailer class. You hand it an HTML string, it hands back a result object carrying the rewritten markup and any warnings. The README's static example reads a file from disk first, which is a good hint that the library expects a complete document rather than a fragment:

csharp
string htmlSource = File.ReadAllText(@"C:\Workspace\testmail.html");

var result = PreMailer.MoveCssInline(htmlSource);

result.Html 		// Resultant HTML, with CSS in-lined.
result.Warnings 	// string[] of any warnings that occurred during processing.

When you need to configure behaviour or add tracking, you switch to the instance form. The instance is constructed with the HTML, can be given analytics tags, and exposes a Document property for editing the tree before inlining happens:

csharp
var pm = new PreMailer(htmlSource);
// Optional to add analytics tags
pm.AddAnalyticsTags(source, medium, campaign, content, domain = null);

var result = pm.MoveCssInline(
    removeStyleElements: false,
    ignoreElements: "#ignore",
    preserveMediaQueries: true
);

The Document property hands back the underlying IHtmlDocument, and the README notes that AngleSharp can be used to manipulate it first. That is the hook for templates you need to restructure, for example removing a header or swapping a placeholder, before the CSS is resolved and written inline.

Options that decide what survives the inlining pass

MoveCssInline accepts a set of named options, and each one changes the output in a way you can check in the result markup.

- baseUri sets the base URL applied to link elements whose href ends with .css. - removeStyleElements drops the elements the CSS was sourced from, currently only style elements. - ignoreElements takes a CSS selector for elements that must not be inlined. - css supplies a style sheet as a string. - stripIdAndClassAttributes removes ID and class attributes from the output. - removeComments controls whether comments are kept. - customFormatter accepts an IMarkupFormatter for the HTML output. - preserveMediaQueries keeps media queries in the style node while other CSS is removed. - useEmailFormatter keeps empty HTML tags as full tags instead of self-closing ones, and preserves entities such as ©.

Two of these deserve a closer look. useEmailFormatter exists because self-closing tags and decoded entities are not what many e-mail templates expect, and stripping the class and ID attributes changes how the markup reads afterwards, which matters if you later inspect the output or feed it to another tool. The warnings collection is worth watching in every mode: the README states that unsupported pseudo classes and pseudo elements are ignored and recorded there, so an empty warnings array is the signal that the whole style sheet was understood.

Media queries need a deliberate choice, and there are two

Mobile styles are the part of a template that cannot be inlined, because at send time you do not know whether the message will be opened on a phone. The README is direct about this: the style block that targets mobile devices should not be inlined.

There are two documented ways to achieve that, and they differ in outcome. The first uses the ignoreElements selector so a chosen block is left untouched:

csharp
var result = PreMailer.MoveCssInline(input, false, ignoreElements: "#ignore");

The second combines removeStyleElements with preserveMediaQueries, which keeps the media queries in the style node while removing other CSS from it:

csharp
var result = PreMailer.MoveCssInline(input, removeStyleElements: true, preserveMediaQueries: true);

With the second option the mobile block needs an ID of ignore:

html
<style type="text/css" id="ignore">.target { width: 1337px; }</style>

Pick one and apply it consistently across your templates. Mixing the two conventions means some templates keep a whole mobile block while others keep only the media query rules, and the difference is only visible when you read the sent message source.

External stylesheets are fetched, so relative href values need a baseUri

PreMailer.Net can pull in external style sheets referenced by a link element, and it does so automatically as long as the href value ends with .css. Both absolute and relative URLs are supported. The constraint is on the relative case: you must pass the baseUri parameter, either through the constructor or on the static MoveCssInline method, otherwise there is nothing to resolve the relative path against.

The download happens at processing time, which makes network access part of the library's contract rather than something you opt into. A template that references a stylesheet on your own server means a request to that server every time the template is processed. If the stylesheet is unavailable at that moment, the rules it contained are simply not part of the inlined result.

One escape hatch exists: link elements that match the ignoreElements selector are not downloaded. That gives you a way to keep an external sheet as a live reference rather than a flattened copy of it, which is the same reasoning that drives the mobile styles approach.

The -premailer prefix pushes CSS values onto HTML attributes

Some table presentation lives in HTML attributes rather than in style attributes, and e-mail clients treat those two categories differently. PreMailer.Net bridges that gap with a naming convention: CSS properties prefixed with -premailer are proxied through to the DOM element as attributes.

css
table {
    -premailer-cellspacing: 5;
    -premailer-width: 500;
}

The table element then renders as:

html
<table cellspacing="5" width="500">

This is a small feature and a telling one. It shows the library was built around the assumption that the output is a complete e-mail document rather than a page, where cellspacing and width attributes would be obsolete. The trade-off is that the prefixed declarations are project-specific, so a style sheet written for the browser will not carry them, and they have to be added to the source templates deliberately.

Analytics tags and the NuGet path to get started

The instance exposes an AddAnalyticsTags method that appends Google Analytics parameters to links in the HTML:

csharp
var pm = new PreMailer(htmlSource);
pm.AddAnalyticsTags(
    source: "newsletter",       // utm_source parameter
    medium: "email",            // utm_medium parameter
    campaign: "summer_sale",    // utm_campaign parameter
    content: "logo_link",       // utm_content parameter
    domain: "example.com"       // Optional: only add tags to links matching this domain
);

Passing a domain restricts the tagging to links matching that domain. Without one, the parameters are appended to all links in the document.

The installation path is NuGet. The README points to PreMailer.Net on nuget.org and nothing else, so no build-from-source steps are given. The repository does ship a GitHub Actions workflow, and the README shows how to run it locally with act:

bash
act push --container-architecture=linux/arm64 --platform ubuntu-slim=node:lts-bullseye

That command runs the pipeline on a container locally rather than installing the library, so it is useful for reproducing a build check, not for adding the package to a project. The published releases tell the rest of the story: v2.7.4 on 2026-08-28, v2.7.3 on 2026-06-15 and v2.7.2 on 2025-06-27, with the last push on 2026-10-16.

Editorial conclusion

PreMailer.Net fits teams that send HTML e-mail from .NET and want the CSS reduced to inline attributes before the message leaves the process. It does not fit teams that rely on CSS the library cannot inline, such as pseudo classes it cannot resolve, because those failures are only reported as warnings rather than errors. Before adopting it, check the warnings collection on your own templates, confirm that relative stylesheet links resolve against the baseUri you pass, and decide how mobile media queries should be preserved, since the two options behave differently.

Frequently asked questions

What does PreMailer.Net do with a stylesheet?

It resolves the CSS and rewrites the matched rules into inline style attributes on the elements, so the rules travel with the markup. The README describes it as a C# library for moving CSS to inline style attributes, to gain maximum e-mail client compatibility.

How do I keep mobile styles from being inlined by PreMailer.Net?

Mark the mobile style block with an ID of ignore and pass ignoreElements: "#ignore", or combine removeStyleElements: true with preserveMediaQueries: true so the media queries stay in the style node while other CSS is removed.

Does PreMailer.Net support external CSS files and relative URLs?

Yes, link elements whose href ends with .css are downloaded and used, with both absolute and relative URLs supported. For a relative URL you must supply the baseUri parameter in the constructor or when calling the static MoveCssInline method, and link elements matching ignoreElements are not downloaded.

What happens to CSS that PreMailer.Net cannot apply?

Pseudo classes and pseudo elements that are not supported by the external dependencies, or that make no sense in e-mail, are ignored and logged to the InlineResult.Warnings collection. The result object exposes them as Warnings alongside the resulting Html.

Official sources

  1. License: MIT
  2. milkshakesoftware/PreMailer.Net on GitHub
  3. Project website
  4. README
  5. Releases
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/milkshakesoftware-premailer-net.svg)](https://hysenlabs.com/projects/milkshakesoftware-premailer-net)

Was this useful?