home / projects / transactional-emails
Case 02 / 06 · Content-Type: multipart/alternative
The emails nobody praises — and everybody notices when they break.
Order confirmations, password resets, shipping updates: transactional email is infrastructure. This system replaced a drawer of one-off templates with an MJML component library — so every trigger renders consistently, and a change to the footer is one commit, not twelve.
- Project
- Email infrastructure
- Role
- Email developer
- Timeline
- 2024 — present
- Stack
- MJML · HTML · Litmus · Postmark
The brief
Transactional messages are the highest-open-rate email a product sends, and the least glamorous to maintain. The starting state is familiar to anyone who has inherited an email stack: near-identical templates that had drifted apart — different footers, different button code, different dark-mode behaviour — because each had been edited in isolation. The brief: one system, every trigger, no drift.
The constraints
- Rendering targets that disagree with each other. Outlook for Windows still uses Word's engine; Gmail clips at 102KB and strips
<style>in some contexts; dark mode is implemented three different ways across clients. - Personalisation happens at send time. Templates carry merge fields and conditional blocks, so the compiled HTML has to stay valid around whatever the ESP injects.
- Accessibility is non-negotiable for receipts — order tables must read as tables to screen readers, while layout tables must not.
- Non-developers touch copy. Content edits shouldn't require understanding ghost tables.
The build
MJML as the abstraction layer. Writing <mj-section>/<mj-column> instead of nested tables moves Outlook conditional comments and hybrid-layout boilerplate into the compiler, where they're generated correctly every time. On top of it, a small component library: header, footer, order-summary table, bulletproof button, spacer system — each defined once, included everywhere.
- Shared design tokens: colours, type scale and spacing live in one attributes file, so brand adjustments propagate through every template on the next build.
- Semantic receipts: the compiled order table keeps real
<th>semantics while everything structural isrole="presentation"— the same split you can inspect in my order-confirmation template. - Dark-mode strategy:
color-schememetas,prefers-color-schemeoverrides, and[data-ogsc]duplicates for Outlook.com's forced repaint — encoded once in the base layout. - A Litmus pass as the definition of done: the client matrix that matters here is Gmail (web + apps), Outlook Windows, Apple Mail/iOS and Samsung Mail. A template isn't shipped until the matrix is green.
The outcome
New transactional templates start from components, not blank files — assembly instead of authorship. Cross-client bugs get fixed once, in the component, and disappear from every template at the next compile.