E-commerce Cart Abandonment Push Notifications

A cart-abandonment push recovers a sale that is already half-made: the shopper chose items and walked away. This playbook covers when to trigger, how to build a deep-linked payload that fits the size budget, which TTL keeps the message relevant, and a short sequence that converts without nagging.

Quick answer

Trigger a cart-abandonment push when a cart has been inactive for 15–60 minutes (not seconds — give people time to come back on their own). Send a payload carrying a deep link straight to the cart plus a short, specific body, with a TTL of a few hours because purchase intent decays fast. Use urgency: normal or high, cap the sequence at one or two touches per cart, and measure success as recovered revenue, not clicks. Keep the encrypted payload under the 4 KB limit with aes128gcm encoding — the cart contents live behind the link, not in the notification.

Why timing and TTL dominate

Cart abandonment is a race against decaying intent. Fire too early and you interrupt a shopper who was still deciding; fire hours late and they’ve bought elsewhere or forgotten. The 15–60 minute window catches genuine abandonment while intent is warm. TTL follows the same logic: a reminder that surfaces a day later is noise. A TTL of 3,600–14,400 seconds (one to four hours) lets the push service buffer for a briefly-offline device but drops the message once it’s stale — see TTL and expiration handling and the trade-offs in setting optimal TTL values for time-sensitive alerts.

This playbook is one of three in the use-case playbooks reference; cart abandonment sits at moderate urgency, short TTL, and low per-user frequency.

There is a subtle interaction between TTL and urgency worth getting right. A cart reminder with urgency: high and a short TTL will wake the device and deliver immediately — appropriate when the shopper is likely mid-session on another tab. But for most carts, urgency: normal is the safer default: it still delivers promptly to an online device, conserves battery, and avoids the perception of an aggressive brand. Reserve high urgency for time-boxed offers (“your 20%-off code expires in an hour”) where the deadline genuinely justifies interrupting the user. The TTL, meanwhile, is your insurance against a briefly-offline device — a three-hour TTL means a shopper who closed their laptop still gets the reminder when they reopen it, provided that happens within the window where the cart still matters to them.

Defining “abandoned” precisely

The trigger is only as good as its definition. A cart is abandoned when it contains items, the shopper has not advanced to checkout, and no cart activity has occurred for your inactivity window. The “not advanced to checkout” clause is what stops you from pushing someone who is actively typing their card number. Wire the trigger to a debounced event: each cart mutation resets a timer, and only when the timer fires without further activity does the abandonment job enqueue. This naturally handles the shopper who adds an item, browses for ten minutes, then adds another — the clock restarts each time, so you message genuine departure rather than ordinary deliberation.

The notification’s job is to get the shopper back to their cart in one tap. That means a deep link in data.url, a tag/collapse key per cart so a second touch replaces the first instead of stacking, and a body that names what’s waiting. Heavy content — product images, prices — loads on the cart page, not in the payload, because the encrypted ciphertext must stay under the 4 KB limit mandated by RFC 8291’s aes128gcm encoding. See push API payload encryption for what fits.

const webpush = require('web-push');

// Never hardcode the public key server-side — read both keys from the environment.
webpush.setVapidDetails(
  'mailto:shop@yourdomain.com',
  process.env.VAPID_PUBLIC_KEY,
  process.env.VAPID_PRIVATE_KEY
);

async function sendCartReminder(sub, cart) {
  const payload = JSON.stringify({
    title: "You left something behind",
    body: `Your ${cart.topItemName}${cart.count > 1 ? ` + ${cart.count - 1} more` : ''} is still in your cart.`,
    data: { url: `/cart?id=${cart.id}&src=push`, campaign: "cart_recovery" }
  });
  const subscription = {
    endpoint: sub.endpoint,
    keys: { p256dh: sub.p256dh_key, auth: sub.auth_secret }
  };
  return webpush.sendNotification(subscription, payload, {
    TTL: 10800,          // 3 hours — intent decays fast
    urgency: 'normal',
    topic: `cart-${cart.id}`  // collapse: a later touch supersedes this one
  });
}

The service worker turns the deep link into a one-tap return to the cart:

self.addEventListener('notificationclick', (event) => {
  event.notification.close();
  const url = event.notification.data?.url || '/cart';
  event.waitUntil(clients.openWindow(url));
});

Steps to launch cart-abandonment push

  1. Define the abandonment trigger. A cart is abandoned when it has items and no activity for your chosen window (15–60 min) and the shopper hasn’t started checkout.
  2. Confirm a usable subscription for that user — opted in, not hard-bounced — before enqueueing.
  3. Build the deep-linked payload under 4 KB with a per-cart collapse key.
  4. Send touch 1 with a short TTL and urgency: normal; prune 410 Gone endpoints and back off on 429/5xx using your retry and backoff layer.
  5. Suppress on conversion. If the cart converts or the shopper returns, cancel any pending second touch immediately.
  6. Send an optional touch 2 several hours later (e.g. with a small incentive) only if the cart is still abandoned, reusing the same collapse key so it replaces the first.
  7. Attribute recovered revenue via the src=push link parameter, not just clicks.

The recovery sequence

For most catalogs, one well-timed reminder recovers the bulk of what’s recoverable, and a single optional follow-up captures a meaningful slice more. Beyond two touches per cart, you’re firmly in nag territory and the block rate climbs faster than recovery. A sound default:

  • Touch 1 (≈15–60 min): a plain reminder, no incentive. Many carts recover here purely from the nudge, and you’ve spent nothing on margin.
  • Touch 2 (≈3–6 h later, optional): sent only if the cart is still abandoned, optionally with a modest incentive (free shipping often beats a percentage discount on margin). Reuse the cart’s collapse key so this replaces touch 1 rather than stacking.

Leading with the discount on touch 1 is the classic mistake: it trains shoppers to abandon carts deliberately and erodes margin on sales that would have completed for free. Hold the incentive for the follow-up, and only for shoppers who didn’t respond to the reminder.

Frequency caps still apply across the whole program. A shopper who abandons three carts in a day should not get three reminder sequences — fold them into a single message or suppress the extras, and respect any global per-user cap your other campaigns share.

Gotchas and edge cases

  • Firing too fast. A push 30 seconds after the user tabbed away feels like surveillance and pre-empts a natural return. Wait at least 15 minutes.
  • Putting prices in the payload. Discounts and product details inflate the payload past the 4 KB limit and go stale; keep them on the landing page behind the deep link.
  • No collapse key. Without a per-cart topic, a second touch stacks on top of the first, doubling the annoyance. Reuse the cart-scoped key so the latest replaces the rest.
  • TTL too long. A cart reminder delivered the next morning converts poorly and erodes trust. Keep TTL in the hours range so stale messages are dropped.
  • Counting clicks as success. A click that doesn’t check out isn’t recovery. Tie the metric to completed purchases attributed through the deep-link parameter.

FAQ

How long after abandonment should the first push fire?

Wait 15–60 minutes. Sending within seconds interrupts shoppers who are still deciding or about to return on their own; waiting hours lets intent decay and risks the sale going elsewhere. The 15–60 minute window catches genuine abandonment while purchase intent is still warm.

Should the discount go in the first or second touch?

Lead with a plain reminder; reserve any incentive for an optional second touch hours later, sent only if the cart is still abandoned. Offering a discount immediately trains shoppers to abandon carts on purpose and erodes margin on sales that would have completed anyway.