Built a custom CMS and CRM for an online store

OperationsAnalyticsIntegrations & automationProductDevelopment
Project: Tray
Date: 2024–25
Stack:Next.jsReactTypeScriptTailwindSupabase / PostgresRechartsResendOpenAI APIApiShipYandex Market APIGitea ActionsPM2
Contents

When an online store sells through six channels, orders, customers, the catalog, and money quickly scatter across different services. I pulled it all into one system: the team runs orders, customers, the catalog, and site content there, and I keep an eye on the analytics and the economics. It's a CMS and a CRM in one that I built myself to replace the off-the-shelf InSales platform, and Tray still runs on it.

Why build our own

At first, the business ran on InSales: it had every integration we needed, which made for a quick start. A year later, our needs had outgrown it. Analytics hit the wall first: six sales channels, each with its own reports and fees, and I needed to see the real margin. I looked at Power BI, Tableau, and Metabase, but decided to collect raw data in my own Supabase database and build the charts and reports myself with Cursor. Over time, the database held everything we needed: orders, products, cost, and margin. So I started turning the dashboard into a full management system and moving work off InSales. In June 2026, we shut InSales down completely.

Sales analytics by channel
Sales analytics by channel

What's inside

  • Analytics. Revenue, buyout rate, and average order value, a breakdown across six sales channels, and product analytics with trends, margin, and stock.
  • Orders and shipments (OMS). One list of orders from every channel, with statuses and history. An order can be split into shipments by warehouse, and each one goes to Yandex Delivery, CDEK, or Russian Post through ApiShip. Packing slips and made-to-order requests.
Order list
Order list
Order details
Order details
  • Customers (CRM). One customer database with order history and LTV for each buyer, segments for email campaigns, and promo codes.
  • Catalog (PIM). Products and variants, specs, stock, waitlists, reviews, and product feeds for marketplaces.
Product page in the admin
Product page in the admin
  • Vendors and dropshipping. Partner products that stay at the vendor's warehouse and ship directly from them, with their own lead times and shipping rules.
  • Email marketing. Transactional emails, a campaign builder with AI suggestions, and subscriber segments.
Email campaign builder
Email campaign builder
  • Website content (CMS). The homepage, collections, content blocks, posts, and a UGC gallery — the team updates the site without a developer.
  • Marketplaces. Yandex Market orders sync every five minutes, and stock goes back automatically with every warehouse movement. For Avito, there's end-to-end listing analytics matched to catalog products.

How it works

The whole system runs on one database: the admin writes to it, and the storefront and analytics read the same data. So when a warehouse worker changes stock, it shows up on the site and the marketplace right away, and the margin report is calculated from real orders, not exports. When we moved off InSales, I migrated the history so old orders stayed linked to their customers, and I switched things over step by step: analytics first, then orders, the catalog, and integrations, and the platform itself last.

The whole team worked in this system, and the new manager runs the business in it now. As the next step, I connected an AI agent that watches the store on its own and sends fixes ready for review — that's a separate case.