Get notified when Amul products are back in stock for your pincode.
- Scraper fetches product catalog from
shop.amul.com(StoreHippo platform) - Users subscribe with email + pincode + products they want
- Stock checker runs every 15 min, checking availability per pincode
- Email notification fires when a product goes from out-of-stock → in-stock
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Scraper │────▶│ SQLite DB │◀────│ tRPC API │
│ (Playwright │ │ (products, │ │ (Hono) │
│ or HTTP) │ │ subs, │ │ │
└─────────────┘ │ stock) │ └──────┬──────┘
└──────┬──────┘ │
│ ┌────▼──────┐
┌──────▼──────┐ │ Frontend │
│ Stock Check │ │ (React) │
│ (Cron) │ └───────────┘
└──────┬──────┘
│
┌──────▼──────┐
│ Resend │
│ (Email) │
└─────────────┘
- Runtime: Bun
- API: Hono + tRPC
- Database: SQLite (better-sqlite3 + Drizzle ORM)
- Scraping: Playwright (fallback) / HTTP + StoreHippo session
- Email: Resend
- Language: TypeScript
# Install dependencies
bun install
# Install Playwright browsers (needed for scraper fallback)
bunx playwright install chromium
# Copy env and configure
cp .env.example .env
# Create database tables
bun run db:push
# Scrape products from Amul
bun run scrape
# Start the server (includes stock check cron)
bun run devproduct.getProducts({ query?, category? })— Search/filter productsproduct.getProductAggregation()— Categories with countsproduct.getProduct({ id })— Single product
subscription.createSubscription({ email, pincode, products[] })— Subscribesubscription.cancelSubscriptions({ email })— Unsubscribe allsubscription.getSubscriptions({ email })— List active subscriptions
POST /admin/check-stock— Manually trigger stock check
Amul's shop runs on StoreHippo, a headless e-commerce platform. Key details:
- Products are stored as
ms.productsentities with internal_ids - Stock is pincode-specific — different Amul distributors serve different areas
- Availability is determined via
ms.linked_productswhich maps products to substores (distributors) - When you enter a pincode, StoreHippo finds the matching substore and checks inventory
This means a product can be in stock in Mumbai (400001) but out of stock in Delhi (110001).
- Primary: HTTP requests with a StoreHippo session token (fast, no browser needed)
- Fallback: Playwright headless browser — intercepts XHR responses to
/api/ms.products
The session token expires every ~30 min and is auto-refreshed.
fly launch
fly secrets set RESEND_API_KEY=re_xxxx
fly deployFROM oven/bun:1
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --production
COPY . .
RUN bun run db:push
CMD ["bun", "run", "start"]# Run stock check as a cron job instead of built-in interval
*/15 * * * * cd /path/to/amul-restock && bun src/cron/check-stock.ts >> /tmp/amul-restock.log 2>&1MIT