PRICIUM
All posts
price alertsapiproduct variationsdeveloper tutorialautomation

How to Build an AI Price Alert System Using a Product Variation API

A practical tutorial for building a price alert system that monitors specific product variations - not just the base product - and notifies users when prices drop.

Aman Patel

Aman Patel

Founder & CEO

2026-03-27 9 min read

The Problem With Existing Price Alert Tools

CamelCamelCamel and similar tools track price changes on Amazon listings. They're useful but have a critical limitation: they track the listing-level price, not the variant-level price.

If you're waiting for the black XL hoodie to drop below $40, you don't care that the white S dropped to $32. But existing alert tools would still ping you - because to them, "the price dropped."

This guide shows you how to build a price alert system that monitors specific product variations and notifies users only when their exact variant crosses a threshold.

Architecture

Cron Job (every 30 min)
    ↓
Fetch all tracked products from DB
    ↓
For each product: call Pricium API
    ↓
Compare variation prices to stored snapshots
    ↓
If price dropped below threshold: send email/webhook
    ↓
Store new price snapshot in DB

Database Schema

CREATE TABLE price_alerts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_email TEXT NOT NULL,
  product_url TEXT NOT NULL,
  target_size TEXT,
  target_color TEXT,
  target_price DECIMAL(10,2) NOT NULL,
  location CHAR(2) DEFAULT 'US',
  active BOOLEAN DEFAULT true,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE price_snapshots (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  alert_id UUID REFERENCES price_alerts(id),
  price DECIMAL(10,2) NOT NULL,
  available BOOLEAN NOT NULL,
  captured_at TIMESTAMPTZ DEFAULT NOW()
);

The Price Checker Script

import { createClient } from '@supabase/supabase-js';
import nodemailer from 'nodemailer';

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_KEY!
);

const mailer = nodemailer.createTransport({
  service: 'gmail',
  auth: { user: process.env.EMAIL_USER, pass: process.env.EMAIL_PASS }
});

interface Variation {
  size?: string;
  color?: string;
  price: number;
  available: boolean;
}

async function fetchVariationPrice(
  url: string,
  location: string,
  size?: string,
  color?: string
): Promise<Variation | null> {
  const res = await fetch('https://api.pricium.store/product-detail', {
    method: 'GET',
    headers: {
      Authorization: `Bearer ${process.env.PRICIUM_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ url, location }),
  });

  const data = await res.json();

  return data.variations.find((v: Variation) => {
    const sizeMatch = !size || v.size?.toLowerCase() === size.toLowerCase();
    const colorMatch = !color || v.color?.toLowerCase() === color.toLowerCase();
    return sizeMatch && colorMatch;
  }) ?? null;
}

async function runPriceCheck() {
  const { data: alerts } = await supabase
    .from('price_alerts')
    .select('*')
    .eq('active', true);

  if (!alerts) return;

  for (const alert of alerts) {
    const variation = await fetchVariationPrice(
      alert.product_url,
      alert.location,
      alert.target_size,
      alert.target_color
    );

    if (!variation) continue;

    // Store snapshot
    await supabase.from('price_snapshots').insert({
      alert_id: alert.id,
      price: variation.price,
      available: variation.available,
    });

    // Check if threshold met
    if (variation.available && variation.price <= alert.target_price) {
      await sendAlert(alert, variation);
      // Deactivate alert after trigger (or keep active for ongoing monitoring)
      await supabase.from('price_alerts').update({ active: false }).eq('id', alert.id);
    }
  }
}

async function sendAlert(alert: any, variation: Variation) {
  const variantDesc = [alert.target_size, alert.target_color].filter(Boolean).join(' / ');
  
  await mailer.sendMail({
    from: '[email protected]',
    to: alert.user_email,
    subject: `🔔 Price Alert: Your tracked item dropped to $${variation.price}`,
    html: `
      <h2>Price Drop Alert!</h2>
      <p>The <strong>${variantDesc}</strong> variant you're tracking just dropped to 
         <strong>$${variation.price}</strong> - below your target of $${alert.target_price}.</p>
      <a href="${alert.product_url}" style="background:#3b82f6;color:white;padding:12px 24px;border-radius:6px;text-decoration:none;display:inline-block;margin-top:16px;">
        Buy Now →
      </a>
      <p style="color:#999;font-size:12px;margin-top:24px;">
        Prices can change quickly. Act fast!
      </p>
    `
  });
  
  console.log(`Alert sent to ${alert.user_email} for variation ${variantDesc} at $${variation.price}`);
}

// Run the check
runPriceCheck().then(() => process.exit(0));

Setting Up the Cron Job

On Vercel, use a Vercel Cron Job in vercel.json:

{
  "crons": [
    {
      "path": "/api/cron/price-check",
      "schedule": "*/30 * * * *"
    }
  ]
}

Create app/api/cron/price-check/route.ts:

import { NextRequest, NextResponse } from 'next/server';

export async function GET(req: NextRequest) {
  // Verify cron secret to prevent unauthorized calls
  const authHeader = req.headers.get('authorization');
  if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  await runPriceCheck();
  return NextResponse.json({ success: true });
}

What Makes This Special

Unlike basic price trackers, this system:

  • Monitors variant-specific prices (size + color combination)
  • Tracks availability separately - won't alert on a price drop if the item is out of stock
  • Is fully location-aware - UK users track UK prices
  • Sends rich, actionable emails with direct buy links

That's the kind of experience users keep and recommend.


Get your Pricium API key to start building →

Aman Patel

Written by Aman Patel

Founder & CEO at Pricium