Case Study: Responsive email templates at zsa.io

  • ReactJS
  • Responsive Emails
  • NodeJS

Overview

ZSA.io specializes in designing ergonomic, mechanical keyboards with an artisanal approach. They pour time and care into their craft, which has earned them a dedicated following and high praise in the mechanical keyboard community. Currently, ZSA keyboards are sold exclusively through their online shop.

The store’s e-commerce infrastructure relies on Shopify, handling order management, payment processing, and other essential back-office tasks, including customer emails.

Unfortunately, Shopify’s native email template editing features leave much to be desired. Email templates for crucial notifications like order confirmations and shipping updates are edited directly within a text area in Shopify’s dashboard. This setup presents numerous challenges:

  • Limited Flexibility: The built-in editor offers little control or customization. Shopify’s email previews generate random dummy data, making it impossible to preview with real-world examples.
  • Complex Templating Requirements: Email templates are built using Shopify’s Liquid, a templating language that requires a steep learning curve and limits functionality. Liquid syntax disallows handy features like include and include_relative for code reuse, meaning each template must reside in a single file.
  • Error-Prone Process: This setup makes for a challenging and error-prone development experience, especially given the high complexity and specificity of Liquid syntax.

I was tasked with building a pipeline to smooth out these friction points and to provide a method for ZSA stakeholders to create, edit, preview, and test email templates populated with real-world data, drawn directly from ZSA.io’s store.

The Problem

Developing effective email templates is an uphill battle. Email client compatibility ranges from robust, HTML-friendly setups to outdated ones with the feel of “It’s 2001, Cut My Life Into Pieces.” It’s a lot like the browser wars of the past—only with quirks and bugs that make cross-client compatibility especially tricky.

Several key pain points emerge when developing email templates:

  • Content Length Restrictions: Certain email clients, notably Gmail, will cut off emails after a certain number of characters, appending a “click here to view more” link, which can make longer emails appear incomplete.
  • Spam Triggers: Overly lengthy emails and image-heavy templates may be filtered as spam, disrupting the user experience.
  • Rendering Issues: Images aren’t always displayed by default, and accessibility (a11y) and security requirements add further technical hurdles.

Achieving feature parity across clients requires workarounds, shims, and polyfills, which can turn into more hindrance than help. This situation presents not only technical but design challenges as well.

The Solution

Research and Knowledge Building

The first step was diving into Shopify’s documentation on email templates, which outlines the data structures available for each email type. Though these generally have a consistent shape, there are small variations requiring close attention. I also needed to familiarize myself with Liquid, including its syntax quirks and limitations.

Collaboration on Design and Functionality

After the technical foundation, I worked with ZSA’s designer to review email designs, discussing what was feasible, where compromises might be needed, and which red lines couldn’t be crossed.

Email templates, much like any other UI, often contain repeatable patterns that can be broken down into reusable components. However, unlike standard web UI, email layout rendering engines are notoriously finicky. Outlook, for example, uses the Microsoft Word rendering engine—a reminder that we’re dealing with a highly non-standard environment.

Tool Selection: MJML for Layout Management

Given the time constraints, I turned to a tool I’d used before: MJML, a framework that simplifies responsive email design. MJML excels at rendering static markup but isn’t designed to handle dynamic templating languages like Liquid.

Combining MJML with Liquid and React

To bridge this gap, I decided to render MJML components into Liquid syntax. I selected React for its established component-based architecture, extensive community, and ReactDOM’s ability to render static HTML (or in this case, Liquid syntax). Using the react-mjml package, I built a process for rendering these templates into Liquid, then set up:

  • A Build Pipeline: Utilizing Rollup for converting React components into Liquid-compatible MJML components.
  • Liquid-to-HTML Renderer: A custom renderer to transform Liquid templates into HTML.
  • Local Server and Preview: A simple HTTP server and frontend app for viewing and debugging templates.
Created with Fabric.js 5.3.0 React / MJMLComponents CompiledJS LiquidRenderer LiquidTemplates HTMLRenderer PreviewServer Data(Shopify) User Email Rollup Shopify Platform

Variable Scoping

Liquid scoping adds a layer of complexity, especially since Shopify compiles emails as a single, large template file. This made scoped variable management essential to avoid conflicts and simplify reusability.

To manage scope effectively, I implemented three additionl utilities:

  • LiquidVar: A JavaScript representation of Liquid variables, allowing manipulation and output without interpolation. LiquidVar acts as a reference, outputting the variable’s name in the final template. It merely extends String with a few additional methods, meaning it can be used as a string would for interpolation.
  • useScopedVariables: A custom hook to manage scoped variables, returning LiquidVars and a dedicated <Assignments /> component to map higher-level variables to their scoped equivalents.
  • withScope HOC: A higher-order component that provides scoped variables via React context. Components using withScope inherit the current scope unless further overridden by another withScope.

Declarative Control Flow

Order confirmation emails often have intricate logic based on order details, location, payment method, etc. To handle this, I developed declarative components that represent Liquid’s control structures:

  • If, Else, and For Components: These React components mimic Liquid’s conditional and loop logic in a declarative format, allowing complex templating logic to be built systematically and readably.
  • `Raw` and `Liquid` escape hatches: Additional components for when you need to write more complex logic. Providing an escape hatch

Local Testing and Debugging

One significant pain point for ZSA was the difficulty of debugging Shopify email templates. Shopify lacks console logging for templates, so developers are left with Liquid’s {{ variable | json }} for debugging, requiring template deployment to Shopify for each iteration—tedious and unversioned.

With control over the build pipeline and a local preview server, we could pass Shopify data as JSON directly to the preview app. This setup enabled front-end debugging, where developers could inspect template variables in a structured tree view.

Additionally:

  • Litmus Integration: Added Litmus to test and validate templates across email clients and platforms.

Results & Impact

  • Improved Developer Experience (DX): Streamlined the template editing, preview, and debugging processes, making iteration faster and less error-prone.
  • Consistent Branding: Emails now reflect ZSA’s brand identity rather than relying on Shopify’s default styles.
  • Modular and Scalable Templates: Component-based architecture enables rapid template creation and updates.
  • Enhanced Debugging: Easier testing of complex logic within templates, thanks to the preview server.
  • Cross-Platform Testing: Integrated Litmus testing for reliable, consistent rendering across clients.

Reflections / Lessons Learned

  • Exploring Alternatives: React.email is a promising alternative to MJML, bringing more modern and flexible tools for email templating in React.
  • Considering Alternative Flows: With webhooks, we could bypass Shopify’s email system entirely, sending JSON data to an external email service for more control and reliability.