DEV Community

Vardan Hakobyan
Vardan Hakobyan

Posted on • Originally published at Medium

Mastering Email-Ready HTML: Automated Style Inlining with Tailwind CSS

Image description
In the world of email development, ensuring consistent styling across various email clients is a significant challenge. Email clients are notoriously inconsistent in their CSS support. Many strip out <style> tags or ignore external stylesheets. So ensuring consistent appearance across various email clients often requires inline CSS styles. This requirement can conflict with modern development practices, especially when using utility-first CSS frameworks like Tailwind.

Recently, while working on a project that required sending various emails to users, I faced this exact dilemma. We were using Tailwind for our main application, and I wanted to maintain this efficient workflow for our email templates as well. However, the need for inline styles in emails posed a significant hurdle.

I considered several approaches:

  1. Creating email templates with inline styles from the start.

    • Pros: Guaranteed compatibility with email clients.
    • Cons: Time-consuming to write and maintain, prone to errors, and incompatible with Tailwind.
  2. Using online converters to transform Tailwind-styled templates into inline styles. Then, save the HTML with inline styles as a separate file to be sent via email.

    • Pros: Allows use of familiar Tailwind syntax during development.
    • Cons: Requires keeping two versions of each template (original and converted).
  3. Utilizing an npm package to convert templates to inline styles at runtime.

    • Pros: It’s time-efficient and allows me to maintain only one version of each email template using Tailwind.
    • Cons: I did come across some packages like tailwind-to-css or tw-to-css, but while they have their merits, I encountered the following issues:

      tailwind-to-css generates random class names in the result HTML
      tw-to-css requires calling a converter function for every Tailwind class usage.

After evaluating these options and finding existing tools lacking, I decided to implement a custom solution. In this article, I’ll guide you through the development of a TypeScript module that automates the process of inlining Tailwind classes for email-ready HTML.

I want to highlight that the code actually runs on the Node.js server, so I aimed to use the bare minimum from the frontend world necessary to make the logic work.

Actual implementation

The main logic is resided in makeStylesInline function:

export const makeStylesInline: TMakeStylesInline = async (
  templatePath,
  data,
) => {
  const templateSource = fs.readFileSync(templatePath, 'utf8');
  const template = Handlebars.compile(templateSource);
  const html = template(data);
  return inlineStyles(html);
};
Enter fullscreen mode Exit fullscreen mode

This function takes a template path and optional data, compiles the template with Handlebars, and processes the resulting HTML to inline styles.

Harnessing the Power of Tailwind CSS

A key component is the processTailwindCSS function, which applies Tailwind CSS to our HTML:

const processTailwindCSS = async (html: string): Promise<string> => {
  const tailwindConfig = {
    content: [{ raw: html, extension: 'html' }],
    corePlugins: {
      preflight: false,
    },
  };

  const result = await postcss([
    tailwindcss(tailwindConfig),
    autoprefixer,
  ]).process('@tailwind components; @tailwind utilities;', {
    from: undefined,
  });

  return result.css;
};
Enter fullscreen mode Exit fullscreen mode

This function sets up a minimal Tailwind configuration and uses PostCSS to generate the necessary CSS.

Simplifying and Optimizing CSS for Emails

Given the limited CSS support in email clients, the simplifyColors function optimizes the generated CSS:

const simplifyColors = (css: string): string => {
  const generalSimplifications = css
    .replace(/rgb\(([^)]+)\) \/ var\(--tw-[^)]+\)/g, 'rgb($1)')
    .replace(
      /rgba\(([^,]+),([^,]+),([^,]+),var\(--tw-[^)]+\)\)/g,
      'rgba($1,$2,$3,1)'
    )
    .replace(/var\(--tw-[^)]+\)/g, '1')
    .replace(/--tw-[^:]+:[^;]+;/g, '');

  return generalSimplifications.replaceAll(
    /(rgba?\(\d+\s+\d+\s+\d+\s*\/.*\))/g,
    (match) => rgbToHex(match)
  );
};
Enter fullscreen mode Exit fullscreen mode

This function removes Tailwind-specific CSS variables and converts RGB colors to hexadecimal for better email client support.

The Art of Style Inlining

For the actual inlining process, we use the juice library:

const inlineStyles = async (html: string): Promise<string> => {
  const tailwindCss = await processTailwindCSS(html);
  const simplifiedCss = simplifyColors(tailwindCss);

  return juice(html, {
    extraCss: simplifiedCss,
    applyStyleTags: true,
    removeStyleTags: true,
    preserveMediaQueries: true,
    preserveFontFaces: true,
    preserveImportant: true,
    inlinePseudoElements: true,
  });
};
Enter fullscreen mode Exit fullscreen mode

This function processes the HTML with Tailwind, simplifies the resulting CSS, and then uses juice to inline the styles. It preserves important structures like media queries and font faces.

Handling Dynamic Content with Handlebars

The solution integrates Handlebars for template compilation, allowing for dynamic content insertion:

const templateSource = fs.readFileSync(templatePath, 'utf8');
const template = Handlebars.compile(templateSource);
const html = template(data);
Enter fullscreen mode Exit fullscreen mode

This approach enables the use of placeholder values in email templates, enhancing flexibility and reusability.

Conclusion

This TypeScript code offers an automated solution for generating email-ready HTML with inlined styles. By leveraging Tailwind CSS, PostCSS, and careful CSS optimization, it significantly streamlines the email development process. The result is consistently styled, cross-client compatible email HTML, generated with minimal manual intervention.

For developers looking to improve their email development workflow, this solution provides a robust starting point. It combines the utility-first approach of Tailwind CSS with the practicality of automated style inlining, all while maintaining the flexibility needed for dynamic email content.

For the full source code and more detailed implementation, visit the GitHub repository.

It’s important to note that this is version 1 of the solution, and as such, it may not cover all possible use cases or have all desired features. There’s always room for improvement and expansion, and I welcome feedback from developers who try this solution in their projects.

Thanks for reading! If you have any question or need some clarification, feel free to reach out.

Stay updated with the latest JavaScript and software development news! Join my Telegram channel for more insights and discussions: TechSavvy: Frontend & Backend.

Top comments (1)

Collapse
 
clementjanssens profile image
Clément Janssens

Or you can just use Mailhub.sh
Easier 😀