# Push Notifications: Engage Users on Web Mobile

# 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:

1. **Background Processing**: Service workers run independently of the main thread
2. **Push Events**: Receive notifications from a server via Push API
3. **User Engagement**: Display rich, interactive notifications
4. **Cross-Platform**: Works on web, PWAs, and mobile browsers
5. **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

```javascript
// 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

```javascript
// 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

```javascript
// 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)

```javascript
// 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

```bash
# 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

```javascript
// 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

```javascript
{
  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

```javascript
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

```javascript
// 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

```javascript
{
  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

```javascript
// 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.
