Solutions Services Work About Contact
Case Study · E-commerce

Lunula Botanique

Headless e-commerce for a natural-cosmetics brand — B2C + B2B, an AI selection assistant, Polish payments, and Instagram automation. Built from scratch, architecture to deployment, on a reusable white-label foundation.

Year
2026
Type
Headless commerce · WordPress → headless migration
Role
Product consulting · Full-stack (architecture, backend, frontend, integrations, deployment)
Next.js 15 Medusa.js v2 TypeScript PostgreSQL Tailwind CSS Claude API Paynow (BLIK) InPost Railway Cloudflare
lunulaoil.pl
Lunula Botanique homepage — biocompatible skincare hero and product showcase
Homepage · scroll inside the frame to explore ↕

A store shaped to the sales model, not the other way around.

Lunula Botanique is a "biocompatible skincare" brand — cosmetics whose ingredients are recognizable to the skin. The catalog is unusual for a typical store: alongside physical products (oils, serums, creams, ritual soaps) there are workshops and digital products, and sales run on two tracks — retail (B2C) and wholesale to salons (B2B).

Off-the-shelf platforms (Shopify, WooCommerce) couldn't handle these requirements without compromises and costly plugins. We chose a headless architecture — a decoupled Medusa.js v2 backend and a Next.js 15 storefront — for full control over business logic, design, and integrations. As a bonus, the whole thing takes shape as a white-label-ready template for future brands.

My approach — consulting, not just implementation

Some of the store's strongest features weren't in the brief — I proposed them, and the client adopted them: the AI selection assistant, the bidirectional ingredient lexicon, and the Instagram comment-to-DM automation. Not "ticking off a spec," but product thinking — pointing out where technology would genuinely lift sales, education, and acquisition, then delivering each idea end to end.

Seven requirements an off-the-shelf store couldn't meet cleanly.

01

Two pricing models in one store — gross prices for retail, net prices for salons (B2B), with a separate wholesale price list.

02

The Polish payments market — a native integration with the Paynow gateway (BLIK), not a generic Stripe setup.

03

Content as a differentiator — the brand is built on ingredient education; the store needed an ingredient lexicon linked both ways with products (and the SEO value that comes with it).

04

Clean migration & domain cutover — replacing the brand's existing WordPress site on the same domain, seamlessly, with SEO rebuilt properly on the new platform.

05

Help with choosing — a knowledge-driven catalog (skin type, concerns, face vs. body) where the customer needs guidance from need to product, not a wall of categories.

06

Acquiring sales from Instagram — a comment under a post should automatically trigger a product link sent in a private message.

07

Brand scalability — a foundation that can be quickly "re-skinned" for another brand.

Custom modules instead of plugin compromises.

Brand-specific logic lives in custom Medusa modules, keeping the platform core clean and upgradable. Nine building blocks:

01

B2C + B2B in one system

A "Salon/B2B" customer group with a separate price list. Price presentation adapts to context — gross for retail, net for businesses — with correct VAT breakdown in cart and checkout, and customer-type detection on the API side. Salons get a dedicated "For Salons" landing page with a retail-vs-wholesale price comparison and a partner CTA.

02

A Polish checkout flow

A custom Paynow integration (HMAC verification, idempotency keys, webhooks, status polling) supporting inline BLIK with no redirect, plus classic transfer/card. On top: InPost parcel-locker selection via Geowidget, in-person pickup, and free shipping above a threshold with a progress-bar nudge.

03

Ingredient lexicon (content ↔ store)

A custom data module: every ingredient has its own page (name, Latin name, properties, source) and a list of products that contain it — with add-to-cart right there. Product pages link back to the lexicon. One coupling that works as an SEO hub, an education tool, and natural cross-sell at once.

04

AI selection assistant (Claude)

A multi-step wizard asks about skin type, concerns, and area, then uses the Claude model to pick from the store's real catalog — grounded in brand philosophy, constrained by hard rules (face vs. body, exactly three items, advisor's tone). Recommendations never hallucinate products and lead straight to the cart.

05

Instagram automation (comment → DM)

The most elaborate module — a full "comment a keyword, get a link in a message" flow:

  • keyword / regex / exact matching (unit-tested)
  • admin-managed triggers (post + keyword → product)
  • compliance: STOP opt-out, rate limiter, Meta's 24h window with public-reply fallback
  • Meta OAuth + webhooks with HMAC verification
  • a dedicated admin panel with logs & analytics
06

White-label from the foundations

Brand (name, logo, taglines), colors (CSS variables), typography, and feature set are driven by a single config and feature flags (B2B, subscriptions, workshops, IG bot, newsletter). Spinning up another store under a different brand is a config change — not rewriting code.

07

Performance, deployment & privacy

Hosted on Railway, media on Cloudflare R2. Backend-driven revalidation: a subscriber listens to Medusa events and refreshes the storefront cache — admin changes appear almost instantly, with no rebuild. Analytics via GA4 with Consent Mode v2 and a custom GDPR banner; transactional email through a custom Resend module.

08

SEO from the foundations

A dynamic sitemap.xml (products, categories, collections, lexicon), JSON-LD structured data (Organization, WebSite, Product, BreadcrumbList), and dynamic metadata + OpenGraph images per page. The ingredient lexicon adds valuable, indexable content around ingredient keywords.

09

WordPress migration & infra

The new store replaced the brand's previous WordPress site on the same domain — a clean DNS cutover, managed SSL, www→apex normalization, and SEO built right from day one. Cloudflare also serves as the infra layer: DNS, CDN/proxy, R2 object storage, and Email Routing for the brand inbox.

A "magic link" ties it together: a ?discount=CODE URL (e.g. from the bot's DM) auto-applies the code in the cart and carries UTM params straight into analytics.

The store, end to end.

lunulaoil.pl/leksykon
Ingredient lexicon — ingredient entries linked bidirectionally to products
Ingredient lexicon · linked both ways with products
Lunula Botanique homepage on mobile
Homepage
Lunula Botanique product page on mobile
Product page
Responsive storefront · scroll inside a phone to explore ↕

The stack, by layer.

FrontendNext.js 15 (App Router, SSR), TypeScript, Tailwind CSS, next-intl
BackendMedusa.js v2, Node.js 22, custom modules (TypeScript)
DatabasePostgreSQL
Payments & deliveryPaynow (BLIK + transfer/card, custom integration), InPost parcel lockers (Geowidget)
AIAnthropic API (Claude) — recommendation engine for the selection assistant
IntegrationsInstagram Graph API / Meta, Resend (email), GA4 + Google Consent Mode v2
InfrastructureRailway, Cloudflare (DNS, CDN, R2 files, Email Routing), backend-driven ISR revalidation, CI/CD via a deploy branch

A full store, not a demo.

Built for the business, not around a platform.

The brand got a store tailored to its sales model — combining retail and wholesale, an AI selection assistant, ingredient education linked bidirectionally with the store, Polish payments, and automated acquisition from Instagram — rather than a set of compromises around an off-the-shelf platform.

Thanks to the headless, modular approach, the business logic is tested, extensible, and resilient to core updates, and the whole architecture serves as a ready white-label foundation for future rollouts.

Have a project that needs more than a template?

Whether it's commerce, AI, or a custom system — I build software that survives contact with reality. Drop me a line.