/web-app-features

How to Add Price Alerts to Your Web App

Learn how to add price alerts to your web app easily and boost user engagement with our step-by-step guide.

Book a free  consultation
4.9
Clutch rating 🌟
600+
Happy partners
17+
Countries served
190+
Team members
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

How to Add Price Alerts to Your Web App

Adding Price Alerts to Your Web App: A Developer's Implementation Guide

 

Why Price Alerts Matter

 

Price alerts transform passive browsers into engaged customers. Rather than hoping users remember to check back on products they're interested in, you're creating a direct communication channel that brings them back exactly when they're most likely to convert. For businesses, this translates to higher conversion rates, increased customer satisfaction, and valuable data about price sensitivity.

 

Core Components of a Price Alert System

 

1. Data Architecture

 

Let's break down what our database needs to track:

 

  • Alert records (user ID, product ID, target price, status)
  • Price history (for detecting when alerts should trigger)
  • Notification preferences and delivery statuses

 

Here's a simplified database schema:

 

CREATE TABLE price_alerts (
  id SERIAL PRIMARY KEY,
  user_id INTEGER NOT NULL REFERENCES users(id),
  product_id INTEGER NOT NULL REFERENCES products(id),
  target_price DECIMAL(10, 2) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  triggered_at TIMESTAMP NULL,
  status VARCHAR(20) DEFAULT 'active', // active, triggered, dismissed
  notification_method VARCHAR(20) DEFAULT 'email', // email, sms, push
  UNIQUE(user_id, product_id) // Prevent duplicate alerts
);

CREATE TABLE price_history (
  id SERIAL PRIMARY KEY,
  product_id INTEGER NOT NULL REFERENCES products(id),
  price DECIMAL(10, 2) NOT NULL,
  recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

 

2. User Interface Components

 

Think about the user journey. A good price alert UI needs:

 

  • An accessible trigger button near product prices
  • A simple modal/form for setting target prices
  • A dashboard for users to manage existing alerts
  • Clear notifications when alerts trigger

 

Here's a React component for the alert creation modal:

 

import React, { useState } from 'react';

const PriceAlertModal = ({ product, currentPrice, isOpen, onClose, onSubmit }) => {
  const [targetPrice, setTargetPrice] = useState(currentPrice * 0.9); // Default to 10% off
  const [notificationMethod, setNotificationMethod] = useState('email');
  
  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit({
      productId: product.id,
      targetPrice,
      notificationMethod
    });
    onClose();
  };
  
  if (!isOpen) return null;
  
  return (
    <div className="modal-overlay">
      <div className="modal-content">
        <h3>Set Price Alert for {product.name}</h3>
        <p>Current price: ${currentPrice.toFixed(2)}</p>
        
        <form onSubmit={handleSubmit}>
          <div className="form-group">
            <label htmlFor="target-price">Alert me when price drops to:</label>
            <div className="price-input-wrapper">
              <span className="currency-symbol">$</span>
              <input
                id="target-price"
                type="number"
                step="0.01"
                min="0"
                value={targetPrice}
                onChange={(e) => setTargetPrice(parseFloat(e.target.value))}
                required
              />
            </div>
            {targetPrice >= currentPrice && (
              <p className="price-warning">Target price should be lower than current price</p>
            )}
          </div>
          
          <div className="form-group">
            <label>Notification method:</label>
            <select 
              value={notificationMethod} 
              onChange={(e) => setNotificationMethod(e.target.value)}
            >
              <option value="email">Email</option>
              <option value="sms">SMS</option>
              <option value="push">Push notification</option>
            </select>
          </div>
          
          <div className="modal-actions">
            <button type="button" className="btn-secondary" onClick={onClose}>Cancel</button>
            <button 
              type="submit" 
              className="btn-primary"
              disabled={targetPrice >= currentPrice}
            >
              Create Alert
            </button>
          </div>
        </form>
      </div>
    </div>
  );
};

export default PriceAlertModal;

 

3. Backend Implementation

 

The backend needs to handle two main functions:

 

  • Managing alert creation, retrieval, and updates
  • Running the price check service that triggers notifications

 

Here's how the alert creation endpoint might look using Express:

 

// routes/priceAlerts.js
const express = require('express');
const router = express.Router();
const { authenticateUser } = require('../middleware/auth');
const pool = require('../db');

// Create a new price alert
router.post('/', authenticateUser, async (req, res) => {
  try {
    const { productId, targetPrice, notificationMethod } = req.body;
    const userId = req.user.id;
    
    // Validate inputs
    if (!productId || !targetPrice) {
      return res.status(400).json({ error: 'Missing required fields' });
    }
    
    // Check if product exists and get current price
    const productResult = await pool.query(
      'SELECT id, current_price FROM products WHERE id = $1',
      [productId]
    );
    
    if (productResult.rows.length === 0) {
      return res.status(404).json({ error: 'Product not found' });
    }
    
    const product = productResult.rows[0];
    
    // Validate target price is below current price
    if (targetPrice >= product.current_price) {
      return res.status(400).json({ 
        error: 'Target price must be lower than current price' 
      });
    }
    
    // Check for existing alert for this user/product
    const existingAlertResult = await pool.query(
      'SELECT id FROM price_alerts WHERE user_id = $1 AND product_id = $2',
      [userId, productId]
    );
    
    // If alert exists, update it instead of creating new one
    if (existingAlertResult.rows.length > 0) {
      const alertId = existingAlertResult.rows[0].id;
      
      const updatedAlert = await pool.query(
        `UPDATE price_alerts 
         SET target_price = $1, notification_method = $2, status = 'active', triggered_at = NULL
         WHERE id = $3
         RETURNING *`,
        [targetPrice, notificationMethod, alertId]
      );
      
      return res.status(200).json({
        message: 'Price alert updated',
        alert: updatedAlert.rows[0]
      });
    }
    
    // Create new alert
    const result = await pool.query(
      `INSERT INTO price_alerts 
       (user_id, product_id, target_price, notification_method, status) 
       VALUES ($1, $2, $3, $4, 'active') 
       RETURNING *`,
      [userId, productId, targetPrice, notificationMethod]
    );
    
    res.status(201).json({
      message: 'Price alert created',
      alert: result.rows[0]
    });
  } catch (error) {
    console.error('Error creating price alert:', error);
    res.status(500).json({ error: 'Server error creating price alert' });
  }
});

// Get all alerts for current user
router.get('/', authenticateUser, async (req, res) => {
  try {
    const userId = req.user.id;
    
    const result = await pool.query(
      `SELECT pa.*, p.name as product_name, p.current_price 
       FROM price_alerts pa
       JOIN products p ON pa.product_id = p.id
       WHERE pa.user_id = $1
       ORDER BY pa.created_at DESC`,
      [userId]
    );
    
    res.json(result.rows);
  } catch (error) {
    console.error('Error fetching price alerts:', error);
    res.status(500).json({ error: 'Server error fetching price alerts' });
  }
});

module.exports = router;

 

4. Price Monitoring Service

 

This is the heart of your alert system. You need a reliable worker that:

 

  • Regularly checks current prices against alert thresholds
  • Triggers notifications when conditions are met
  • Marks alerts as triggered to prevent duplicate notifications

 

Let's build a worker using Node.js:

 

// workers/priceAlertWorker.js
const pool = require('../db');
const { sendEmail } = require('../services/emailService');
const { sendSMS } = require('../services/smsService');
const { sendPushNotification } = require('../services/pushService');

async function checkPriceAlerts() {
  console.log('Running price alert check...');
  
  try {
    // Get all active alerts with current product prices
    const alertsResult = await pool.query(`
      SELECT 
        pa.id as alert_id,
        pa.user_id,
        pa.product_id,
        pa.target_price,
        pa.notification_method,
        p.current_price,
        p.name as product_name,
        u.email,
        u.phone,
        u.push_token
      FROM price_alerts pa
      JOIN products p ON pa.product_id = p.id
      JOIN users u ON pa.user_id = u.id
      WHERE pa.status = 'active'
    `);
    
    const alerts = alertsResult.rows;
    console.log(`Found ${alerts.length} active alerts to process`);
    
    // Process each alert
    for (const alert of alerts) {
      if (alert.current_price <= alert.target_price) {
        console.log(`Alert triggered: ${alert.alert_id} for product ${alert.product_name}`);
        
        try {
          // Mark alert as triggered
          await pool.query(
            `UPDATE price_alerts 
             SET status = 'triggered', triggered_at = NOW() 
             WHERE id = $1`,
            [alert.alert_id]
          );
          
          // Record the event
          await pool.query(
            `INSERT INTO alert_events 
             (alert_id, price_at_trigger, event_type) 
             VALUES ($1, $2, 'triggered')`,
            [alert.alert_id, alert.current_price]
          );
          
          // Send notification based on preferred method
          await sendNotification(alert);
          
        } catch (notificationError) {
          console.error(`Error processing alert ${alert.alert_id}:`, notificationError);
          
          // Record failed notification attempt but keep alert active
          await pool.query(
            `INSERT INTO alert_events 
             (alert_id, price_at_trigger, event_type, details) 
             VALUES ($1, $2, 'notification_failed', $3)`,
            [alert.alert_id, alert.current_price, notificationError.message]
          );
        }
      }
    }
    
    console.log('Price alert check completed');
  } catch (error) {
    console.error('Error in price alert worker:', error);
  }
}

async function sendNotification(alert) {
  const message = `Great news! ${alert.product_name} is now $${alert.current_price.toFixed(2)}, which is at or below your target price of $${alert.target_price.toFixed(2)}. Check it out now!`;
  
  switch (alert.notification_method) {
    case 'email':
      await sendEmail({
        to: alert.email,
        subject: `Price Drop Alert: ${alert.product_name}`,
        html: `
          <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
            <h2>Price Drop Alert!</h2>
            <p>${message}</p>
            <a href="${process.env.BASE_URL}/products/${alert.product_id}" style="display: inline-block; background-color: #4CAF50; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; margin-top: 15px;">
              View Product
            </a>
          </div>
        `
      });
      break;
      
    case 'sms':
      await sendSMS({
        to: alert.phone,
        message: message
      });
      break;
      
    case 'push':
      await sendPushNotification({
        token: alert.push_token,
        title: 'Price Drop Alert!',
        body: message,
        data: {
          productId: alert.product_id
        }
      });
      break;
      
    default:
      throw new Error(`Unknown notification method: ${alert.notification_method}`);
  }
}

// Execute immediately if run directly
if (require.main === module) {
  checkPriceAlerts()
    .then(() => process.exit(0))
    .catch(err => {
      console.error(err);
      process.exit(1);
    });
} else {
  // Export for use in scheduled jobs
  module.exports = { checkPriceAlerts };
}

 

5. Scheduling the Price Check Service

 

You have several options for scheduling regular price checks:

 

// Using node-cron for scheduling within your application
const cron = require('node-cron');
const { checkPriceAlerts } = require('./workers/priceAlertWorker');

// Run every 15 minutes
cron.schedule('*/15 * * * *', async () => {
  console.log('Running scheduled price alert check');
  await checkPriceAlerts();
});

 

Alternatively, for more robust scheduling in a production environment:

 

// scheduler.js - Entry point for a separate worker process
const { checkPriceAlerts } = require('./workers/priceAlertWorker');

// For use with PM2, Kubernetes CronJobs, or similar
async function runScheduledTask() {
  console.log(`Starting scheduled price check at ${new Date().toISOString()}`);
  await checkPriceAlerts();
  console.log(`Completed scheduled price check at ${new Date().toISOString()}`);
}

runScheduledTask()
  .then(() => process.exit(0))
  .catch(err => {
    console.error('Fatal error in scheduled task:', err);
    process.exit(1);
  });

 

Implementation Considerations for Tech Leads

 

Performance and Scaling

 

As your user base grows, the price alert worker could become a bottleneck. Consider:

 

  • Batch processing: Process alerts in chunks with a configurable size
  • Distributed workers: Run multiple instances of your worker for different segments of your user base
  • Notification queues: Use a message queue (like RabbitMQ or SQS) to separate alert detection from notification delivery

 

Here's how to implement batched processing:

 

async function processPriceAlertsInBatches(batchSize = 500) {
  let processedCount = 0;
  let hasMoreRecords = true;
  let lastProcessedId = 0;
  
  while (hasMoreRecords) {
    // Get batch of alerts
    const batchResult = await pool.query(`
      SELECT 
        pa.id as alert_id,
        pa.user_id,
        pa.product_id,
        pa.target_price,
        pa.notification_method,
        p.current_price,
        p.name as product_name,
        u.email,
        u.phone,
        u.push_token
      FROM price_alerts pa
      JOIN products p ON pa.product_id = p.id
      JOIN users u ON pa.user_id = u.id
      WHERE pa.status = 'active' AND pa.id > $1
      ORDER BY pa.id
      LIMIT $2
    `, [lastProcessedId, batchSize]);
    
    const alerts = batchResult.rows;
    
    if (alerts.length === 0) {
      hasMoreRecords = false;
      continue;
    }
    
    // Process this batch
    for (const alert of alerts) {
      // Price check and notification logic
      // ...
      
      lastProcessedId = alert.alert_id;
      processedCount++;
    }
    
    console.log(`Processed ${processedCount} alerts so far`);
    
    // Optional: Add a short delay between batches to reduce database load
    await new Promise(resolve => setTimeout(resolve, 500));
  }
  
  return processedCount;
}

 

Error Handling and Resilience

 

Price alerts are a customer-facing feature, so reliability matters:

 

  • Implement retry mechanisms for notification delivery
  • Log detailed error information for debugging
  • Consider a dead-letter queue for alerts that repeatedly fail

 

Here's a robust notification service with retries:

 

// services/notificationService.js
const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 5000;

async function sendNotificationWithRetry(alert) {
  let attempts = 0;
  let success = false;
  let lastError;
  
  while (attempts < MAX_RETRIES && !success) {
    try {
      attempts++;
      
      if (attempts > 1) {
        console.log(`Retry attempt ${attempts} for alert ${alert.alert_id}`);
      }
      
      await sendNotification(alert);
      success = true;
      
    } catch (error) {
      lastError = error;
      console.error(`Notification attempt ${attempts} failed for alert ${alert.alert_id}:`, error);
      
      // Record the failed attempt
      await pool.query(
        `INSERT INTO notification_attempts 
         (alert_id, attempt_number, error_message) 
         VALUES ($1, $2, $3)`,
        [alert.alert_id, attempts, error.message]
      );
      
      if (attempts < MAX_RETRIES) {
        // Wait before retry
        await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS));
      }
    }
  }
  
  if (!success) {
    // Move to dead letter queue after max retries
    await pool.query(
      `UPDATE price_alerts 
       SET status = 'notification_failed', 
           failure_reason = $1
       WHERE id = $2`,
      [lastError.message, alert.alert_id]
    );
    
    throw new Error(`Failed to send notification after ${MAX_RETRIES} attempts: ${lastError.message}`);
  }
  
  return success;
}

 

User Experience Considerations

 

The best technical implementation still needs thoughtful UX:

 

  • Smart defaults: Pre-fill alert forms with prices 5-10% below current price
  • Alert management: Make it easy for users to view/edit/delete their alerts
  • Notification preferences: Let users choose how and when they get notified

 

Business Impact Measurement

 

Once implemented, track these metrics to measure the business impact of your price alerts:

 

  • Alert conversion rate: What percentage of triggered alerts result in purchases?
  • Revenue from alert-driven sales: How much revenue comes from alert-triggered sessions?
  • Price sensitivity insights: At what discount thresholds do users set most alerts?
  • Re-engagement rate: How many dormant users return through price alert notifications?

 

Tracking Code Example:

 

// On the purchase confirmation page
function trackAlertConversion(orderId, productIds) {
  if (!window.localStorage) return;
  
  // Check if this purchase came from a price alert click
  const alertClickSource = localStorage.getItem('alert_click_source');
  if (!alertClickSource) return;
  
  const alertData = JSON.parse(alertClickSource);
  
  // Only count if purchased product matches the alerted product
  if (productIds.includes(alertData.productId)) {
    // Track conversion
    analytics.track('Alert Conversion', {
      alertId: alertData.alertId,
      productId: alertData.productId,
      orderId: orderId,
      timeSinceAlert: Date.now() - alertData.clickTime
    });
    
    // Clear the stored alert data
    localStorage.removeItem('alert_click_source');
    
    // Also send to backend for accurate tracking
    fetch('/api/analytics/alert-conversion', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        alertId: alertData.alertId,
        orderId: orderId
      })
    });
  }
}

 

Final Thoughts

 

Price alerts represent one of those rare features that create a win-win situation. Users get notified about products they're interested in at prices they're willing to pay, while your business gets increased conversions and valuable data about price sensitivity.

 

The implementation outlined here provides a solid foundation that can scale with your user base. Start with the basics, monitor the performance and business impact, and then iterate based on what you learn. The most successful price alert systems evolve to match the specific shopping patterns of your customer base.

Ship Price Alerts 10x Faster with RapidDev

Connect with our team to unlock the full potential of code solutions with a no-commitment consultation!

Book a Free Consultation

Top 3 Price Alerts Usecases

Explore the top 3 practical use cases for price alerts to boost your web app’s user engagement and value.

 

1. Inventory Restocking Alerts

 

Price alerts notify users when products they want return to affordability thresholds they've set. This creates a second conversion opportunity after initial browsing abandonment, effectively functioning as an automated, user-configured remarketing tool with exceptionally high intent.

 

2. Competitive Price Monitoring

 

Allows users to track competitors' pricing fluctuations without constant manual checking. This reduces comparison shopping abandonment by promising to notify users when your prices become more favorable, while simultaneously giving your business intelligence on what price points trigger conversion for specific customer segments.

 

3. Flash Sale Amplification

 

When temporary discounts hit user-defined thresholds, alerts drive immediate traffic spikes during promotional windows. This creates urgency-based conversion surges with precision targeting exactly matched to individual price sensitivity, dramatically improving flash sale ROI compared to broad email campaigns that often miss price-conscious segments.

 


Recognized by the best

Trusted by 600+ businesses globally

From startups to enterprises and everything in between, see for yourself our incredible impact.

RapidDev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with.

They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

Arkady
CPO, Praction
Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost.

He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Donald Muir
Co-Founder, Arc
RapidDev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space.

They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Mat Westergreen-Thorne
Co-CEO, Grantify
RapidDev is an excellent developer for custom-code solutions.

We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Emmanuel Brown
Co-Founder, Church Real Estate Marketplace
Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 

This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Samantha Fekete
Production Manager, Media Production Company
The pSEO strategy executed by RapidDev is clearly driving meaningful results.

Working with RapidDev has delivered measurable, year-over-year growth. Comparing the same period, clicks increased by 129%, impressions grew by 196%, and average position improved by 14.6%. Most importantly, qualified contact form submissions rose 350%, excluding spam.

Appreciation as well to Matt Graham for championing the collaboration!

Michael W. Hammond
Principal Owner, OCD Tech

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â