Situation
The brand had built a loyal following for a core range of outdoor drinkware. Their top 15 SKUs — specific colourways and sizes of their flagship products — accounted for 60% of revenue. When those SKUs stocked out, revenue dropped sharply. Loyal customers who wanted the exact product didn’t substitute; they waited, or bought from an authorised retailer at a margin hit.
The stockout problem had three root causes:
1. Manual monitoring lag. The merchandising coordinator pulled inventory reports from Shopify every morning, compared against a spreadsheet of reorder points, and flagged items manually. By the time a reorder was raised, the item was often already at critically low stock — or out.
2. No lead time intelligence. Reorder points were static numbers set at the start of each season. They didn’t account for supplier lead times, which varied by 2–6 weeks depending on the factory and shipping method.
3. No velocity awareness. A product going viral on social could burn through six weeks of stock in 72 hours. The static reorder system had no way to detect velocity spikes until it was too late.
Task
Build a custom Shopify app that:
- Monitors all inventory levels continuously against dynamic reorder thresholds
- Calculates reorder quantities based on velocity, lead time, and target cover days
- Alerts the merchandising team via Slack and email with pre-filled purchase order drafts
- Provides a dashboard showing current cover days, projected stockout dates, and pending reorders
- Integrates with their three primary suppliers’ APIs for real-time lead time data
The app needed to be production-ready, with proper error handling and audit trails, and operable by non-technical staff.
Action
Architecture
We built the app using Shopify’s Remix framework and the Shopify CLI. The backend runs on Railway. The core components:
┌─────────────────────────────────────────────────────────────┐
│ Shopify Admin App │
├──────────────────┬──────────────────┬───────────────────────┤
│ Inventory │ Reorder Engine │ Supplier Integration │
│ Monitor │ │ │
│ (Webhooks) │ - Velocity calc │ - Supplier A API │
│ │ - Lead time adj │ - Supplier B API │
│ inventory_ │ - Cover days │ - Supplier C EDI │
│ levels/update │ - PO generation │ │
└──────────────────┴──────────────────┴───────────────────────┘
│ │ │
└──────────────┬─────┘ │
▼ │
┌─────────────────┐ │
│ Alert Engine │◄─────────────────┘
│ (Slack + Email)│
└─────────────────┘
Step 1 — Inventory Monitoring via Webhooks
Instead of polling the Inventory API (which would burn API quota and be slow), we subscribed to inventory_levels/update webhooks. Every inventory change — sale, return, manual adjustment, fulfilment — triggers the reorder engine in near real-time.
// Webhook handler — inventory_levels/update
export async function action({ request }) {
const payload = await request.json();
const { inventory_item_id, location_id, available } = payload;
// Look up the variant and product
const variant = await db.variants.findByInventoryItemId(inventory_item_id);
if (!variant) return json({ ok: true }); // not a tracked SKU
// Run reorder check
await checkReorderThreshold(variant, available);
return json({ ok: true });
}
Step 2 — Dynamic Reorder Threshold Calculation
The most important piece. Static reorder points fail because they don’t adapt. We built a nightly recalculation job using a rolling 28-day velocity:
async function calculateReorderPoint(variantId) {
// 28-day rolling sales velocity (units/day)
const velocity = await getSalesVelocity(variantId, 28);
// Supplier lead time for this SKU (from supplier API, or fallback to historical avg)
const leadTimeDays = await getLeadTime(variantId);
// Safety stock = 7 days of velocity (buffer for demand spikes)
const safetyStock = Math.ceil(velocity * 7);
// Reorder point = stock needed to cover lead time + safety stock
const reorderPoint = Math.ceil(velocity * leadTimeDays) + safetyStock;
// Reorder quantity = 60 days of cover (adjustable per SKU)
const reorderQty = Math.ceil(velocity * 60);
return { reorderPoint, reorderQty, velocity, leadTimeDays, safetyStock };
}
This means a SKU that’s selling 10 units/day with a 21-day lead time gets a reorder point of (10 × 21) + 70 = 280 units. One that’s selling 2 units/day with a 14-day lead time gets (2 × 14) + 14 = 42 units.
Step 3 — Velocity Spike Detection
For social virality protection, we added a short-window velocity check that runs hourly:
// If 48-hour velocity is > 2.5x the 28-day average → spike alert
const recentVelocity = await getSalesVelocity(variantId, 2); // 48h
const normalVelocity = await getSalesVelocity(variantId, 28);
if (recentVelocity > normalVelocity * 2.5) {
await sendSpikeAlert(variant, {
currentStock: available,
projectedStockoutHours: Math.floor(available / (recentVelocity / 24))
});
}
This caught a viral TikTok moment three weeks after go-live. A creator posted a video featuring the brand’s most popular colourway, driving 400% normal velocity for 36 hours. The spike alert fired 4 hours in — giving the merchandising team time to contact the supplier and expedite an existing order. Previous system: they’d have found out when the product sold out.
Step 4 — Slack and Email Alerts with Pre-Filled POs
Every reorder alert includes a Slack message with:
- Current stock level and days of cover remaining
- Recommended reorder quantity with reasoning
- Supplier name and current quoted lead time
- One-click link to confirm the PO (or edit quantities)
The PO draft is generated automatically in the app and can be sent to the supplier with a single button press, or exported to PDF for approval workflows.
Step 5 — Admin Dashboard
A Polaris-based dashboard showing:
| Column | Data Shown |
|---|---|
| SKU / Product | Name, variant, current stock |
| Cover Days | Days of inventory remaining at current velocity |
| Stockout Date | Projected date, colour-coded (red < 14d, amber < 30d) |
| Velocity | Units/day (7d and 28d rolling) |
| Reorder Point | Current dynamic threshold |
| Status | On track / Alert / Critical / PO Pending |
Result
Three months post-launch:
Stockouts across the hero SKU range dropped from 3–4 per month to an average of 0.4. The one remaining stockout in month two was a force majeure situation (supplier factory closure) that no automated system could have prevented.
Revenue impact: The merchandising team estimated that each stockout event cost $15–20k in lost sales (based on velocity at time of stockout × average days to restock). At 3.5 stockouts/month previously, that was roughly $630k annually. Post-launch: roughly $50k remaining.
Operational impact: The daily manual inventory check went from a 2–3 hour morning task to a 10–15 minute review of the app dashboard and Slack alerts. The coordinator described it as “going from being a spreadsheet janitor to actually doing merchandising strategy.”
The spike detection feature has fired three times in four months — each time giving the team 4–12 hours of advance warning before stock would have run out.
Off-the-shelf inventory apps can handle simple threshold alerts. But dynamic reorder points that account for velocity, lead time, and demand spikes require custom logic — and that’s exactly where the ROI is. If your hero SKUs stockout even once a quarter, a custom app pays for itself.