Skip to content

Abandoned cart recovery

Off by default. A merchant opts in under Settings, and the reminder goes out under their own SMTP sending reputation. Beluga sends nothing on its own until this is turned on.

The cart is client-side identifiers only, so there is nothing server-side to remind anyone about until a signed-in customer’s cart is mirrored to the carts table, debounced from the browser, only ever for a customer with an account. A guest’s cart never reaches the server before checkout; there is no address to contact and nothing worth storing.

A customer with nothing touched in their cart for the configured delay (default four hours) gets exactly one reminder, with a single-use, seven-day /cart?recover=<token> link that repopulates the cart from the stored identifiers and re-resolves every line against the live catalogue. Dropped, discontinued or unpublished lines are simply not in the recovered cart, the same way an ordinary cart hides them.

A checkout.session.expired webhook, a buyer who reached Stripe and did not pay, salvages into the same machinery immediately rather than waiting for the delay. It is behind the same opt-in: on an opted-out store the webhook stores no cart and sends no mail.

There is no job runner. The reminder is sent by a setInterval in the API process, the same shape as the session store’s prune timer. What keeps two API instances from sending two emails is not that timer but a conditional UPDATE … WHERE reminder_sent_at IS NULL when claiming a cart: only the first of two racing claims can win, so a duplicate tick costs a wasted query, never a duplicate email.

Every reminder needs a verified, non-suppressed email. Unverified per the customer accounts gate, and suppressed the moment a customer clicks the unsubscribe link every reminder carries.

The timer lives in the process. A platform that stops the machine between requests stops the reminders too; see Deploy your store.