Most platforms in 2018 treated notification emails as a chore. A subject line, a button, a templated body, ship-it. The emails went out. Nobody opened them twice.

Swirl’s notification stack was no different — until I argued we should treat it as a product surface.

The setup

Admins had access to live campaign data inside the console. The console was the polished surface. The notification emails — your weekly campaign report is ready, your beacon health summary — were nested-table HTML with static screenshots already two weeks stale by the time they hit the inbox. Nobody clicked.

The data was already in the platform. The blocker wasn’t tech, and it wasn’t ops. It was that email was treated as out of scope for design. Most developers reach for a basic template and move on. I’d spent years coding high-converting emails before this, so giving up at the edges didn’t sit right.

The decision

Two bets, made at the same time.

Bet one: MJML. A new framework at the time — React-for-emails was the shorthand. Component-driven, responsive by default, compiled to the nested-table HTML that email clients still require under the hood. It let me build email layouts the same way the rest of the product was built — shared components, predictable spacing, a real type scale — instead of fighting Outlook through hand-written <table> tags.

Bet two: dynamic charts via the Image Charts API. Rather than screenshotting the console and pasting a static image, the platform queried its own data at send time and encoded the results directly into an Image Charts URL — data values, labels, dimensions, and colors as URL parameters. The API returned a rendered chart image. That URL went straight into the MJML template as an <img src>. The recipient opened the email and saw a chart built from their actual numbers at the moment it was sent, not a two-week-old screenshot.

Click the chart, and the destination wasn’t a generic landing page — it was the console view that matched it, already scoped to the same time range and segment. What they saw in the inbox lined up with what they saw on screen.

The CTA-from-data swap took more convincing internally than the chart pipeline did. Live charts looked impressive; automating the call-to-action based on what those charts showed was the part stakeholders worried about. The answer was that the CTAs were data-driven, not data-generated — a lookup table mapped report signals to a curated set of next actions. The system never invented copy; it picked from copy a human had written.

What shipped

Three weekly digest reports, each scoped to a different audience need:

  • Campaigns Overview — reach, impressions, and engagement across active campaigns for the week
  • BLE Location Health — beacon status across the fleet: how many healthy, how many require maintenance, coverage gaps
  • InStore Traffic Performance — foot traffic patterns, dwell time, and store-by-store comparison

Each report mailed every Monday with data from the previous week. An admin subscription modal let each user toggle exactly which digests they wanted and preview what the email would look like before saving — the inbox preview rendered live in the modal.

Email Subscriptions modal — left side shows Weekly Digest toggles for BLE Location Health (on), Campaigns Overview, and Traffic Overview, plus Real Time Notification toggles for Analytics, Applications, Beacons, Campaigns, and Creative Approvals; right side shows a live inbox preview of the BLE Location Health email with an Overall Location Health donut chart showing 110 beacons require maintenance
Email Subscriptions panel. The toggle controls which digest streams each admin receives; the inbox preview on the right renders the actual email live so there are no surprises. The Location Health email is already showing a donut chart — built from platform data at the time of preview, the same way it would render at send time.
Campaign Performance Last Week email — gauge chart showing engagement rate, KPI tiles for reach, notification impressions, and creative views at 1,000,000 each, active campaigns listed below, and a Campaigns Ending This Week sidebar; shown at mobile and desktop sizes
Campaign Performance. Engagement rate as a gauge, reach and impression counts as KPI tiles, active campaigns surfaced inline. Each metric was pulled from the platform at send time via the Image Charts API and encoded into the email as a rendered image — the same query the console ran, delivered to the inbox.
Overall Location Health email — two donut charts showing beacons requiring maintenance vs healthy, Locations Requiring Maintenance list showing Mall of America MN with beacon status icons; shown at mobile and desktop sizes
BLE Location Health. Fleet health at a glance — beacons requiring maintenance surfaced by store, with drill-down links back to the Fleet Management console. The donut charts were Image Charts API renders: beacon counts from the platform, encoded as URL parameters, returned as images at send time.
Traffic Performance Last Week email — line chart showing Unique Visitors vs Reached Visitor week over week, KPI tiles showing 78% up week-over-week, 1,999,999 unique visitors, stores ranked by visits up and visits down, Login to Swirl CTA at the bottom; shown at mobile and desktop sizes
InStore Traffic Performance. Unique visitors, reached visitors, and week-over-week delta in one view. The store-level breakdown — visits up, visits down — gave location managers a reason to click through. The Login to Swirl CTA at the bottom was the direct line back to the console scoped to their store.

What I’d take with me

The polish people associate with the app gets thrown out at the edges — login emails, notification emails, error pages, billing receipts. Those are the places the brand meets the user when the app isn’t open.

The Image Charts API made the chart generation almost trivially easy once the data pipeline was wired up — the complexity was deciding which data to surface and what a marketer should do next. The engagement numbers moved. But the more durable lesson was that the design system should run all the way to the edges. If the email feels like a different product, it’s because the team treated it like one.