Summary
Add a customer-facing Wishlist plus a “Notify me” alert system for restock and price-drop events. Shoppers can save products to a wishlist, subscribe to alerts per product/variant, and receive notifications when (a) an out-of-stock item is back or (b) the price falls below a user-selected threshold or percentage.
Motivation
- Increases engagement and return visits without requiring paid vector DB credits.
- Complements current recommendations (Weaviate/FAISS/LangChain) by keeping users in the loop for items they actually want.
- Common e-commerce expectations (wishlists + restock emails) make the demo feel production-grade.
Scope (v1)
-
Wishlist
- Add/remove product (and optional variant) to user wishlist.
- Wishlist page & mini-widget (header icon + count).
-
Alerts
- “Notify me” for restock (when
inventory > 0) and price-drop (below target/percent).
- Single alert per user/product/variant per alert-type (idempotent).
- Delivery: email (dev: console logger; prod: pluggable provider).
-
Backoffice hooks
- Trigger checks when products are updated (price/inventory changes).
- Daily sweep job to re-evaluate alerts (safety net).
Non-Goals (v1): SMS/mobile push, complex targeting, multi-currency handling, bulk imports.
UX Notes
Data Model (MongoDB)
New collections:
// wishlists
{
_id, userId, items: [
{ productId, variantId: String|null, addedAt }
],
updatedAt, createdAt
}
// alert_subscriptions
{
_id,
userId,
productId,
variantId: String|null,
type: "RESTOCK" | "PRICE_DROP",
// Exactly one of the below for price alerts
targetPrice: Number|null,
dropPercent: Number|null, // e.g., 15 => notify when price <= price_at_subscribe * 0.85
status: "ACTIVE" | "CANCELLED" | "TRIGGERED",
lastNotifiedAt: Date|null,
createdAt
}
Indexes:
wishlists: { userId: 1 }
alert_subscriptions: { userId: 1, productId: 1, variantId: 1, type: 1, status: 1 } (unique on {userId, productId, variantId, type, status: ACTIVE})
API (Express)
Wishlist
POST /api/wishlist body: { productId, variantId? }
DELETE /api/wishlist body: { productId, variantId? }
GET /api/wishlist -> { items: [...] }
Alerts
POST /api/alerts/subscribe body: { productId, variantId?, type: "RESTOCK" | "PRICE_DROP", targetPrice?, dropPercent? }
POST /api/alerts/cancel body: { subscriptionId }
GET /api/alerts/mine -> { subscriptions: [...] }
Admin/Hook (internal)
POST /api/admin/products/:id/reindex // optional: trigger recompute after price/stock update
Validation rules:
PRICE_DROP: exactly one of targetPrice or dropPercent must be provided.
- Prevent duplicate ACTIVE subscription by (userId, productId, variantId, type).
Update openapi.yaml + Swagger docs with new schemas and endpoints.
Triggering Logic
-
On product update (price or inventory), run evaluateSubscriptions(productId):
-
Mark subscription as TRIGGERED and set lastNotifiedAt.
-
Idempotency: keep a small Redis/Memory TTL key (fallback to Mongo query) {subId}:{productPrice}:{inventory} to avoid double-send in bursty updates.
Email Delivery (pluggable)
Background Job
-
Add a small scheduler:
- Option A (simple):
node-cron daily at 02:00 server time → re-evaluate all ACTIVE subs for safety.
- Option B (if Redis available): BullMQ queue
alerts:eval to process in batches.
Security
- All endpoints require JWT (already used in app).
- Verify user ownership on wishlist and subscriptions.
- Rate limit write endpoints (e.g., 10/min/user) to prevent abuse.
Observability
-
Logs: alerts.triggered, alerts.skipped_reason, wishlist.add/remove with userId, productId.
-
Counters: alerts_triggered_total{type}, subscriptions_active_total.
-
Add basic unit tests for:
- Subscription validation.
- Threshold evaluation.
- Idempotent trigger on repeated product updates.
Acceptance Criteria
-
A logged-in user can:
- Add/remove products from wishlist; wishlist persists and renders on its page.
- Subscribe to RESTOCK for an OOS product; when stock becomes available, an email (or console log in dev) is produced and subscription becomes
TRIGGERED.
- Subscribe to PRICE_DROP (target price or percent); on qualifying price change, receive notification and subscription becomes
TRIGGERED.
-
API and Swagger/OpenAPI are updated and pass validation.
-
Unit tests cover rule evaluation and API validators.
-
No duplicate notifications are sent for the same event/version.
-
CI passes and feature is behind env flag FEATURE_ALERTS=true.
Tasks
Backend
Frontend (React)
Docs & CI
Open Questions
- Should
PRICE_DROP alerts auto-rearm (stay ACTIVE) until user cancels, or single-shot (v1 uses single-shot TRIGGERED)?
- Variant-level stock/price: do we already store variant prices/inventory separately? If yes, require
variantId to avoid false alerts.
- Retention: auto-clean
TRIGGERED subscriptions older than 90 days?
Summary
Add a customer-facing Wishlist plus a “Notify me” alert system for restock and price-drop events. Shoppers can save products to a wishlist, subscribe to alerts per product/variant, and receive notifications when (a) an out-of-stock item is back or (b) the price falls below a user-selected threshold or percentage.
Motivation
Scope (v1)
Wishlist
Alerts
inventory > 0) and price-drop (below target/percent).Backoffice hooks
Non-Goals (v1): SMS/mobile push, complex targeting, multi-currency handling, bulk imports.
UX Notes
Product detail page:
Wishlist page:
Toasts for add/remove/success; form validation for target price.
Data Model (MongoDB)
New collections:
Indexes:
wishlists:{ userId: 1 }alert_subscriptions:{ userId: 1, productId: 1, variantId: 1, type: 1, status: 1 }(unique on{userId, productId, variantId, type, status: ACTIVE})API (Express)
Wishlist
Alerts
Admin/Hook (internal)
Validation rules:
PRICE_DROP: exactly one oftargetPriceordropPercentmust be provided.Update openapi.yaml + Swagger docs with new schemas and endpoints.
Triggering Logic
On product update (price or inventory), run
evaluateSubscriptions(productId):RESTOCK: notify if
inventory > 0.PRICE_DROP:
targetPrice: notify ifcurrentPrice <= targetPrice.dropPercent: compute threshold from current price at subscribe time or storebasePricein doc (recommended field:basePriceAtSubscribe).Mark subscription as
TRIGGEREDand setlastNotifiedAt.Idempotency: keep a small Redis/Memory TTL key (fallback to Mongo query)
{subId}:{productPrice}:{inventory}to avoid double-send in bursty updates.Email Delivery (pluggable)
Dev: console transport (logs JSON of “email”).
Prod option: environment-driven provider (
SENDGRID_API_KEYorRESEND_API_KEYorMAILGUN_*).Templated subject lines:
“Back in stock: {{productName}}”“Price dropped to {{price}}: {{productName}}”Background Job
Add a small scheduler:
node-crondaily at 02:00 server time → re-evaluate all ACTIVE subs for safety.alerts:evalto process in batches.Security
Observability
Logs:
alerts.triggered,alerts.skipped_reason,wishlist.add/removewith userId, productId.Counters:
alerts_triggered_total{type},subscriptions_active_total.Add basic unit tests for:
Acceptance Criteria
A logged-in user can:
TRIGGERED.TRIGGERED.API and Swagger/OpenAPI are updated and pass validation.
Unit tests cover rule evaluation and API validators.
No duplicate notifications are sent for the same event/version.
CI passes and feature is behind env flag
FEATURE_ALERTS=true.Tasks
Backend
wishlistsandalert_subscriptions(+ indexes).evaluateSubscriptions(product)with idempotency.node-cron(or BullMQ) daily sweep.Frontend (React)
/wishlist.Docs & CI
README.md(feature overview + envs).openapi.yaml+ Swagger UI..env.examplekeys for email provider +FEATURE_ALERTS.Open Questions
PRICE_DROPalerts auto-rearm (stay ACTIVE) until user cancels, or single-shot (v1 uses single-shotTRIGGERED)?variantIdto avoid false alerts.TRIGGEREDsubscriptions older than 90 days?