Imagine: you added a product to your wishlist, but the price is too high. A week later, there's a 20% discount, and you find out by chance. That's a lost sale. We solve this problem: automatic notifications bring hesitant buyers back into the funnel. Our stack: Laravel 11 (PHP 8.3) for the backend, React 18 on the frontend, and PostgreSQL for storing subscriptions. Building the core logic takes 1-2 working days, provided the wishlist already exists. According to our statisticsinternal e-commerce project analytics 2022-2024, price drop notifications increase conversion from deferred purchases by 15-25%, and one in five subscribers makes a purchase, generating an average revenue of 2000-5000 rubles. With over 5 years of experience in e-commerce development and 50+ successful projects, we guarantee reliable notification delivery.
Problems solved by price drop notifications
Problem 1: users leave without buying. Our data shows that 60-70% of visitors leave the product page without making a purchase. A price drop notification is a gentle trigger to bring them back. This automated email campaign re-engages customers effectively.
Problem 2: high load during mass mailings. Without queues and chunkById, 10,000 subscribers for one product will crash the server. We use Laravel Horizon with Redis — proven on 50+ projects.
Problem 3: unlimited repeat notifications. We implement a strategy: notify only when the price drops another 10% relative to the price at the last notification.
Implementation details
Data schema
Subscriptions are stored in a separate table with a unique index on (user_id, product_id, variant_id). This prevents duplicates. The price_at_subscription field records the price at the time of subscription — the notification triggers when the price drops relative to that, not the last price.
CREATE TABLE price_drop_subscriptions (
id BIGSERIAL PRIMARY KEY,
product_id BIGINT NOT NULL REFERENCES products(id) ON DELETE CASCADE,
variant_id BIGINT REFERENCES product_variants(id) ON DELETE CASCADE,
user_id BIGINT REFERENCES users(id) ON DELETE CASCADE,
email VARCHAR(255) NOT NULL,
price_at_subscription NUMERIC(12,2) NOT NULL,
target_price NUMERIC(12,2),
notified_at TIMESTAMP,
notified_price NUMERIC(12,2),
created_at TIMESTAMP DEFAULT NOW()
);
CREATE UNIQUE INDEX idx_pds_user_product
ON price_drop_subscriptions(user_id, product_id, COALESCE(variant_id, 0));
Subscription workflow step by step
- On the product page, the user sees a "Track price" button. If not logged in, they are redirected to login and then back to the product page.
- After login, clicking the button sends a POST request to the API, which creates a record in the
price_drop_subscriptionstable with the current product price and user email. - When the product price changes (via admin panel or synchronization), a Laravel Observer fires. If the new price is lower, it dispatches a
CheckPriceDropSubscriptionsjob to thenotificationsqueue. - The job processes subscriptions in chunks of 100 records. For each, it checks the percentage drop (threshold 5%). If conditions are met, an email notification is sent.
- After sending, the subscription is marked as used (fields
notified_atandnotified_priceare set) to avoid duplicate notifications at the same price.
This asynchronous processing approach ensures the server is not overloaded during mass mailings, and users get timely notifications.
Subscription button on the product page
The "Track price" button appears next to "Add to cart". For unauthenticated users, it redirects to login with a return URL. We use useState and api (Axios) — no third-party libraries.
const PriceDropButton = ({ product, variant }: PriceDropProps) => {
const { user } = useAuth();
const [subscribed, setSubscribed] = useState(product.is_price_drop_subscribed ?? false);
const toggle = async () => {
if (!user) {
router.push(`/login?redirect=${encodeURIComponent(window.location.pathname)}`);
return;
}
if (subscribed) {
await api.delete(`/price-drop-subscriptions/${product.id}`);
setSubscribed(false);
toast.success('Subscription cancelled');
} else {
await api.post('/price-drop-subscriptions', {
product_id: product.id,
variant_id: variant?.id ?? null,
});
setSubscribed(true);
toast.success('We will notify you when the price drops');
}
};
return (
<button
onClick={toggle}
className={cn('flex items-center gap-1.5 text-sm', {
'text-blue-600': subscribed,
'text-gray-500 hover:text-gray-700': !subscribed,
})}
>
<BellIcon className={cn('w-4 h-4', { 'fill-blue-600': subscribed })} />
{subscribed ? 'Tracking price' : 'Track price'}
</button>
);
};
API and Job for sending (combined code)
When the price changes, an Observer dispatches a Job. The Job uses chunkById to handle large volumes without memory overload. The drop threshold is configurable, and the user's target_price is considered if set.
class CheckPriceDropSubscriptions implements ShouldQueue
{
public function __construct(
private Product $product,
private float $newPrice,
) {}
public function handle(): void
{
$threshold = 0.05;
PriceDropSubscription::where('product_id', $this->product->id)
->whereNull('notified_at')
->chunkById(100, function ($subscriptions) use ($threshold) {
foreach ($subscriptions as $sub) {
$dropPercent = ($sub->price_at_subscription - $this->newPrice) / $sub->price_at_subscription;
if ($dropPercent < $threshold) continue;
if ($sub->target_price && $this->newPrice > $sub->target_price) continue;
Mail::to($sub->email)->queue(new PriceDropNotification($sub, $this->product, $this->newPrice));
$sub->update([
'notified_at' => now(),
'notified_price' => $this->newPrice,
]);
}
});
}
}
Setting up repeat notifications
After the first notification, the subscription is marked as used. To send repeat notifications on further drops, add a condition: a drop of another 10% from the notified_price. This is easily implemented by extending CheckPriceDropSubscriptions — check notified_price and compare. This increases the value of the notification: the user doesn't get spam but stays informed about advantageous discounts.
What's included in setting up notifications?
- Database schema design and migrations
- REST API for subscribe/unsubscribe
- Frontend button component and tracking list
- Job for price check and email sending
- Queue configuration (Redis + Horizon)
- Email template with UTM and unsubscribe link
- Integration with existing wishlist (if any)
- API documentation and deployment instructions
Estimated timelines
| Stage | Duration |
|---|---|
| Analysis and design | 0.5–1 day |
| Schema and API development | 0.5–1 day |
| Frontend and integration | 0.5–1 day |
| Testing and deployment | 0.5–1 day |
| Total | 2–4 days |
Cost is calculated individually — starting from $500 for basic setup. Contact us to evaluate your project.
Comparison of notification strategies
Choosing a notification strategy depends on business goals. One-time notification is the simplest: the client gets an email on the first price drop below the threshold. Easy to implement, but the user risks missing a deeper discount. Repeat notification notifies on each new drop of 10% from the previous notification price — more complex logic but maintains interest without spam. If the client specifies a desired price, notification only triggers when that price is reached: maximum relevance, but conversion may be lower due to rare triggers.
Tracking list in the user account
The /account/price-tracking section shows all user subscriptions: product, price at subscription, current price, percentage change. The component uses a table with an unsubscribe button.
const PriceTrackingList = ({ subscriptions }: { subscriptions: PriceDropSubscription[] }) => (
<table className="w-full text-sm">
<thead>
<tr className="border-b">
<th className="text-left py-2">Product</th>
<th>Price at subscription</th>
<th>Current price</th>
<th>Change</th>
<th></th>
</tr>
</thead>
<tbody>
{subscriptions.map(sub => {
const change = ((sub.current_price - sub.price_at_subscription) / sub.price_at_subscription) * 100;
return (
<tr key={sub.id} className="border-b">
<td className="py-3">
<a href={`/products/${sub.product.slug}`} className="font-medium hover:underline">
{sub.product.name}
</a>
</td>
<td className="text-center">{formatPrice(sub.price_at_subscription)}</td>
<td className="text-center font-medium">{formatPrice(sub.current_price)}</td>
<td className={cn('text-center text-sm', change < 0 ? 'text-green-600' : 'text-gray-400')}>
{change < 0 ? `−${Math.abs(change).toFixed(1)}%` : `+${change.toFixed(1)}%`}
</td>
<td>
<button onClick={() => unsubscribe(sub.id)} className="text-gray-400 hover:text-red-500">
Unsubscribe
</button>
</td>
</tr>
);
})}
</tbody>
</table>
);
How does the notification affect conversion?
Price drop notifications increase conversion from deferred purchases by 15-25% (based on our project data). The user receives a personalized offer — better than mass mailings. One in five subscribers makes a purchase, generating an average of 2000-5000 rubles in revenue for the store. This customer re-engagement strategy also aids wishlist recovery by targeting users who already showed interest.
Ready to implement? Contact us, and we will deliver a turnkey notification solution. We'll discuss the details of your project and propose the optimal solution.







