← Index | Strategy Document — "Your Routine" Module · Email Implementation
Strategy Document

"Your Routine" Module — Email Implementation

How to bring the PDP routine strip into every Klaviyo email · Project Routine

Email clients can't render modern CSS

The PDP routine module uses flexbox, border-radius, hover states, and JavaScript — none of which work in email. Gmail strips <style> tags entirely. Outlook uses Word's rendering engine. We need a different approach that achieves the same visual and strategic effect within these constraints.

Additionally, Klaviyo's show/hide logic works with profile properties only (not order history directly). So we need to sync routine state data to each customer's Klaviyo profile to drive personalisation.

Three approaches — with trade-offs

B. HTML table-based layout with conditional blocks

Build the 4-step strip as a nested HTML table with inline styles. Use Klaviyo's show/hide on individual table cells to swap content (owned vs missing state). Use dynamic images for product thumbnails.

Pros
  • Truly dynamic — adapts per customer in real-time
  • No image design overhead
  • Text remains indexable/searchable
  • Easier to maintain long-term
Cons
  • Tables break across email clients (especially Outlook)
  • 4-column table on mobile is a nightmare
  • Can't do dashed borders, rounded corners, or opacity in all clients
  • Show/hide on individual cells is fragile
  • High QA burden across 10+ email clients

C. Server-side image generation (dynamic)

Build a lightweight image API that generates the routine strip on-the-fly per customer. Pass profile properties as URL parameters, return a rendered PNG. Embed as a dynamic image in Klaviyo.

Pros
  • Truly personalised — unique per customer
  • Unlimited state combinations
  • One image block in Klaviyo, zero show/hide logic
  • Scales to any number of products/routines
Cons
  • Requires building + hosting an image API (Vercel/Cloudflare Worker)
  • Added infrastructure to maintain
  • Image generation latency at send time
  • Debugging is harder (image URL parameters)
  • Overkill if we only have 8-12 states
Recommendation: Start with Option A, plan for Option C Option A (pre-rendered images) gets us live fastest with zero infrastructure. The number of realistic customer states is manageable (~8-12 images). If we later need truly per-customer personalisation (e.g., showing specific product images from their order history), we can upgrade to Option C. Option B (HTML tables) is the worst of both worlds — fragile rendering AND high maintenance.

How the image strip approach works

1

Design the image variants

Create a set of 600px wide PNG images — one for each customer state. Each image shows the 4-step routine strip with the appropriate visual treatment (highlighted, owned, missing).

2

Sync routine state to Klaviyo profile properties

Use a Shopify Flow or webhook to write custom properties to each customer's Klaviyo profile: their routine type (clarity/radiance), which steps they own, and their progress (1/4, 2/4, etc.).

3

Use Klaviyo show/hide blocks to display the right image

Each email template contains multiple image blocks (one per variant), each with a show/hide condition based on the profile properties. Only one image renders per recipient.

4

Link each image to the right destination

The image links to the set PDP, concern landing page, or personalised routine page — depending on the customer's state.

Klaviyo profile properties we need

These custom properties need to be synced to each customer profile (via Shopify Flow, Klaviyo webhook, or a nightly sync script):

Property Type Values Source
routine_type Text clarity / radiance / none Routine Finder result or inferred from orders
routine_step_cleanse Boolean true / false Order history — has purchased Cleanser
routine_step_activate Boolean true / false Order history — has purchased Neutralising Gel, Brightly Serum, or Nightly Serum
routine_step_hydrate Boolean true / false Order history — has purchased Moisturiser or Moisturiser Intense
routine_step_protect Boolean true / false Order history — has purchased Daily Sunscreen
routine_progress Number 0 / 1 / 2 / 3 / 4 Count of owned steps
is_subscriber Boolean true / false Skio subscription status
has_bought_kit Boolean true / false Order history — has purchased any trial kit
has_completed_quiz Boolean true / false Octane AI / Routine Finder — set when quiz is submitted
Sync method Three data sources feed these properties: (1) Routine Finder completion — Octane AI (or your quiz tool) should fire a Klaviyo event or update profile properties directly when the quiz is submitted, setting routine_type, has_completed_quiz, and the recommended products. This is the earliest signal — it arrives before any purchase. (2) Shopify order events — a Shopify Flow triggered on "Order paid" updates the step booleans and progress count via the Klaviyo API. (3) Skio subscription events — sets is_subscriber. A nightly batch sync as a safety net catches anything the real-time triggers miss (refunds, cancellations, edge cases).

Image variants to design

We don't need an image for every possible combination of 4 boolean steps (which would be 16). Instead, we design for the realistic customer journeys. Two segments deserve special attention: quiz completers (~150-200/day who have a routine recommendation but haven't purchased) and single-product buyers (1/4 steps — the biggest routine expansion opportunity).

Variant Routine State Show/hide condition CTA destination
1. Cold (no data) Neutral All 4 steps shown generically, no highlighting. "Discover your routine" messaging. not routine_type AND not has_completed_quiz Routine Finder quiz
2. Quiz completer — Clarity Clarity Shows personalised Clarity routine with all 4 steps named. "Your recommended routine" header. Emphasis on the trial kit as entry point. No checkmarks — nothing purchased yet. has_completed_quiz == 1 AND routine_type == 'clarity' AND routine_progress == 0 Clarity Kit PDP (or Clarity Set PDP)
3. Quiz completer — Radiance Radiance Same as above but Radiance products has_completed_quiz == 1 AND routine_type == 'radiance' AND routine_progress == 0 Radiance Kit PDP (or Radiance Set PDP)
4. Kit buyer — Clarity Clarity "You've tried the kit" badge, arrow pointing to full-size set has_bought_kit == 1 AND routine_type == 'clarity' AND routine_progress < 4 Clarity Set PDP
5. Kit buyer — Radiance Radiance Same as above but Radiance products has_bought_kit == 1 AND routine_type == 'radiance' AND routine_progress < 4 Radiance Set PDP
6. Single product buyer (1/4) — Clarity Clarity 1 step with ✓, 3 dashed gaps. Strong "you've started — keep going" message. Progress ring shows 1/4. Highlights the NEXT most logical step to add. Price shows cost to complete. routine_type == 'clarity' AND routine_progress == 1 AND has_bought_kit != 1 Clarity Set PDP
7. Single product buyer (1/4) — Radiance Radiance Same as above but Radiance products routine_type == 'radiance' AND routine_progress == 1 AND has_bought_kit != 1 Radiance Set PDP
8. Partial routine (2 steps) — Clarity Clarity 2 ✓, 2 dashed gaps, progress ring 2/4. "Halfway there" message. routine_type == 'clarity' AND routine_progress == 2 AND has_bought_kit != 1 Clarity Set PDP
9. Partial routine (2 steps) — Radiance Radiance Same as above but Radiance products routine_type == 'radiance' AND routine_progress == 2 AND has_bought_kit != 1 Radiance Set PDP
10. Nearly complete (3/4) — Clarity Clarity 3 ✓ badges, 1 gap highlighted with "Just 1 step to go!" Strong urgency. routine_type == 'clarity' AND routine_progress == 3 Missing product PDP
11. Nearly complete (3/4) — Radiance Radiance Same as above but Radiance products routine_type == 'radiance' AND routine_progress == 3 Missing product PDP
12. Full routine (no sub) Both All 4 ✓, "Subscribe and save 20%" CTA routine_progress == 4 AND is_subscriber != 1 Flawless365 page
13. Active subscriber Both All 4 ✓, "You're on Flawless365" badge, referral CTA is_subscriber == 1 Referral page or account

That's 13 images total. The two new priority segments:

Quiz completers (variants 2-3) — ~4,500-6,000/month These people took 60 seconds to answer questions. They have intent. They have a recommendation. They just haven't bought yet. Showing them "Your recommended Clarity routine" with all 4 named products and a "Start with the kit — £37" CTA in every email is the highest-value nudge we can deliver. This image should feel aspirational — "this is what we built for you" — not transactional. It's the email equivalent of the Routine Finder results page.
Single product buyers (variants 6-7) — the retention sweet spot A customer who bought one product (e.g., just the Cleanser) is at the biggest inflection point. They either expand into the routine or they churn after one repurchase cycle. Showing "1/4 complete" with 3 empty gaps creates strong completion motivation. The CTA should frame the next step specifically: "You have Cleanse covered — Activate is your next step" rather than a generic "complete your routine." This is where the individual step-level boolean properties pay off: if we know they own Cleanse specifically, the image can highlight Activate as the logical next addition.
Pragmatic simplification for v1 For the partial/single-product variants (6-11), v1 uses the progress count rather than specific step combinations. "1 of 4 steps complete" with a generic visual is effective. If we want step-specific accuracy (e.g., showing exactly which step they own and which to add next), we'd need the individual boolean properties AND more image variants — or upgrade to Option C (dynamic image generation). The step-level booleans are worth syncing from day one though, even if v1 images only use the progress count, because they unlock future personalisation.

How it looks in email

These mockups simulate how the routine strip renders as a single image block within an email. Toggle between states:

Show/hide logic in practice

In the Klaviyo template editor, you'd add 13 image blocks stacked vertically. Each one has a show/hide condition. Only one renders per recipient. Here's the logic for each (showing Clarity examples — duplicate with 'radiance' for Radiance variants):

// Block 1: Cold (no quiz, no purchase data)
Condition: not person|lookup:'routine_type'
         and not person|lookup:'has_completed_quiz'
// Fallback — shows when we know nothing about them

// Block 2: Quiz completer — Clarity (no purchase)
Condition: person|lookup:'has_completed_quiz' == 1
         and person|lookup:'routine_type' == 'clarity'
         and person|lookup:'routine_progress' == 0
// ~150-200 quiz completions/day, high intent, no conversion yet

// Block 4: Kit buyer — Clarity
Condition: person|lookup:'has_bought_kit' == 1
         and person|lookup:'routine_type' == 'clarity'
         and person|lookup:'routine_progress' < 4

// Block 6: Single product buyer (1/4) — Clarity
Condition: person|lookup:'routine_type' == 'clarity'
         and person|lookup:'routine_progress' == 1
         and person|lookup:'has_bought_kit' != 1
// Key retention segment — 1 product, 3 gaps

// Block 8: Partial routine (2/4) — Clarity
Condition: person|lookup:'routine_type' == 'clarity'
         and person|lookup:'routine_progress' == 2
         and person|lookup:'has_bought_kit' != 1

// Block 10: Nearly there (3/4) — Clarity
Condition: person|lookup:'routine_type' == 'clarity'
         and person|lookup:'routine_progress' == 3

// Block 12: Complete — not subscribed
Condition: person|lookup:'routine_progress' == 4
         and person|lookup:'is_subscriber' != 1

// Block 13: Active subscriber (overrides all)
Condition: person|lookup:'is_subscriber' == 1
Evaluation order matters Klaviyo evaluates all blocks independently — it doesn't "pick" one. So conditions must be mutually exclusive. Key exclusions: quiz completer requires routine_progress == 0 (no purchases); kit buyer includes has_bought_kit == 1 while single-product and partial checks exclude it with has_bought_kit != 1; the subscriber block uses is_subscriber == 1 which overrides all purchase states. Test with profiles at each state before going live.

Where this module sits in each email

The routine strip should appear in a consistent position across all emails — creating a persistent, recognisable element that reinforces the routine concept over time.

Email type Position Rationale
Welcome flow After main CTA, before footer Introduces the system concept early. "Here's how the routine works."
Post-purchase After order confirmation content Shows their progress. "You're 1/4 of the way there."
Campaigns / promos After main campaign content, before footer Persistent reminder. Every campaign email subtly nudges routine completion.
Pre-charge (F365) Below delivery details Subscriber variant. "Your routine is on autopilot" + referral CTA.
Winback / re-engagement Primary content position Shows what they've built and what's missing. "Your routine misses you."
Browse/cart abandonment Below abandoned product Contextualises the abandoned product within the system.
Build it as a reusable "Universal Content" block In Klaviyo, create this as a saved template section (or use Klaviyo's Universal Content feature if available on your plan). This means you design it once and include it across all emails. When you update the images or logic, it updates everywhere automatically. This is critical — you don't want to edit 30 individual email templates when you add a new product variant.

Option C: Dynamic image generation

If you outgrow the pre-rendered image approach (e.g., you want to show the exact products a customer owns with their actual product images), here's how the dynamic image API would work:

// Klaviyo dynamic image URL
https://routine-img.drsamsbunting.com/strip
  ?routine=clarity
  &steps=1,1,0,0        // owned: cleanse, activate
  &progress=2
  &subscriber=false

// In Klaviyo, the URL would be:
https://routine-img.drsamsbunting.com/strip
  ?routine={{ person|lookup:'routine_type' }}
  &steps={{ person|lookup:'routine_step_cleanse'|default:'0' }},{{ person|lookup:'routine_step_activate'|default:'0' }},{{ person|lookup:'routine_step_hydrate'|default:'0' }},{{ person|lookup:'routine_step_protect'|default:'0' }}
  &progress={{ person|lookup:'routine_progress'|default:'0' }}
  &subscriber={{ person|lookup:'is_subscriber'|default:'0' }}

The API (a Vercel edge function or Cloudflare Worker) would receive these parameters and return a rendered PNG using something like Satori (from Vercel) or Puppeteer. One image block in Klaviyo, zero show/hide logic, infinite personalisation.

When to upgrade to Option C Only worth building if: (1) you add more routines beyond Clarity/Radiance, (2) you want to show actual product photography per customer, or (3) you find the 9-variant approach limiting for a specific use case. For 2 routines and ~9 states, pre-rendered images are perfectly sufficient and much simpler to maintain.