
Low Stock Alerts
- 62 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Configure automated low-stock and reorder alerts with fixed or demand-based thresholds so you reorder before selling out.
About
Sets up low-stock notifications and reorder points across Shopify (Stocky), WooCommerce (ATUM), BigCommerce, or a custom cron job, including velocity-based dynamic thresholds. A developer uses it to replace manual inventory checks and automate supplier reorder notifications.
- Per-variant thresholds, dynamic reorder-point formula (velocity x lead time x safety buffer)
- Custom background-job example with alert de-duplication and consolidated supplier emails
Low Stock Alerts by the numbers
- 62 all-time installs (skills.sh)
- Ranked #984 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill low-stock-alertsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 62 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Configure automated low-stock and reorder alerts with fixed or demand-based thresholds so you reorder before selling out.
Files
Low Stock Alerts
Overview
Low stock alerts notify you when inventory drops below a threshold so you can reorder before running out. Every major platform has this built in — Shopify, WooCommerce, and BigCommerce all send email notifications when stock falls below a configured level. For more advanced needs like demand-based thresholds, supplier PO automation, or team notifications in Slack, dedicated apps add those capabilities without custom code.
When to Use This Skill
- When products are unexpectedly running out of stock and missing sales
- When the current inventory workflow relies on manual checks rather than automated alerts
- When you want demand-based reorder points rather than fixed thresholds
- When the store has supplier lead times that need to be factored into when to reorder
Core Instructions
Step 1: Determine platform and choose the right tool
| Platform | Built-in Alerts | Recommended Extension |
|---|---|---|
| Shopify | Shopify sends an email notification when stock hits zero; very limited threshold control | Stocky (free, by Shopify) for configurable thresholds and PO automation; Back in Stock for customer notifications |
| WooCommerce | WooCommerce emails the store admin when stock drops below the configured low-stock threshold | ATUM Inventory Management for per-product thresholds, supplier emails, and reorder suggestions |
| BigCommerce | Built-in low stock notifications per product with configurable threshold | Multi-Location Inventory app for location-specific alerts |
| Custom / Headless | Build a background job that checks levels against reorder points | Required when platform has no native alerting or you need supplier email automation |
---
Step 2: Platform-specific setup
---
Shopify
Built-in low stock notification:
Shopify sends an email to the store admin when inventory hits zero, but doesn't support configurable thresholds natively.
1. Go to Settings → Notifications → Scroll to Staff order notifications 2. Ensure the admin email is set — Shopify will notify this address when stock reaches 0
Setting per-variant thresholds with Stocky:
1. Install Stocky from the Shopify App Store (free) 2. Open Stocky and go to Products 3. For each product, set the Reorder point (the stock level that triggers an alert) 4. Set the Reorder quantity (how many units to order when the alert fires) 5. Stocky will flag products below their reorder point in the dashboard and can generate draft purchase orders automatically
Customer "back in stock" notifications:
- Install Back In Stock (paid) or Klaviyo from the App Store
- These apps show a "Notify me when available" button on out-of-stock products
- Automatically email opted-in customers when stock is replenished
---
WooCommerce
Configure global low-stock threshold:
1. Go to WooCommerce → Settings → Products → Inventory 2. Enter a value in Low stock threshold (e.g., 10) 3. Enter your notification email in Notification recipient(s) — comma-separate multiple emails 4. WooCommerce sends an email when any product drops to or below this threshold
Set per-product thresholds:
1. Go to WooCommerce → Products → [Product] → Inventory tab 2. Enable Manage stock? 3. Enter a Low stock threshold specific to this product (overrides the global setting)
Advanced alerting with ATUM:
1. Install ATUM Inventory Management for WooCommerce (free) 2. ATUM's dashboard shows all products at or below their reorder point in one view 3. Per-product reorder point configuration under ATUM → Product Settings 4. ATUM can also email your supplier directly when a product needs reordering (paid feature)
Demand-based thresholds:
- Install Inventory Planner (Shopify/WooCommerce) for sales velocity-based reorder suggestions
- Inventory Planner analyzes your sales history and suggests reorder points based on lead time × daily velocity + safety stock
---
BigCommerce
Set per-product low stock threshold:
1. Go to Products → [Product] → Inventory tab 2. Set the Low stock level field 3. BigCommerce sends an automatic email notification to the store admin when stock crosses this threshold
Configure who receives alerts: 1. Go to Store Setup → Store Settings → Miscellaneous 2. Set Low stock email address to your purchasing team's email
For location-specific alerts:
- Install the Multi-Location Inventory app
- Set thresholds per location — useful when you stock the same SKU at multiple warehouses
---
Custom / Headless
For custom platforms, implement a background job that checks inventory levels against configured reorder points:
// jobs/checkStockLevels.ts — run every 15-30 minutes via cron
export async function checkStockLevels() {
const levels = await db.inventoryLevels.findMany({
include: { reorderConfig: true, variant: { include: { product: true } }, location: true },
where: { reorderConfig: { isNot: null } },
});
const newAlerts = [];
for (const level of levels) {
const config = level.reorderConfig!;
const available = level.onHand - level.reserved;
// Dynamic reorder point based on sales velocity and lead time
let reorderPoint = config.reorderPoint;
if (config.useDynamicReorderPoint) {
const velocity = await calculateDailySalesVelocity(level.variantId, 30); // 30-day rolling average
reorderPoint = Math.ceil(velocity * config.leadTimeDays * 1.2); // 20% safety stock buffer
}
const alertType = available === 0 ? 'out_of_stock' : available <= reorderPoint ? 'low_stock' : null;
if (!alertType) continue;
// Only create a new alert if the previous one is resolved
const existingAlert = await db.stockAlerts.findFirst({
where: { variantId: level.variantId, locationId: level.locationId, alertType, resolvedAt: null },
});
if (!existingAlert) {
newAlerts.push({ level, config, available, reorderPoint, alertType });
await db.stockAlerts.create({ data: {
variantId: level.variantId, locationId: level.locationId,
alertType, triggeredAt: new Date(), availableAtTrigger: available,
}});
}
}
if (newAlerts.length > 0) await sendAlertNotifications(newAlerts);
}
// Calculate daily sales velocity from order history
async function calculateDailySalesVelocity(variantId: string, windowDays: number) {
const since = new Date(Date.now() - windowDays * 86400000);
const result = await db.orderLineItems.aggregate({
where: { variantId, order: { createdAt: { gte: since }, status: { in: ['completed', 'shipped'] } } },
_sum: { quantity: true },
});
return (result._sum.quantity ?? 0) / windowDays;
}
// Send consolidated alerts — group by supplier to avoid email spam
async function sendAlertNotifications(alerts: Alert[]) {
const bySupplier: Record<string, Alert[]> = {};
for (const alert of alerts) {
const key = alert.config.supplierId ?? 'merchant';
if (!bySupplier[key]) bySupplier[key] = [];
bySupplier[key].push(alert);
}
for (const [supplierId, supplierAlerts] of Object.entries(bySupplier)) {
const to = supplierId === 'merchant' ? process.env.MERCHANT_ALERT_EMAIL : (await db.suppliers.findById(supplierId))?.email;
if (!to) continue;
await emailService.send({ to, template: 'low-stock-alert', data: { alerts: supplierAlerts } });
}
}---
Step 3: Calculate dynamic reorder points (optional)
Fixed thresholds (e.g., "alert at 10 units") are simple but can be wrong — a product that sells 50 units per day needs a much higher threshold than one that sells 2 per week.
Formula:
Reorder Point = (Daily Sales Velocity × Lead Time Days) × 1.2 (20% safety buffer)Example: Product sells 5 units/day, supplier lead time is 7 days
- Demand during lead time: 5 × 7 = 35 units
- Reorder point with safety buffer: 35 × 1.2 = 42 units
For Shopify: Use Inventory Planner app — it computes velocity-based reorder points automatically from your sales history.
For WooCommerce: ATUM Inventory Management can be configured with supplier lead times and suggests reorder points based on sales velocity.
---
Step 4: Automate reorder notifications to suppliers
Once an alert fires, you want to notify your purchasing team or supplier automatically.
Shopify + Stocky: Stocky generates draft purchase orders when stock falls below the reorder point. Your team reviews and sends the PO to the supplier from within Stocky.
WooCommerce + ATUM (paid): ATUM can email the configured supplier directly when a product needs reordering, including the suggested quantity.
Email workflow (any platform): Set the low-stock notification email directly to your supplier's ordering inbox — include the SKU, product name, and suggested reorder quantity in the notification template.
Best Practices
- Set reorder points based on lead time and sales velocity — a fixed threshold of 10 units may be fine for slow movers but dangerously low for fast sellers; use the formula above as a starting point
- De-duplicate alerts — platforms and custom solutions should only send one alert per SKU per incident, not an alert on every inventory decrement; resolve old alerts when stock is replenished
- Consolidate supplier emails — one email with 5 low-stock SKUs from the same supplier is far less noisy than 5 separate emails
- Set thresholds at the variant level, not just the product level — a shirt with 3 remaining in XL and 50 in M should alert only for XL
- Review and update thresholds seasonally — a product that sells 2/day normally may sell 20/day during peak season; adjust thresholds before high-demand periods
Common Pitfalls
| Problem | Solution |
|---|---|
| Alert fires repeatedly for the same SKU | Ensure the platform only sends one alert per incident until stock is replenished; Shopify and WooCommerce do this correctly by default; custom builds need a resolvedAt guard |
| Dynamic reorder point too high during off-season | Use a rolling 30-day window for velocity calculations; consider separate seasonal configurations for products with strong seasonality |
| Supplier emails go to spam | Use an authenticated sending domain (SPF, DKIM, DMARC) for your alert emails; transactional email providers (SendGrid, Postmark) improve deliverability |
| Alert threshold too low — too many false alarms | Start with a threshold at 2× your typical order quantity from the supplier, then tune based on how often you're actually running out before the reorder arrives |
Related Skills
- @inventory-tracking
- @multi-warehouse
- @catalog-import-export
{
"context": "Tests whether the agent correctly designs the reorder configuration and stock alert data models with all required fields, computes available inventory correctly, applies the right alert type logic, and implements deduplication to prevent re-firing resolved alerts.",
"type": "weighted_checklist",
"checklist": [
{
"name": "reorder_configs fields",
"max_score": 10,
"description": "reorder_configs model/table includes ALL of: variant_id (or variantId), location_id (or locationId), reorder_point (or reorderPoint), reorder_quantity (or reorderQuantity), supplier_id (or supplierId), lead_time_days (or leadTimeDays), use_dynamic_reorder_point (or useDynamicReorderPoint), created_at (or createdAt)"
},
{
"name": "stock_alerts fields",
"max_score": 10,
"description": "stock_alerts model/table includes ALL of: variant_id, location_id, alert_type, triggered_at, available_at_trigger, reorder_point_at_trigger, resolved_at, notification_sent, acknowledged_by"
},
{
"name": "alert_type enum",
"max_score": 10,
"description": "alert_type field accepts exactly three values: 'low_stock', 'out_of_stock', and 'overstock' (or equivalent enum)"
},
{
"name": "available calculation",
"max_score": 10,
"description": "Available stock is computed as onHand minus reserved (e.g. level.onHand - level.reserved or on_hand - reserved), NOT just using onHand alone"
},
{
"name": "out_of_stock condition",
"max_score": 10,
"description": "alert_type is set to 'out_of_stock' specifically when available equals 0 (not just <= reorderPoint)"
},
{
"name": "low_stock condition",
"max_score": 10,
"description": "alert_type is set to 'low_stock' when available is greater than 0 AND less than or equal to the reorder point"
},
{
"name": "deduplication check",
"max_score": 15,
"description": "Before creating a new alert, the code queries for an existing unresolved alert for the same variantId + locationId + alertType combination (where resolvedAt is null)"
},
{
"name": "resolvedAt guard",
"max_score": 10,
"description": "The deduplication check specifically filters on resolvedAt being null (not just any existing alert), so resolved alerts do not block new ones"
},
{
"name": "dynamic reorder support",
"max_score": 10,
"description": "The job code checks use_dynamic_reorder_point / useDynamicReorderPoint flag and computes a different reorder point when it is true"
},
{
"name": "20% safety buffer",
"max_score": 5,
"description": "When computing the dynamic reorder point in the job, the formula uses a 1.2 multiplier (20% safety stock buffer): Math.ceil(velocity * leadTimeDays * 1.2) or equivalent"
}
]
}
Inventory Alert System — Core Data Layer
Problem Description
A small e-commerce retailer has been managing stock levels manually, relying on a warehouse manager to scan a spreadsheet every morning and email suppliers when something looks low. This process breaks down over weekends and busy seasons, and the team has lost sales on several occasions because they ran out of a variant before anyone noticed.
The engineering team has been asked to build an automated stock monitoring system. The first milestone is the core data layer: the database models that track per-variant, per-location reorder configuration and the alerts that get fired when a threshold is breached. The second part of the milestone is a background job that scans all inventory levels, compares them against configured thresholds, and creates alert records as needed.
A critical requirement from the ops team is that the system must not spam the same alert for a variant that is already in an alerted state — if a low-stock alert for "Red T-Shirt / Medium / East Warehouse" was created yesterday and hasn't been addressed yet, the next job run should not create a duplicate. The system should only create a new alert once the previous one has been closed out.
Output Specification
Produce the following files:
schema.js— JavaScript object definitions (or equivalent data model) for the two tables needed: one for storing per-variant reorder configuration and one for tracking stock alerts. Include field names, types, and a brief comment on each field's purpose.jobs/checkStockLevels.js— The background job function that: fetches all inventory levels that have a reorder config, determines alert type and severity for each, and creates alert records while preventing duplicates.DESIGN.md— A short (1-2 page) design note explaining the deduplication strategy and how the system decides what type of alert to raise.
{
"context": "Tests whether the agent correctly implements the demand forecasting module with the right velocity calculation window, order status filters, arithmetic formula, correct default parameters, and appropriate handling for seasonal products.",
"type": "weighted_checklist",
"checklist": [
{
"name": "30-day default window",
"max_score": 10,
"description": "calculateDailySalesVelocity defaults to a 30-day rolling window (windowDays = 30), not a different period like 7 or 90 days"
},
{
"name": "Order status filter",
"max_score": 10,
"description": "Velocity calculation filters order line items to only completed, shipped, and delivered orders (excludes pending/cancelled/returned orders)"
},
{
"name": "Velocity arithmetic",
"max_score": 10,
"description": "Daily velocity is computed as totalSold / windowDays (divides total quantity sold by the number of days in the window, not by number of orders or transactions)"
},
{
"name": "calculateDynamicReorderPoint signature",
"max_score": 10,
"description": "calculateDynamicReorderPoint function accepts dailyVelocity, leadTimeDays, and safetyStockMultiplier as parameters"
},
{
"name": "Default safetyStockMultiplier",
"max_score": 10,
"description": "The default value of safetyStockMultiplier in calculateDynamicReorderPoint is 1.5, not 1.0, 1.2, or any other value"
},
{
"name": "Reorder point formula",
"max_score": 15,
"description": "calculateDynamicReorderPoint computes Math.ceil(leadTimeDemand + safetyStock) where leadTimeDemand = dailyVelocity * leadTimeDays and safetyStock = leadTimeDemand * (safetyStockMultiplier - 1)"
},
{
"name": "Rounds up",
"max_score": 10,
"description": "The final reorder point value is rounded up using Math.ceil (not Math.round, Math.floor, or left as a float)"
},
{
"name": "Module exports",
"max_score": 10,
"description": "Both calculateDailySalesVelocity and calculateDynamicReorderPoint are exported from the demand forecasting module (named exports or equivalent)"
},
{
"name": "Seasonal product note",
"max_score": 10,
"description": "The implementation or documentation mentions that seasonal products should use separate high-season / off-season configurations (or equivalent note about rolling-window limitations for seasonal demand)"
},
{
"name": "1.2 multiplier in job",
"max_score": 5,
"description": "In the background stock check job (not the standalone function), when use_dynamic_reorder_point is true, the formula uses a 1.2 multiplier (20% safety buffer): Math.ceil(velocity * leadTimeDays * 1.2)"
}
]
}
Demand-Driven Reorder Points for Inventory Management
Problem Description
A fashion retailer has been using fixed reorder points across their entire product catalogue — every variant triggers an alert when stock drops to 10 units regardless of how quickly it sells. This means slow-moving items get reorder alerts constantly while fast-moving variants sometimes run out before an alert fires, because 10 units represents three days of supply for one product but two months for another.
The engineering team has been asked to build a demand forecasting module that replaces fixed thresholds with dynamically calculated reorder points. The core idea is simple: look at recent order history to estimate how many units sell per day, then multiply by the supplier's lead time so the reorder fires with enough runway to receive new stock before running out. The formula also needs to include a buffer for safety stock.
The product team flagged one concern: some of their seasonal lines (swimwear, winter coats) see very skewed velocity during peak months. The solution should address how seasonal products should be handled within this framework.
Output Specification
Produce the following files:
lib/demandForecasting.js— The demand forecasting module containing the velocity calculation and the reorder point formula as separate exported functions.jobs/checkStockLevels.js— A background job that fetches inventory levels with their reorder configs and, for variants with dynamic reorder enabled, calls the forecasting module to compute the threshold before checking stock levels.DEMAND_FORECASTING.md— A short technical note explaining the approach, the formula, how the safety stock buffer works, and how seasonal products should be configured.
{
"context": "Tests whether the agent correctly implements consolidated supplier email notifications (batched per supplier rather than per SKU), marks alerts after notification, and builds the admin alert management API with proper filtering, ordering, acknowledgement, and alert resolution.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Group by supplier",
"max_score": 10,
"description": "Before sending emails, alerts are grouped by supplierId so that all alerts for the same supplier are collected into one batch (not sent individually per alert)"
},
{
"name": "Single email per supplier",
"max_score": 10,
"description": "Only ONE email is sent per supplier per invocation — multiple low-stock SKUs from the same supplier appear in a single email, not separate emails"
},
{
"name": "Merchant template name",
"max_score": 10,
"description": "The merchant notification email uses the template identifier 'low-stock-merchant' (not a generic name or different string)"
},
{
"name": "Supplier template name",
"max_score": 10,
"description": "Supplier reorder emails use the template identifier 'low-stock-supplier-reorder' (not a generic name or different string)"
},
{
"name": "Supplier reorder lines",
"max_score": 10,
"description": "Each item in the supplier email's reorderLines array includes ALL of: sku, productName, currentStock, reorderQuantity, location"
},
{
"name": "notificationSent flag",
"max_score": 10,
"description": "After sending the supplier email, the code updates the corresponding stock alert records setting notificationSent to true"
},
{
"name": "GET unresolved only",
"max_score": 10,
"description": "The GET /api/admin/stock-alerts endpoint filters to only return alerts where resolvedAt is null (unresolved alerts only)"
},
{
"name": "GET ordered by triggeredAt desc",
"max_score": 10,
"description": "The GET endpoint orders results by triggeredAt descending (newest alerts first)"
},
{
"name": "Acknowledge sets user",
"max_score": 10,
"description": "The POST acknowledge endpoint sets acknowledgedBy to the current user's ID (from session, auth token, or request context) — does NOT set it to a hardcoded value or boolean"
},
{
"name": "resolveStockAlerts implementation",
"max_score": 10,
"description": "resolveStockAlerts(variantId, locationId) performs a bulk update (updateMany or equivalent) setting resolvedAt to the current date/time on all unresolved alerts for that variant+location"
}
]
}
Stock Alert Notifications and Merchant Dashboard API
Problem Description
A retailer's inventory system can already detect when stock falls below a reorder threshold and creates alert records in the database. The next milestone is to notify the right people and give the ops team a way to manage those alerts through an admin interface.
On the notification side, the retailer works with around a dozen suppliers. When multiple products from the same supplier are running low at the same time (which happens frequently before long weekends), the ops team has complained that their suppliers get bombarded with individual emails — one per SKU — which has led several suppliers to start ignoring them. Notifications need to be consolidated so that a supplier receives a single email listing everything they need to reorder, rather than a flood of individual messages. Merchants also need their own alert email summarising what's happening across the store.
On the API side, the ops team wants a simple dashboard that shows all active (unresolved) stock alerts with the ability to filter by alert type and warehouse location. When a team member has reviewed an alert and decided on a course of action, they should be able to mark it as acknowledged. When a purchase order is received and stock is replenished, the system should automatically close out any open alerts for that variant and location.
Output Specification
Produce the following files:
lib/alertNotifications.js— The notification module. It should accept a list of alert objects and send emails to merchants and suppliers, handling the consolidation logic.api/admin/stock-alerts.js— The admin API handlers: one to retrieve active alerts with query-parameter filtering, and one to mark an alert as acknowledged.lib/stockAlerts.js— A helper module containing the function to resolve alerts when stock is replenished.API.md— Brief API documentation listing the endpoints, their parameters, and response shapes.
{
"name": "finsi/low-stock-alerts",
"version": "0.1.0",
"summary": "Automated reorder point monitoring with supplier notifications and demand forecasting",
"skills": {
"low-stock-alerts": {
"path": "SKILL.md"
}
}
}