Inventory & suppliers
Back-in-Stock Notifications
This ships in the core. It is implemented in src/pages/api/products/[id]/notify-me.ts. What follows is the plan it was built from, kept as written — the shipped code may go further than it, or differ in the details.
Shoppers opt in to be told when a sold-out product is back in stock. Captures customer email at the moment of disappointment (out-of-stock page), queues email notifications when inventory restocks, and tracks engagement metrics. Converts lost sales to future sales and reduces cart abandonment.
The problem
A customer visits my shop, finds a product sold out, and leaves. I have no way to tell them it is back in stock. Every day, I lose sales to customers who gave up. I need to capture their email and send them a ‘back in stock’ email when I restock.
What it does
- Subscription form on out-of-stock product page: email input, optional name, ‘Notify me’ button
- Subscription storage: back_in_stock_subscriptions table (customer_email, product_id, email_verified, status, created_at)
- Email verification (optional): send verification link before adding to queue, or auto-subscribe
- Restock trigger: when product qty changes from 0 to >0, query all subscriptions for that product, queue notifications
- Email template: customizable message with product link, product image, current price, unsubscribe link
- Notification queue: send emails via job queue (rate-limited, retryable)
- Unsubscribe link: one-click unsubscribe removes subscription without login
- Subscription management: customer can view and manage their subscriptions (if logged in) via account page
- Merchant controls: enable/disable back-in-stock feature, customize email template, view subscription count per product
- Notification history: log sent/bounced/clicked notifications for engagement tracking
- Duplicate prevention: do not send duplicate notification to same email on same product within 48 hours
What it deliberately does NOT do
Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.
- SMS notifications — email only; SMS is separate paid tier
- Marketing automation or segmentation — no email list export or campaign tools; notifications are product-specific only
- Frequency capping beyond unsubscribe — no ‘daily digest’ mode or per-customer rate limiting; each product restock = one email
- Analytics or engagement scoring — notification_sent is logged; merchants use email platform analytics for click/open rates
Data model
New tables: back_in_stock_subscriptions (id, product_id, customer_email, customer_id [nullable], email_verified, status enum(pending, active, unsubscribed, bounced), subscribed_at, verified_at), back_in_stock_notifications (id, subscription_id, notification_sent_at, email_status enum(pending, sent, bounced, clicked), error_detail [nullable]), back_in_stock_config (id, shop_id, feature_enabled boolean, email_template_id, require_email_verification boolean).
API
- POST /api/subscriptions/back-in-stock
- GET /api/subscriptions/back-in-stock/:id
- DELETE /api/subscriptions/back-in-stock/:id
- GET /api/subscriptions?customer_id=X
- POST /api/subscriptions/:id/unsubscribe
- GET /api/products/:id/back-in-stock-subscriptions
- PATCH /api/back-in-stock-config
- GET /api/back-in-stock-notifications/:subscriptionId
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Back-in-stock feature toggle in Settings. Email template editor. Subscription list per product (count, last subscribed date). Manual trigger button to send notification (test). Notification log showing sent/bounced/clicked. Customer subscription management UI (view, unsubscribe).
Why this is in the core
It shipped free. POST /api/products/{id}/notify-me takes the opt-in, src/lib/commerce/stock-waitlist.ts holds it, and the scheduler sends one notice when stock returns and then deletes the row, so there is nothing to unsubscribe from.
This item was first filed as paid. Charging for it now would mean taking a working feature out of the core to create a gap, which LICENSING.md rules out; and Magento ships the same thing free.
Dependencies
- product module (SKU tracking)
- inventory module (watches for qty 0 → >0 transitions)
- email system (template rendering, queue, bounce handling)
- customer module (optional: link subscriptions to logged-in customer)
Acceptance checks
Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.
- POST /api/subscriptions/back-in-stock creates subscription only if current product qty = 0
- Subscription status=‘active’ only after email verification completes (or auto-verified if setting disabled)
- Notification is queued when inventory transitions from 0 to >0
- Email includes unsubscribe link that accepts one-click DELETE without login
- Duplicate notification check: if notification sent to same email for same product in last 48 hours, skip send
- GET /api/subscriptions?customer_id=X returns only subscriptions for logged-in customer
- Bounce handling: if email bounces (status=bounced), subsequent restocks do not notify that subscription
Risks
Race condition: qty changes 0→1 during notification queue, subscription email never sent (watch on qty change, queue job synchronously or use event sourcing); email bounce loop (bounced status not checked before send); duplicate notifications if restock-trigger fires twice for same product; unsubscribe link mishandled (no token, anyone can unsubscribe anyone); schema migration on live shop (subscriptions table is new, safe); customer email changed after subscription (orphaned subscription).
Commercial context
| Suggested price | free (core) |
| Rival anchor | Magento ships this free as part of the inventory workflow. |
The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.
Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full
catalogue and CONTRIBUTING.md for how to work on this repository.