Push Notifications: Engage Users on Web Mobile
Learn: Push Notifications: Engage Users on Web Mobile
Welcome to TopperBlog! 👋
I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.
🎯 What I Write About:
• AI/ML Engineering & LLMs
• Web3 & Blockchain Development
• System Design & Architecture
• Interview Preparation (FAANG)
• Freelancing & Remote Work
• Modern Tech Stacks (Next.js, React, Rust, TypeScript)
• Performance Optimization & Best Practices
💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.
📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.
🌐 Let's connect and grow together in this amazing tech journey!
#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering
Push Notifications: Engage Users on Web & Mobile
Problem
Users leave web applications and forget to return. Without a way to re-engage them, businesses lose valuable interactions, conversions, and user retention. Traditional email is slow and intrusive. Real-time notifications are needed to:
- Remind users of important updates
- Drive immediate action on time-sensitive events
- Increase app engagement and retention
- Deliver personalized, timely messages
- Work across web and mobile platforms seamlessly
Solution
Service Workers + Push Notifications API provide a native, browser-based solution for sending real-time notifications even when users aren't actively using your app. This combination enables:
- Background Processing: Service workers run independently of the main thread
- Push Events: Receive notifications from a server via Push API
- User Engagement: Display rich, interactive notifications
- Cross-Platform: Works on web, PWAs, and mobile browsers
- Persistent: Notifications survive app closures and browser restarts
Architecture Flow
Server → Push Service (FCM/APNs) → Browser → Service Worker → Notification
Code Implementation
1. Register Service Worker
// main.js - Register and request notification permission
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('Service Worker registered:', registration);
requestNotificationPermission(registration);
})
.catch(error => console.error('SW registration failed:', error));
}
function requestNotificationPermission(registration) {
if ('Notification' in window && Notification.permission === 'default') {
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
subscribeUserToPush(registration);
}
});
}
}
2. Subscribe to Push Notifications
// Subscribe user and send endpoint to server
async function subscribeUserToPush(registration) {
try {
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
'YOUR_PUBLIC_VAPID_KEY'
)
});
// Send subscription to backend
await fetch('/api/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscription)
});
console.log('User subscribed to push notifications');
} catch (error) {
console.error('Push subscription failed:', error);
}
}
// Convert VAPID key from base64
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
return new Uint8Array([...rawData].map(char => char.charCodeAt(0)));
}
3. Service Worker - Handle Push Events
// sw.js - Service Worker
self.addEventListener('push', event => {
console.log('Push notification received:', event);
let notificationData = {
title: 'New Notification',
body: 'You have a new message',
icon: '/icon-192x192.png',
badge: '/badge-72x72.png',
tag: 'notification-tag',
requireInteraction: false
};
// Parse incoming push data
if (event.data) {
try {
notificationData = event.data.json();
} catch (e) {
notificationData.body = event.data.text();
}
}
event.waitUntil(
self.registration.showNotification(
notificationData.title,
{
body: notificationData.body,
icon: notificationData.icon,
badge: notificationData.badge,
tag: notificationData.tag,
requireInteraction: notificationData.requireInteraction,
data: notificationData.data || {},
actions: [
{ action: 'open', title: 'Open' },
{ action: 'close', title: 'Close' }
]
}
)
);
});
// Handle notification clicks
self.addEventListener('notificationclick', event => {
event.notification.close();
if (event.action === 'close') {
return;
}
const urlToOpen = event.notification.data.url || '/';
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true })
.then(clientList => {
// Check if app is already open
for (let i = 0; i < clientList.length; i++) {
const client = clientList[i];
if (client.url === urlToOpen && 'focus' in client) {
return client.focus();
}
}
// Open new window if not already open
if (clients.openWindow) {
return clients.openWindow(urlToOpen);
}
})
);
});
// Handle notification close
self.addEventListener('notificationclose', event => {
console.log('Notification closed:', event.notification.tag);
});
4. Backend - Send Push Notifications (Node.js)
// server.js - Express backend
const express = require('express');
const webpush = require('web-push');
const app = express();
app.use(express.json());
// Set VAPID details
webpush.setVapidDetails(
'mailto:your-email@example.com',
process.env.PUBLIC_VAPID_KEY,
process.env.PRIVATE_VAPID_KEY
);
// Store subscriptions (use database in production)
const subscriptions = [];
// Subscribe endpoint
app.post('/api/subscribe', (req, res) => {
const subscription = req.body;
subscriptions.push(subscription);
res.status(201).json({ message: 'Subscribed successfully' });
});
// Send notification to all users
app.post('/api/notify', async (req, res) => {
const { title, body, url } = req.body;
const notificationPayload = {
title,
body,
icon: '/icon-192x192.png',
data: { url: url || '/' }
};
try {
const promises = subscriptions.map(subscription =>
webpush.sendNotification(
subscription,
JSON.stringify(notificationPayload)
).catch(error => {
if (error.statusCode === 410) {
// Remove invalid subscription
subscriptions.splice(subscriptions.indexOf(subscription), 1);
}
})
);
await Promise.all(promises);
res.status(200).json({ message: 'Notifications sent' });
} catch (error) {
console.error('Error sending notifications:', error);
res.status(500).json({ error: 'Failed to send notifications' });
}
});
app.listen(3000, () => console.log('Server running on port 3000'));
5. Generate VAPID Keys
# Install web-push CLI
npm install -g web-push
# Generate VAPID keys
web-push generate-vapid-keys
# Output:
# Public Key: BEn...
# Private Key: xyz...
Tips & Best Practices
1. Permission Strategy
- Request permission at the right moment (after user engagement)
- Explain why notifications are valuable
- Respect user preferences and provide opt-out options
// Request after user interaction
button.addEventListener('click', () => {
Notification.requestPermission();
});
2. Notification Content
- Keep titles concise (< 50 characters)
- Use clear, actionable body text
- Include relevant data for deep linking
- Add action buttons for common interactions
{
title: '🎉 Order Confirmed',
body: 'Your order #12345 is being prepared',
tag: 'order-12345', // Prevents duplicates
data: { orderId: '12345', url: '/orders/12345' }
}
3. Frequency & Timing
- Avoid notification fatigue (max 2-3 per day)
- Send at optimal times based on user timezone
- Batch non-urgent notifications
- Respect quiet hours (9 PM - 8 AM)
4. Error Handling
- Handle 410 (Gone) responses - subscription expired
- Implement retry logic with exponential backoff
- Monitor delivery failures
- Clean up invalid subscriptions
webpush.sendNotification(subscription, payload)
.catch(error => {
if (error.statusCode === 410) {
// Remove subscription
} else if (error.statusCode >= 500) {
// Retry later
}
});
5. Security
- Use HTTPS only (required for Service Workers)
- Validate VAPID keys in environment variables
- Sanitize notification data to prevent XSS
- Implement rate limiting on notification endpoints
6. Testing
- Test on multiple browsers (Chrome, Firefox, Safari)
- Test on mobile devices and PWAs
- Simulate offline scenarios
- Monitor notification delivery rates
7. Analytics
- Track notification delivery, clicks, and dismissals
- Measure engagement impact
- A/B test notification content
- Monitor unsubscribe rates
// Track in Service Worker
self.addEventListener('notificationclick', event => {
fetch('/api/analytics', {
method: 'POST',
body: JSON.stringify({
event: 'notification_click',
tag: event.notification.tag,
timestamp: new Date()
})
});
});
8. Browser Support
- Chrome/Edge: Full support
- Firefox: Full support
- Safari: Limited (iOS 16.4+)
- IE: Not supported
9. Rich Notifications
- Add images, badges, and custom styling
- Use action buttons for quick responses
- Include notification groups/tags
- Support dark mode
{
title: 'New Message',
body: 'From John Doe',
image: '/message-image.jpg',
badge: '/badge.png',
tag: 'messages',
requireInteraction: true
}
10. Unsubscribe Handling
- Provide easy unsubscribe mechanism
- Handle subscription expiration gracefully
- Allow notification preference management
- Respect user choices immediately
// Unsubscribe user
async function unsubscribeUser() {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.getSubscription();
if (subscription) {
await subscription.unsubscribe();
}
}
Conclusion
Push notifications via Service Workers provide a powerful, native way to re-engage users and drive meaningful interactions. By combining proper permission handling, strategic timing, and rich content, you can significantly improve user retention and satisfaction while respecting user preferences and privacy.