Email Engine — Full Overhaul & Learning Loop
The email system has been rebuilt from the ground up. A/B subject variants, Klaviyo personalisation tokens, VIP segment copy, plain text versions, and a full closed-loop intelligence system that learns from every campaign result and feeds that knowledge back into the next generation cycle.
- A/B Subject Variants — every email now generates 3 subject line options. V1 is the primary send, V2/V3 shown in the review panel for manual or future auto-split testing.
- Klaviyo Personalisation Tokens — emails now include
{{ first_name|default:'there' }}and other liquid tags, injected at generation time from real Shopify customer data. - VIP Segment Copy — each email includes a dedicated VIP opener paragraph. The review panel surfaces this separately with an "Early Access" badge.
- Plain Text Version — every generated email now includes a clean plain text fallback for inbox deliverability.
- Email Learning Loop — 48h post-send, Claude Haiku interprets open rate, click rate, revenue, and bounce rate vs industry benchmarks. Learnings are written to
campaign_learningsand injected into every future email generation prompt. - Email Intelligence UI — new email tab in the campaign review panel: timeline selector (Day 1 / Day 3 / Final Day), sidebar with A/B variants, VIP opener, email client preview, and execute to Klaviyo button.
- Klaviyo API flow corrected — was incorrectly POSTing to
/campaign-messages/(creates a new message). Now correctly: POST /campaigns/ → GET messages/ → PATCH the auto-created message. send_strategy.datetimeplacement — datetime was at top level ofsend_strategy. Must be insideoptions_static. Campaigns were silently failing to schedule.- Segment IDs rejected by Klaviyo — Klaviyo campaigns only accept list IDs in
audiences.included[]. Segment IDs were being sent, causing campaign creation failures. Audience mapping rewritten to emit list IDs only. - Metrics cron never ran — status was being set to
'scheduled'on execute but cron queriedWHERE status = 'sent'. Status changed to'sent'. - Wrong DB column names in learning writes —
lift_percent,channel,insights_jsondon't exist. Correct columns arelift_pct,channels_used(JSON array),learning_notes. - INNER JOIN dropped campaigns when Klaviyo disconnected — if a client disconnected their Klaviyo account after a campaign was sent, the metrics cron would silently lose those records forever. Changed to LEFT JOIN with explicit handler.
- Broken CTA link —
{% klaviyo_cta_url %}is not a valid Klaviyo Liquid tag. Replaced withbrandConfig.storeUrl. - Ralph prompt queried wrong column —
channel='email'andl.insights_jsonreferenced non-existent columns in email learning context block. Fixed to use correct schema.