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.
