Skip to content

Feature: Wishlists + “Notify Me” (Restock & Price-Drop Alerts) #21

@hoangsonww

Description

@hoangsonww

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)

  1. Wishlist

    • Add/remove product (and optional variant) to user wishlist.
    • Wishlist page & mini-widget (header icon + count).
  2. 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).
  3. 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

  • Product detail page:

    • If OOS → show “Notify me when back in stock”.
    • If in stock → show “Add to wishlist” and a “Price-drop alert” dialog (target price or % drop).
  • Wishlist page:

    • Grid of saved items (name, price, stock badge, remove, go to product).
    • If OOS item has an active subscription, show “Subscribed • Manage”.
  • Toasts for add/remove/success; form validation for target price.


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):

    • RESTOCK: notify if inventory > 0.

    • PRICE_DROP:

      • If targetPrice: notify if currentPrice <= targetPrice.
      • If dropPercent: compute threshold from current price at subscribe time or store basePrice in doc (recommended field: basePriceAtSubscribe).
  • 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)

  • Dev: console transport (logs JSON of “email”).

  • Prod option: environment-driven provider (SENDGRID_API_KEY or RESEND_API_KEY or MAILGUN_*).

  • Templated subject lines:

    • Restock: “Back in stock: {{productName}}”
    • Price drop: “Price dropped to {{price}}: {{productName}}”

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

  • Mongo models for wishlists and alert_subscriptions (+ indexes).
  • POST/DELETE/GET wishlist endpoints with auth + validation.
  • Subscribe/cancel/list alert endpoints with validation.
  • Evaluation service evaluateSubscriptions(product) with idempotency.
  • Hook in product update path (price/stock) to call evaluator.
  • Email provider abstraction + console transport (dev) + env-based provider (prod).
  • node-cron (or BullMQ) daily sweep.
  • Unit tests (Jest) for validators & evaluator.

Frontend (React)

  • Wishlist icon in header + page /wishlist.
  • Product detail: “Add to wishlist” / “Remove from wishlist”.
  • OOS state: “Notify me when back in stock” button (requires auth).
  • Price-drop dialog (target or %), client-side validation.
  • Toasts and loading states.

Docs & CI

  • Update README.md (feature overview + envs).
  • Update openapi.yaml + Swagger UI.
  • Add .env.example keys for email provider + FEATURE_ALERTS.
  • Extend GitHub Actions to run new tests.

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?

Metadata

Metadata

Assignees

Labels

dependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationenhancementNew feature or requestgood first issueGood for newcomershelp wantedExtra attention is needed

Projects

Status

Backlog

Relationships

None yet

Development

No branches or pull requests

Issue actions