
Flash Sale Engine
- 65 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Run time-limited sales with live countdown timers, per-item quantity caps, virtual waiting rooms, and automatic price restoration on expiry.
About
Sets up flash-sale mechanics: countdown timers, per-sale quantity limits, waiting rooms, and price restoration via apps or custom code. A developer uses it for time-limited deals, doorbusters, or high-demand product drops.
- Per-platform tool recommendation table
- Countdown timers, quantity caps, waiting rooms, and auto price restoration
Flash Sale Engine by the numbers
- 65 all-time installs (skills.sh)
- Ranked #3,110 of 4,347 Backend & APIs 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 flash-sale-engineAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 65 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Run time-limited sales with live countdown timers, per-item quantity caps, virtual waiting rooms, and automatic price restoration on expiry.
Files
Flash Sale Engine
Overview
Flash sales are time-limited discounts — typically 2–24 hours — that create urgency and drive conversion spikes. They require three things to work reliably: a countdown timer visible to shoppers, per-sale quantity limits that prevent overselling, and automatic price restoration when the sale ends. For high-traffic launches (product drops, Black Friday doorbusters), a virtual waiting room is also essential to prevent bot scalping. Most platforms have apps that handle this without custom code.
When to Use This Skill
- When launching time-limited sale events (e.g., 24-hour deals, Black Friday doorbusters) that must end at an exact time
- When a product has limited flash-sale quantity separate from the main inventory
- When expecting traffic spikes large enough to cause overselling with naive inventory checks
- When you need a waiting room or queue to fairly admit customers during high-demand drops
- When building a deals platform where multiple flash sales run simultaneously across different products
Core Instructions
Step 1: Determine the merchant's platform and choose the right tool
| Platform | Recommended Tool | Why |
|---|---|---|
| Shopify | Countdown Timer Bar, FOMO, or Hextom Flash Sales app | These apps handle countdown timers, scheduled price changes, and inventory display without custom code |
| Shopify Plus | Launchpad (free for Plus) | Shopify's official flash sale app — schedules price changes, enables/disables discount codes, and restores prices automatically |
| WooCommerce | YITH WooCommerce Flash Sales or WooCommerce Sales Countdown | Manage sale prices with countdown timers directly on product pages |
| BigCommerce | BigCommerce Promotions with custom script for countdown timer | BigCommerce's promotions engine handles discounts; use a storefront script for the timer UI |
| High-traffic drops (any platform) | Cloudflare Waiting Room | Cloudflare's managed waiting room queues visitors fairly; prevents scalper bots from monopolizing limited stock |
| Custom / Headless | Build with Redis for atomic stock and SSE/WebSocket for timer | Full control for custom platforms where apps don't apply |
Step 2: Configure the flash sale on your platform
---
Shopify
Option A: Shopify Launchpad (Plus only, free)
Launchpad is the official Shopify tool for scheduling promotional events:
1. In your Shopify admin, go to Apps → Launchpad 2. Click Create event 3. Set the event name (e.g., "Black Friday Flash Sale 2026"), start time, and end time 4. Under Price changes: select products and set the sale price for each 5. Under Publish/unpublish collections: optionally show/hide sale collections during the event 6. Under Discount codes: enable or disable specific discount codes during the event 7. Click Schedule — Launchpad activates the changes at the start time and reverses them automatically at the end time
Important: Set sale quantities separately from your main inventory. If you only want to sell 100 units at the flash sale price, reduce the product's available inventory to 100 before the sale, then restock after. Launchpad does not manage sale quantities natively.
Option B: Hextom Flash Sales app (non-Plus)
1. Install Hextom Flash Sales from the Shopify App Store (~$10/month) 2. Create a sale with a specific product list, discount percentage, and duration 3. The app adds a countdown timer widget to product pages and updates prices automatically 4. Configure the countdown timer appearance in the app settings
Per-product quantity caps: Use Shopify's built-in inventory tracking to set flash sale quantities: 1. Edit the product variant → set inventory quantity to your flash sale cap 2. Enable Track quantity and Stop selling when out of stock 3. After the sale ends, restock the inventory to the real quantity
---
WooCommerce
Option A: YITH WooCommerce Flash Sales plugin
1. Install YITH WooCommerce Flash Sales from the plugin directory or YITH.com 2. Go to YITH → Flash Sales → Add New Flash Sale 3. Set:
- Products included in the sale
- Sale price or discount percentage
- Start date/time and end date/time
- Maximum quantity available at the flash sale price (separate from main stock)
4. The plugin automatically shows a countdown timer on product pages and reverts prices at the end time
Option B: WooCommerce native sale pricing with an add-on for countdown
WooCommerce supports Sale price and Schedule natively on every product: 1. Edit the product → Pricing tab 2. Enter the Sale price 3. Click Schedule to set start and end dates 4. The sale price activates and deactivates automatically
Add a countdown timer with Countdown Timer for WooCommerce (free plugin) or WooCommerce Sales Countdown Timer.
Per-product flash sale quantity: Reduce the product's stock quantity to the flash sale cap before the sale begins, then restore it manually or via a scheduled script afterward.
---
BigCommerce
1. Go to Marketing → Promotions → Create Promotion 2. Set:
- Promotion type: Percentage off, or dollar amount off
- Applies to: Specific products or categories
- Active date range: Start and end time (BigCommerce restores prices automatically)
3. For a countdown timer, add a custom script:
- Go to Storefront → Script Manager → Create a Script
- Scope: Specific pages → Product pages
- Add a JavaScript timer that counts down to the promotion end time
Quantity limits on BigCommerce: Set a Maximum uses limit on the promotion to cap how many orders can use the flash sale discount, or use the product's inventory tracking to limit stock.
---
Waiting Room for High-Demand Drops (Any Platform)
For product drops expecting high traffic (limited sneakers, concert tickets, exclusive merchandise):
Cloudflare Waiting Room (recommended)
1. In your Cloudflare dashboard, go to Traffic → Waiting Room 2. Click Create Waiting Room 3. Set:
- Hostname and Path: the product page URL pattern (e.g.,
/products/limited-edition-*) - Total active users: maximum concurrent users allowed on the page
- New users per minute: rate at which waiting customers are admitted
4. Cloudflare serves a customizable waiting room page to queued visitors 5. Customers are admitted in FIFO order; bots are filtered by Cloudflare's bot management
This requires a Cloudflare Business or Enterprise plan. For lower-cost alternatives, consider Queue-it or Fastly's Waiting Room.
---
Custom / Headless
For custom storefronts, implement atomic stock reservation using Redis for high-concurrency safety:
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
// Atomically reserve one unit from the flash sale allocation
async function reserveFlashSaleUnit(saleId: string, customerId: string): Promise<boolean> {
const key = `flash_sale:${saleId}:sold`;
const maxKey = `flash_sale:${saleId}:max`;
const max = parseInt(await redis.get(maxKey) ?? '0');
if (max === 0) return false; // sale not configured
// Atomic increment — safe under high concurrency
const newCount = await redis.incr(key);
if (newCount > max) {
await redis.decr(key); // Undo the over-increment
return false; // Sold out
}
return true;
}
// Send countdown timer data to the client via Server-Sent Events
app.get('/api/sales/:saleId/timer', async (req, res) => {
const { endsAt, maxQuantity } = await db.flashSales.findById(req.params.saleId);
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
const interval = setInterval(async () => {
const remaining = Math.max(0, new Date(endsAt).getTime() - Date.now());
const sold = parseInt(await redis.get(`flash_sale:${req.params.saleId}:sold`) ?? '0');
res.write(`data: ${JSON.stringify({ remaining, stockLeft: maxQuantity - sold })}\n\n`);
if (remaining === 0) { clearInterval(interval); res.end(); }
}, 1000);
req.on('close', () => clearInterval(interval));
});Best Practices
- Use server-authoritative end times — never let the client calculate when the sale ends; always read the UTC end timestamp from the server to prevent timer drift across browsers
- Set sale quantity separately from main inventory — flash sale stock is a separate allocation; do not reduce your total inventory by the flash sale quantity until an order is confirmed
- Pre-scale infrastructure before high-traffic drops — load test your checkout with expected peak traffic 48 hours before a major sale; scale your hosting accordingly
- Display "while supplies last" when stock is low — showing live stock counts (e.g., "8 left") creates urgency, but avoid showing exact counts for large quantities as it appears artificial
- Test the full sale cycle in staging — run a complete sale from scheduled start through automatic price restoration before going live
- Never manually edit prices during an active Launchpad/app-managed sale — manual edits can prevent the automatic restoration from working correctly
Common Pitfalls
| Problem | Solution |
|---|---|
| Price not restored after sale ends | Use Launchpad or a countdown app with auto-restore; if using manual scheduling, set a calendar reminder and verify restoration; build a fallback check that validates prices against a "source of truth" table |
| Overselling during traffic spike | Use Redis atomic increment for custom builds; for platforms, set product inventory to the flash sale cap before the sale starts |
| Countdown timer shows different time in different timezones | Always base the countdown on the sale's absolute UTC end time; calculate remaining = endsAt - Date.now() client-side |
| Bots exhaust all stock before real customers can buy | Use Cloudflare Waiting Room or a similar queuing system; enforce per-customer purchase limits in your order system |
| App-managed countdown timer conflicts with manual price changes | Do not modify prices through the Shopify admin while a Launchpad event or countdown app is active |
Related Skills
- @coupon-management
- @dynamic-pricing
- @price-rules-engine
- @bot-protection
- @order-management-system
{
"context": "Tests whether the agent uses the correct flash sale database schema with the exact column types and constraints, implements atomic Redis-based stock reservation with ioredis, handles the overshoot scenario, persists reservations asynchronously to the DB, and addresses data reconciliation.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Prices as integer cents",
"max_score": 8,
"description": "sale_price and original_price columns are defined as INTEGER (not DECIMAL/FLOAT/NUMERIC), with a comment or name indicating they store cents"
},
{
"name": "Status CHECK constraint",
"max_score": 8,
"description": "The status column has a CHECK constraint that includes all four allowed values: 'scheduled', 'active', 'sold_out', and 'ended'"
},
{
"name": "queue_enabled column",
"max_score": 6,
"description": "The flash_sales table includes a queue_enabled BOOLEAN column with a DEFAULT false"
},
{
"name": "Composite index",
"max_score": 8,
"description": "A database index is created on the flash_sales table covering at minimum (status, starts_at, ends_at)"
},
{
"name": "ioredis package",
"max_score": 8,
"description": "Uses ioredis (import from 'ioredis') rather than another Redis client library such as redis or node-redis"
},
{
"name": "REDIS_URL env var",
"max_score": 6,
"description": "Redis connection is initialized using process.env.REDIS_URL as the connection string"
},
{
"name": "Atomic INCR reservation",
"max_score": 10,
"description": "Stock reservation uses redis.incr() (or equivalent atomic increment) on a Redis key rather than a database SELECT + UPDATE pattern"
},
{
"name": "Overshoot correction",
"max_score": 10,
"description": "After INCR, if the new count exceeds sale_quantity, the code calls redis.decr() (or equivalent) to roll back the increment before returning SOLD_OUT"
},
{
"name": "Async DB persist",
"max_score": 10,
"description": "After a successful Redis reservation, the DB insert/persist is called asynchronously (fire-and-forget) — the reservation response is NOT blocked waiting for the DB write to complete"
},
{
"name": "DB persist error logging",
"max_score": 6,
"description": "The asynchronous DB persist has a .catch() handler that logs the error (e.g., console.error) rather than silently swallowing it"
},
{
"name": "Redis-to-DB reconciliation",
"max_score": 10,
"description": "reconcile.ts contains a mechanism (e.g., periodic job or function) that reads the Redis sold counter and updates sold_count in the database — not relying solely on Redis for permanent state"
},
{
"name": "Separate sale quantity pool",
"max_score": 10,
"description": "DESIGN.md or code comments explain that flash sale stock (sale_quantity) is a separate allocation from main product inventory"
}
]
}
Flash Sale Data Layer
Problem/Feature Description
ShopNow, a growing e-commerce platform, is launching a new "Deals of the Day" feature where select products are sold at heavily discounted prices for a limited time with a hard cap on units available. The existing inventory system handles normal stock, but flash sales have their own quantity pools that must never oversell — even during Black Friday traffic where thousands of users hit the reservation endpoint simultaneously.
The engineering lead wants the data layer and reservation service implemented as a clean, standalone module before the frontend team builds on top of it. They've had bad experiences with naive SELECT + UPDATE patterns causing overselling in the past and are insisting on a high-throughput approach that can handle spikes without database lock contention. The module must also not lose data if the in-memory layer goes away — so reservations need to be durably persisted, and there should be a mechanism to keep the persistent store consistent over time.
Output Specification
Produce a TypeScript module that covers the following:
1. `schema.sql` — SQL DDL to create the required database tables and indexes for the flash sale system. 2. `reserveFlashSaleUnit.ts` — TypeScript implementation of the stock reservation function, including all edge case handling (sale not active, sale ended, sold out). 3. `reconcile.ts` — A short TypeScript snippet or function that describes how the persistent sold count is kept in sync with the in-memory counter. 4. `DESIGN.md` — A brief (1–2 paragraph) explanation of the reservation strategy chosen, covering why that approach was taken and how data durability is maintained.
All monetary values in the codebase should follow the convention used in your data model.
{
"context": "Tests whether the agent implements a Server-Sent Events endpoint (not polling or WebSocket) with correct headers, sends authoritative server time to avoid drift, formats the countdown as HH:MM:SS with zero-padded components, cleans up the interval on client disconnect and when the sale ends, and conditionally shows stock counts only when low.",
"type": "weighted_checklist",
"checklist": [
{
"name": "SSE content-type header",
"max_score": 7,
"description": "The endpoint sets the response header Content-Type to 'text/event-stream'"
},
{
"name": "SSE cache-control header",
"max_score": 6,
"description": "The endpoint sets Cache-Control to 'no-cache'"
},
{
"name": "SSE connection header",
"max_score": 6,
"description": "The endpoint sets Connection to 'keep-alive'"
},
{
"name": "1-second interval",
"max_score": 7,
"description": "The server uses setInterval with a 1000ms (1 second) interval to push updates to the client"
},
{
"name": "Remaining payload field",
"max_score": 8,
"description": "Each SSE data message includes a 'remaining' field representing milliseconds left, computed as Math.max(0, ends_at - now)"
},
{
"name": "stockLeft payload field",
"max_score": 7,
"description": "Each SSE data message includes a 'stockLeft' field computed from sale_quantity minus sold_count"
},
{
"name": "Cleanup on client disconnect",
"max_score": 8,
"description": "The endpoint clears the interval when the client disconnects (req.on('close', ...) or equivalent)"
},
{
"name": "Cleanup when time expires",
"max_score": 8,
"description": "The endpoint clears the interval and ends the response when remaining reaches 0"
},
{
"name": "Server-authoritative time",
"max_score": 9,
"description": "The client-side component does NOT set its own end time; it derives 'remaining' from the server-sent value on each message tick rather than computing it from a locally stored end timestamp"
},
{
"name": "HH:MM:SS format",
"max_score": 9,
"description": "The countdown is displayed in HH:MM:SS format, with each of hours, minutes, and seconds zero-padded to two digits using padStart(2, '0') or equivalent"
},
{
"name": "EventSource in React",
"max_score": 8,
"description": "The React component uses EventSource (not fetch polling or WebSocket) to connect to the timer endpoint"
},
{
"name": "Conditional stock display",
"max_score": 9,
"description": "The stock availability message is only shown (or shows exact numbers) when stock is low — not displayed unconditionally for all stock levels with large exact counts"
},
{
"name": "EventSource cleanup",
"max_score": 8,
"description": "The React component closes the EventSource connection on component unmount (es.close() in useEffect cleanup or equivalent)"
}
]
}
Live Flash Sale Timer Widget
Problem/Feature Description
TrendBuy's product team wants to add urgency-building elements to their flash sale product pages. When a flash sale is active, users should see a live countdown showing how much time is left in the sale, along with a real-time units-remaining indicator. The previous implementation polled a REST endpoint every second — at peak traffic this caused significant load on the API layer and had noticeable clock drift between browser tabs opened at different times.
The team wants a server-push approach for the timer so that all clients receive consistent ticks directly from the server. The implementation should be tolerant of clients disconnecting (e.g., navigating away) and should clean itself up properly. The units-remaining display should create a sense of scarcity but should only show exact numbers when stock is genuinely low — it should not show a precise count for large remaining quantities.
Output Specification
Produce the following files:
1. `timerEndpoint.ts` — A Node.js/Express route handler that streams sale timer updates to the client. The endpoint should send updates every second and include the time remaining, stock information, and sale status. It must handle client disconnection gracefully. 2. `FlashSaleCountdown.tsx` — A React component that connects to the timer endpoint and renders a formatted countdown display. The countdown format should be hours, minutes, and seconds. The component should also render a stock availability message that is conditional on how much stock remains. 3. `DESIGN.md` — A brief explanation of the streaming protocol used, how client-side time is calculated, and the rationale for the stock display behaviour.
You may assume a standard Express app object and a db.flashSales.findById() method are available. Use TypeScript throughout.
{
"context": "Tests whether the agent uses the cron package for sale scheduling with the correct cron expression, includes a real-time sale lookup as a fallback, implements the queue using a Redis Sorted Set with correct operations, sets admission token TTL to 300 seconds, returns 1-indexed queue position, and includes bot-protection measures.",
"type": "weighted_checklist",
"checklist": [
{
"name": "CronJob import",
"max_score": 8,
"description": "Uses CronJob from the 'cron' package (import { CronJob } from 'cron') rather than node-cron, setInterval, or another scheduling library"
},
{
"name": "Per-minute cron expression",
"max_score": 7,
"description": "The cron job uses the expression '* * * * *' (every minute) for sale status transitions"
},
{
"name": "Activate scheduled sales",
"max_score": 8,
"description": "The cron job includes a query to transition sales from 'scheduled' to 'active' where starts_at <= NOW() and ends_at > NOW()"
},
{
"name": "End expired sales",
"max_score": 8,
"description": "The cron job includes a query to transition sales from 'active' to 'ended' where ends_at <= NOW()"
},
{
"name": "Real-time sale lookup",
"max_score": 8,
"description": "A function (e.g. getActiveSale) performs a live DB query with starts_at <= now AND ends_at > now checks, not relying solely on the status field set by the cron job"
},
{
"name": "Redis Sorted Set for queue",
"max_score": 10,
"description": "The queue is backed by a Redis Sorted Set (zadd) where the score is the join timestamp (Date.now() or equivalent epoch value)"
},
{
"name": "zpopmin for batch admission",
"max_score": 9,
"description": "Batch admission uses redis.zpopmin() (or equivalent atomic sorted set pop) rather than zrange + zrem"
},
{
"name": "1-indexed queue position",
"max_score": 7,
"description": "The queue join function returns the position as (zrank result + 1), i.e., 1-indexed, not 0-indexed"
},
{
"name": "Admission token TTL 300s",
"max_score": 9,
"description": "Admission tokens are stored with a TTL of exactly 300 seconds (5 minutes) using redis.setex() or equivalent"
},
{
"name": "Token admission check",
"max_score": 7,
"description": "The isAdmitted function checks the Redis admission token key and returns true only when the token value equals '1'"
},
{
"name": "Bot protection measures",
"max_score": 9,
"description": "DESIGN.md or code comments mention at least two of the following: rate limiting on queue join, CAPTCHA requirement, IP velocity checks"
},
{
"name": "Token expiry re-queue rationale",
"max_score": 10,
"description": "DESIGN.md explains that expired admission tokens cause the slot to be released or returned to the queue (customers who don't complete checkout lose their spot)"
}
]
}
Flash Sale Scheduler and Virtual Waiting Room
Problem/Feature Description
DropZone, a sneaker and streetwear retailer, runs highly anticipated limited-edition product drops where thousands of customers compete for a few hundred units. Two recurring problems have hurt the customer experience: first, the sale sometimes shows "active" a few seconds late because the system relies entirely on a background process to flip the status flag; second, bots routinely exhaust the entire stock before genuine customers can even reach the purchase page.
The team wants a robust scheduling module that handles automatic sale activation and expiry, combined with a fair virtual waiting room that queues customers and admits them in order. The system must protect the queue from automated bots. The solution should also track when admitted customers fail to complete their purchase within a reasonable window so the slot can be offered to the next person in line.
Output Specification
Produce a TypeScript module with the following files:
1. `scheduler.ts` — Implements the cron-based sale scheduler that activates and ends sales automatically. Also include a function that performs a live sale lookup suitable for use in a product page API handler, in case the scheduled job hasn't run yet. 2. `queue.ts` — Implements the virtual waiting room: joining the queue, atomically admitting a batch of customers, checking whether a specific customer has been admitted, and the admission token expiry mechanism. 3. `DESIGN.md` — A brief explanation of the queue data structure chosen, why admission tokens have a finite lifetime, and how the system protects against bots.
The files should be self-contained TypeScript and can reference shared types (e.g., FlashSale) without implementing them.
{
"name": "finsi/flash-sale-engine",
"version": "0.1.0",
"summary": "Time-limited sales with countdown timers, stock limits, and queue management",
"skills": {
"flash-sale-engine": {
"path": "SKILL.md"
}
}
}