zsty.us

Before / After · Case Study

Big Moose Hemp — Wix → headless Medusa ahead of the federal hemp ban

  • Hemp / CBD

P.L. 119-37 (federal hemp ban, Nov 12 2026) removes roughly 86% of Big Moose Hemp's current catalog from legal sale on day one. Wix can't support the platform-level work needed to survive that pivot — V1 catalog, deprecated blog API, locked-in schema. Rebuilt on Medusa 2.x + Next 15 with a three-phase strategy (liquidate → sunset → post-ban) baked into the architecture so the cutoff is driven by per-SKU total-THC data, not a content edit. What started as a survival migration became the operating spine for the whole brand: a live Medusa-backed admin, a consent-stratified retention engine, a verified transactional email + SMS chain, and a 24/7 Slack-fed AI approval queue where agents draft and only the owner ships.

When the legal catalog shrinks 86% on a fixed date, the platform has to know which SKU is on which side of the line.
🔒jackiej.events
jackiej.events — modern site

Receipts — measured

Numbers that moved.

0%
Catalog removed by P.L. 119-37 on Nov 12 2026
was 0%
0
SKUs migrated off Wix V1
was 0
0
Legacy Wix contacts audited + stratified
was 0
0
Bulk-import contacts quarantined CRM-only (TCPA/CAN-SPAM safe)
was 0
0
Transactional email types verified end-to-end
was 0
43
Manual SKU sweeps required on Nov 12
was 43
  • 43 SKUs migrated off Wix V1
  • 3-phase compliance strategy in code, not in spreadsheets
  • Nov 12 2026 cutoff driven by per-product total-THC threshold
  • Two-rail payment processor (see /before-after/bmh-payment-rails)

The pixel diff

Overview, then the walkthrough.

Left: everything the legacy site shipped — frozen in place. Right: an auto-scrolling tour of the rebuild, from hero to booking. No slider to fight with.

Before — the overview
🔒djjackiej.com
Before — the overview

This is everything the legacy site had to offer above the fold. No pricing, no calendar, no booking — the funnel ended at a mailto.

After — scroll through what shipped
🔒jackiej.events
After — scroll through what shipped

Auto-scrolls through the modern site so you can see the booking surface, the package grid, the pricing table, and the calendar inline — without leaving this page.

The four beats

Problem · Insight · Build · Outcome.

Scroll past the legacy frame. The four beats land in order. At the end, the modern site fades into the same window.

🔒djjackiej.com
Legacy
01 · Problem

A platform that's about to lose 86% of its legal catalog on a fixed date.

Wix's V1 catalog couldn't support the structural work needed to survive the November 12 2026 federal hemp ban. The platform also couldn't expose the payment-policy controls the rebuild needed (see Global Merchant Processing case study).

02 · Insight

Make the cutoff a data fact, not a content edit.

Total-THC per SKU is the bright line. Compute it once at migration, store it on the product, query it at runtime. The Phase 2 cutoff becomes a flag flip, not a 43-SKU hand sweep at midnight on Nov 11.

03 · Build

Medusa 2.x backend + Next 15 storefront + total-THC computed pipeline.

Headless rebuild: Medusa 2.x backend (Postgres 16 + Redis), Next 15 storefront. Migration script computes total-THC for every Wix SKU before it lands in Medusa. AlpineIQ + Klaviyo + Bland keep the messaging surfaces running. Two-rail payment abstraction (see Global Merchant Processing).

04 · Outcome

A platform where the cutoff is one query, not 43 manual delistings.

On Nov 12 2026, the database query that drives storefront visibility flips one threshold and the non-compliant SKUs go invisible at the same moment. The payment rail swaps in parallel (Clover → Stripe via the module abstraction). No content emergency.

05 · Compounding

The same backend now runs marketing, sales, CS, and a 24/7 approval crew.

Once the catalog lived in Medusa, every adjacent surface got to share it. Retention reads the customer table; transactional comms fire off order events; an AI approval queue lets staff and agents propose content and design changes around the clock while the owner keeps the only key that ships anything live. Each agent runs a narrow, named job on real data and hands edge cases to a human queue — so the system gets more capable without the agents losing the plot.

🔒jackiej.events
Modern

Architecture

One chain replaced by another. Receipts above.

Old stack
  • Wix V1 catalog + deprecated blog API
  • Wix subscriber list (locked in)
  • Wix dashboard (no API, no mobile ops)
  • Square via WIX-PAY-PRO (single rail)
  • No automation, no approval workflow
New stack
  • Medusa 2.x + Neon Postgres + Upstash Redis
  • Customer table → AIQ v2 SMS + Resend/SES/Stalwart email
  • Live JWT-bridged admin (orders, timeline, mobile PWA)
  • Two-rail processor abstraction (Clover → Stripe/Aeropay)
  • Slack-ingested AI approval queue, owner-gated promotion

What changed

Grouped by what kind of system shipped.

Each claim ships with concrete evidence — env vars, table names, cadence chips. No marketing fluff.

Agent backbone

Per-SKU total-THC computed at migration time, drives the Phase 2 cutoff

Each SKU's total-THC fraction is computed once at Wix → Medusa migration time and stored alongside the product. The Phase 2 Sunset cutoff (Nov 11 2026) is a database query — `WHERE total_thc <= 0.003` — not a hand-curated SKU list. No room for a missed delisting on Nov 12.

Retention

Three-phase strategy baked into the schema

Phase 1 (Liquidation, Apr 19 → Oct 31): full catalog on current rails. Phase 2 (Sunset, Nov 1-11): only compliant SKUs visible, Stripe + Aeropay rails. Phase 3 (Post-ban, Nov 12 →): adaptogen coffee + CBD wellness + compliant smokable hemp. The phase is a runtime flag; product visibility, payment rail, and compliance copy all consult it.

Real-time push

AlpineIQ + Klaviyo + Bland — retention infra survives the catalog pivot

SMS (AlpineIQ, cannabis-compliant), email (Klaviyo), and outbound wholesale voice (Bland) all read from the Medusa customer table, not from the Wix subscriber list. When the catalog shrinks, the messaging surface keeps running — same audience, different SKUs.

Retention

23,924 legacy Wix contacts — stratified by consent, not blast-imported

The easy move is to dump every Wix contact into the marketing list. The honest one is to audit them. 23,924 contacts pulled from the old store, classified into five legal tiers (subscribed / past-purchaser / site-member / bulk-import / unsubscribed). Only 116 cleared as marketable; 16,712 bulk-import leads were written CRM-only with metadata.crm_only=true so no Klaviyo/AIQ sync ever touches them. CAN-SPAM and TCPA compliance is a column, not a promise — and the suppression list for the red tier is written before a single send.

  • 23,924 contacts audited via the Wix Contacts v4 API (paginated)
  • 116 green-tier (4 subscribed + 112 verified purchasers) → marketable
  • 16,712 yellow_bulk quarantined crm_only=true; 0 red-tier ever written
  • Stratifier: scripts/wix-customer-import-stratified.ts (5-tier classifier)
Agent backbone

A Slack-driven, human-gated approval pipeline — agents draft, the owner ships

Shop workers and superusers queue content and design work into Slack; a 5-minute cron ingests it, an AI classifier routes it into one of eight action kinds, and nothing reaches a customer or production without a human tap. Two lanes: content drafts (SMS / email / blog) wait on the brand owner; site-design changes assemble a Vercel preview and wait on the account owner to promote. The agents operate on real store data and surface edge cases to a review queue instead of guessing — the discipline that keeps them from drifting.

  • Neon bmh-storefront-ops: triage_queue, superuser_uploads, design_requests, approval_audit
  • canPromoteToProd() = owner-only hard gate, re-checked server-side on every ship action
  • Every state transition written to an append-only approval_audit table
  • Structurally cloned from the jackiej.events triage architecture, not its content
Real-time push

Order → confirmed → shipped → delivered, verified end-to-end to a real inbox

The transactional spine isn't a stub. An order fires a confirmation email, a shipping email with carrier tracking, and a delivery email — each rendered, sent, and confirmed landing in a real IMAP inbox. The send path is Resend → AWS SES → a self-hosted Stalwart mail server, all on verified domains. Customer SMS rides AlpineIQ's v2 transactional endpoint, consent-gated against AIQ's own opt-out suppression so it never texts a stranger.

  • 3/3 lifecycle emails delivered + IMAP-confirmed (Resend → SES → Stalwart)
  • AIQ migrated off dead v1.1 piiSend/piiLookup → POST /api/v2/sms + GET /api/v2/loyalty/lookup
  • Order-placed webhook fans out: email + consent-gated SMS + AIQ retention tag
  • SES bounce/complaint → identity suppression, fail-closed
Design

An operating surface, not just a storefront — live Medusa-backed admin on mobile

The old store had a Wix dashboard. The rebuild has an admin that reads live from Medusa over a Bearer-JWT bridge: orders with payment + fulfillment status pills, a per-order activity timeline (placed / paid / shipped / delivered), search and filters, and an iOS-style bottom nav so it runs as an installable PWA from a phone. A customer preferences page round-trips consent to the cross-brand OG.Life identity layer so a shopper never opts in four times.

  • /admin/orders renders 'Live from Medusa' over admin.bigmoosehemp.com (JWT-authed)
  • Per-order timeline + status pills + search/filter; mobile bottom-nav PWA
  • /preferences/[token] ↔ OG.Life biz-preferences webhook (HMAC-signed)
  • Backend recovered from a 4-day outage onto a fresh Postgres with zero meaningful data loss

While she sleeps.

Autonomous surfaces

The agent backbone keeps the brand earning between gigs. Jackie approves; the system runs.

  • Tags SKUs by phase eligibility at migration time

    once per SKU at migration; queried on every page render

    Per-SKU total-THC is computed during the Wix → Medusa migration and stored on the product. Phase visibility is a single query at runtime.

  • Keeps the messaging surfaces decoupled from the catalog

    continuous

    AlpineIQ (SMS), Klaviyo (email), and Bland (outbound voice) all read from the Medusa customer table. The audience survives the catalog pivot.

  • Ingests Slack into a triaged approval queue

    every 5 minutes

    Staff and superusers post content/design work into Slack; a cron pulls new messages, an AI classifier sorts them into eight action kinds and two review lanes, and the items wait for a human approval — content for the brand owner, design for the account owner.

  • Fires the transactional + retention chain on every order

    per order; instant

    An order event fans out: confirmation/shipping/delivery email (Resend → SES → Stalwart), consent-gated AIQ SMS, and a retention tag write (bmh_customer, order_placed_YYYY_MM) so segmentation builds itself.

  • Suppresses bounced + complained identities automatically

    continuous, event-driven

    SES bounce/complaint events route to an identity-suppression handler that fails closed — a hard-bounced or complaining address can never be re-marketed, no human in the loop required.

← All rebuilds

Big Moose Hemp — Wix → headless Medusa ahead of the federal hemp ban — zsty.us