Push Notifications: Engineering the Lock Screen Strategy
Email open rates are dying. Push Notifications are the future of retention. A technical deep dive into Web Push, VAPID Keys, Service Workers, and iOS PWA support.
“We need an app so we can send push notifications.” For a decade, this was the only reason brands spent $100k building native apps. The web was silent. If the user closed the tab, you lost them.
That era is over. With iOS 16.4 (2023), Apple finally enabled Web Push for Home Screen Web Apps. The gap between “Native App” and “Website” has effectively closed.
At Maison Code Paris, we see Push not as a marketing channel, but as a User Interface Extension. It is a way to display critical state (“Your taxi is here”, “Your order has shipped”) on the user’s most personal device surface: The Lock Screen.
Why Maison Code Discusses This
At Maison Code Paris, we act as the architectural conscience for our clients. We often inherit “modern” stacks that were built without a foundational understanding of scale. We see simple APIs that take 4 seconds to respond because of N+1 query problems, and “Microservices” that cost $5,000/month in idle cloud fees.
We discuss this topic because it represents a critical pivot point in engineering maturity. Implementing this correctly differentiates a fragile MVP from a resilient, enterprise-grade platform that can handle Black Friday traffic without breaking a sweat.
The Architecture: How Web Push Works
Unlike Native Push (which relies on APNs/FCM proprietary protocols), Web Push is an open standard (RFC 8291). It involves three actors:
- The User Agent (Browser): Generates a subscription.
- The Push Service: A server run by the browser vendor (Mozilla, Google, Apple) that routes messages.
- The Application Server (You): Triggers the message.
VAPID Keys (The Security Layer)
How does Apple know that you are the one sending the notification, and not a spammer spoofing your domain? VAPID (Voluntary Application Server Identification).
You generate a public/private key pair.
- Public Key: Sent to the browser during subscription.
- Private Key: Used to sign every push message (JWT).
If the signature doesn’t match the public key stored on the device, the Push Service drops the message. This prevents “Push Hijacking.”
Implementation Phase 1: The Service Worker
The Service Worker is the brain of the operation. It lives in the background, even when the tab is closed.
You cannot handle push events in your main React bundle (app.js). You must handle them in sw.js.
// public/sw.js
self.addEventListener('push', function(event) {
if (!event.data) return;
const payload = event.data.json();
const { title, body, icon, url, actions } = payload;
const options = {
body,
icon: icon || '/icon-192.png',
badge: '/badge-monochrome.png', // Small icon for Android status bar
data: { url }, // Store deep link
actions: actions || [], // Interactive buttons
tag: 'order-update', // Collapses multiple notifications into one
renotify: true,
};
event.waitUntil(
self.registration.showNotification(title, options)
);
});
// Handling the Click
self.addEventListener('notificationclick', function(event) {
event.notification.close(); // Close the alert
const urlToOpen = event.notification.data.url || '/';
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true }).then(windowClients => {
// If tab is already open, focus it
for (let client of windowClients) {
if (client.url === urlToOpen && 'focus' in client) {
return client.focus();
}
}
// Otherwise open new tab
if (clients.openWindow) {
return clients.openWindow(urlToOpen);
}
})
);
});
Implementation Phase 2: The Frontend Subscription
The browser prompt (“example.com wants to send you notifications”) is terrifying. Users block it by reflex.
Never call Notification.requestPermission() on page load.
We use the Double Permission Pattern.
- context: User clicks “Notify me when in stock”.
- Soft Prompt: Show a nice HTML Modal. “We will send you a one-time alert when this product is back. Allow?”
- Hard Prompt: If they click “Yes”, then trigger the native browser verification.
// components/PushToggle.tsx
import { urlBase64ToUint8Array } from './utils';
const PUBLIC_VAPID_KEY = 'BM...'; // Your Public Key
export const PushToggle = () => {
const subscribe = async () => {
const registration = await navigator.serviceWorker.ready;
// 1. Request Permission
const result = await Notification.requestPermission();
if (result !== 'granted') return;
// 2. Subscribe to Push Service
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(PUBLIC_VAPID_KEY)
});
// 3. Send Subscription endpoint to your Backend
await fetch('/api/push/subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
headers: { 'Content-Type': 'application/json' }
});
};
return <button onClick={subscribe}>Enable Alerts</button>;
};
Implementation Phase 3: The Backend Dispatch
We use the web-push Node.js library to handle encryption and VAPID headers.
// api/push/send.ts
import webpush from 'web-push';
webpush.setVapidDetails(
'mailto:admin@maisoncode.paris',
process.env.VAPID_PUBLIC,
process.env.VAPID_PRIVATE
);
export async function sendNotification(userSubscription, message) {
try {
await webpush.sendNotification(userSubscription, JSON.stringify({
title: 'Order Shipped',
body: 'Your package is on the way via DHL.',
url: '/orders/TRX-123',
actions: [
{ action: 'track', title: 'Track Package' },
{ action: 'close', title: 'Dismiss' }
]
}));
} catch (error) {
if (error.statusCode === 410) {
// Subscription is dead (User blocked us). Delete from DB.
await deleteSubscription(userSubscription.endpoint);
}
}
}
The iOS 16.4 Caveat: The PWA Requirement
Apple introduced a major friction point. On Safari (iOS), Web Push is ONLY available if the user adds the site to their Home Screen. A standard tab cannot receive push.
This means your strategy must focus on PWA Installation first. You need an “Install Prompt” UI (similar to the Push Soft Prompt) that guides the user: “Tap Share -> Add to Home Screen to receive order updates.”
Once installed (running generally in standalone display mode), the Push API becomes unlocked.
Rich Notifications and Actions
Notification Actions allow users to interact without opening the app.
- “New Message received.”
- Action 1: “Reply” (Opens text input).
- Action 2: “Mark as Read” (Does API call in background).
This transforms the notification from a “Signpost” into a “Control Panel.”
Ethics: The Boy Who Cried Wolf
The Click-Through Rate (CTR) of push is high (8-12%), but the Unsubscribe Rate is brutal. If you send one irrelevant notification (“It’s raining! Buy shoes!”), the user will disable permissions globally. Unlike Email (spam folder), there is no middle ground. It’s On or Off.
Our Rule: Push is for Service. Email is for Marketing.
- ✅ Order Shipped.
- ✅ Item back in stock (User requested).
- ✅ Flight Delayed.
- ❌ Winter Sale has started.
- ❌ Check out our new blog post.
10. Managing Push Fatigue (Frequency Capping)
Push notifications have strictly diminishing returns.
- Message 1: 10% CTR.
- Message 2: 5% CTR.
- Message 3: 0.5% CTR (and 2% Unsubscribes).
We implement Global Frequency Capping in Redis.
INCR user:123:push:count(TTL 24 hours). If count > 3, we silently drop the marketing messages (but always allow Transactional messages). We also implement “Quiet Hours” logic (Don’t wake the user at 3 AM unless their house is on fire).
11. The iOS PWA Install Strategy
Since users must install the PWA to get push on iOS, you need a strategy. You cannot just show a banner “Add to Home Screen”. You must offer Value behind the install wall.
- “Install the App to track your package in real-time.”
- “Install the App to Enable the VIP 10% discount.” We build custom “Install Instructions” that detect the user’s OS version and show the exact arrow pointing to the Share button.
13. Rich Media: Images and GIFs
Text is boring.
“Your shoes have shipped” is okay.
“Your shoes have shipped” + [Photo of the actual shoes] is better.
Web Push supports image property.
However, you must be careful with aspect ratios.
- Android: Landscape (2:1).
- Windows: Square (1:1). We build an “Image Resizer” lambda that generates device-specific thumbnails on the fly during the push dispatch.
14. Analytics Attribution
How do you know if Push is driving revenue?
You cannot rely on “Direct Traffic”.
You must tag your URLs.
url: '/product/123?utm_source=web_push&utm_campaign=black_friday'.
But also, listen to the notificationclick event to fire a custom Analytics ping before opening the window.
This captures “Engagement” even if the page fails to load.
15. Conclusion
Push Notifications are the most potent weapon in the Retention arsenal. For high-frequency retailers (Grocery, Fashion drops), they are essential. But they require a disciplined engineering approach: Solid Service Worker management, VAPID security, and a respectful UX strategy.
Maison Code helps brands navigate this space, turning the Lock Screen into a conversion channel, not a nuisance.
Own the Lock Screen
Do you want 90% open rates?