When Blooms at London outgrew WordPress, the brief was clear: rebuild the entire digital operation from scratch — three independent brands, one shared backend, and a team of non-technical florists who needed to manage it all without developer support.
This is how we built it.
The Problem With WordPress
The Blooms group operates three storefronts: Blooms at London, Epsom Flowers, and On The Hill Flowers. Each has its own brand, its own product range, and its own customer base. But operationally, they share a single warehouse and fulfilment team.
On WordPress + WooCommerce, this meant:
- Three separate admin panels to log into
- Manual inventory reconciliation across sites (spreadsheets, phone calls)
- No shared order view — the fulfilment team checked three dashboards separately
- Page load times that failed Core Web Vitals on mobile
- Every new product feature required a developer and a plugin
The team was spending more time managing software than running the business.
Architecture Decision: Medusa.js v2
We evaluated Shopify Plus, BigCommerce, and a fully custom solution before landing on Medusa.js v2. The decision came down to three things:
- Native sales channel architecture — Medusa's sales channels are a first-class primitive. You can have one product published to all three storefronts, or just one, with channel-specific pricing and availability. This solved the shared-inventory / independent-brand requirement without any custom code.
- Fully programmable — Medusa v2's workflow engine let us build complex custom logic (multi-recipient checkout, WhatsApp notifications, VAT invoicing) as first-party extensions rather than third-party plugins.
- Self-hosted — Deployed on Railway with a managed PostgreSQL database. No platform lock-in, no transaction fees, and infrastructure costs that scale predictably.
The Backend: One Platform, Three Brands
The Medusa v2 backend runs on Railway as a containerised Node.js process connected to a PostgreSQL database. All three storefronts make API requests to the same backend.
Sales Channels
Each storefront has its own Medusa sales channel. A product can be:
- Shared across all three (e.g. a seasonal mixed bouquet available everywhere)
- Exclusive to one brand (e.g. a bespoke product created specifically for one location)
- Available in two channels but not the third
Stock is deducted from the shared inventory pool regardless of which storefront placed the order. A single warehouse view in the admin dashboard shows combined stock levels and flags low-inventory alerts.
Custom Admin Dashboard
We built a bespoke admin interface on top of the Medusa Admin SDK. The Blooms team's day-to-day workflow drove every design decision:
- Product management — Edit images, descriptions, pricing, and lead times per product. Publish or unpublish a product to specific sales channels without touching any code.
- Featured products — Each storefront's homepage has a "Featured" section. Staff can drag-and-drop which products appear there from within the admin panel.
- Order management — A unified order view across all three channels. Filter by storefront, date range, status, or delivery date. Mark orders as dispatched and trigger the transactional email flow with one click.
- VAT invoicing — Generate and download a VAT-compliant PDF invoice for any order. This was a hard requirement — the business is VAT-registered and needed invoices that met HMRC formatting requirements.
- Reporting — Revenue by storefront, order volume trends, average order value, and top-performing products. All computed from the Postgres database on demand, no third-party analytics required.
The Storefronts: Three Next.js Apps
Each brand has an independent Next.js (App Router) storefront. They share a design system component library but have entirely separate branding, routing, and deployment pipelines.
Server-side rendering with edge caching means every product and category page loads fast from the first byte — a material improvement over the previous WooCommerce stack, which was generating pages dynamically on a shared hosting server.
Multi-Recipient Checkout
This was the most technically interesting feature on the project.
Florists sell gifts. A meaningful portion of Blooms' orders are from customers sending flowers to multiple people — different addresses, different delivery dates, different personal messages — in a single transaction.
WooCommerce couldn't do this without extensive plugin workarounds. We built it as a Medusa workflow extension: a custom checkout flow that allows a cart to contain line items each with their own recipient data. The order is split into fulfilment groups by recipient at the point of payment, and the fulfilment team sees each sub-order independently in the admin panel.
From the customer's perspective: add products to cart, assign recipients, write individual messages, pay once. From the team's perspective: each delivery shows up as a discrete fulfilment task with all the recipient details on one screen.
Transactional Email: The Full Order Lifecycle
We built a transactional email system using Resend with per-brand HTML templates. The lifecycle:
- Order placed — Confirmation email with order summary, estimated delivery date, and a VAT receipt if applicable
- Payment confirmed — Fires if payment processing is asynchronous (e.g. bank transfer for commercial accounts)
- Order dispatched — Sent when the fulfilment team marks the order as dispatched in the admin panel
- Delivery completed — Triggers a follow-up email 2 hours after the confirmed delivery time, requesting a Google review
Each email is branded per-storefront. The same Medusa event pipeline powers all three, but the email templates render with the correct brand colours, logos, and copy based on which sales channel originated the order.
WhatsApp Order Notifications
The fulfilment team had a simple requirement: they wanted to know about new orders immediately, without checking the admin panel.
We integrated the WhatsApp Business API to send a formatted message to the team's shared WhatsApp group the instant a new order is placed. The message includes:
- Order number and storefront
- Customer name and delivery address
- Requested delivery date and time slot
- Product names and quantities
- Any special instructions left by the customer
This eliminated the lag between order placement and fulfilment acknowledgement. Missed orders dropped to zero.
Results
The platform went live on Railway with zero downtime migration from the existing WordPress sites. Product catalogues, customer data, and order history were all migrated cleanly.
Key outcomes:
- Three storefronts, one admin panel — The operations team logs into a single dashboard for everything
- Faster storefronts — Server-rendered Next.js pages with edge caching vs. dynamic WordPress page generation
- Multi-recipient checkout — A capability that simply didn't exist before, and is now one of the most-used features
- WhatsApp notifications — Zero missed orders since launch
- 100+ products migrated — Full catalogue moved with all variant data, images, and inventory counts intact
Blooms at London continues to hold a 5.0-star Google rating across 400+ reviews.
If you're considering migrating off WordPress to a headless commerce stack, or you need a multi-brand commerce platform built on Medusa.js, get in touch — this is exactly the kind of work we do.