Comment créer une stratégie de récupération de paniers abandonnés au-delà des e-mails (SMS, Push)
How to Build a Custom 'Abandoned Cart' Recovery Strategy Beyond Emails (SMS, Push)
Abandoned carts are the low-hanging fruit of e-commerce: people who were close to buying but left. Most stores rely on e-mail, and e-mails are great — but you can significantly bump recoveries by building a multi-channel stratégie that includes SMS and bligneser push. In this post I’ll walk you through a practical, Magento 2-focused approche covering tiers SMS APIs (Twilio, MessageBird), native bligneser push with Service Workers, personalization, automation triggers, and comment measure ROI with Google Analytics. Think of this like a hands-on conversation — I’ll show code, exemples, and sensible defaults so you can implement this in a real store (and adapt to magefine hosting or extensions later).
Why go beyond e-mail?
Quick bullet reasons:
- SMS has a higher open rate (and immediate attention) than e-mail — great for short recovery nudges.
- Push notifications are great for logged-out visiteurs and mobile web utilisateurs — they’re opt-in and persistent.
- Combining channels vous permet de tailor cadence and message format — e.g., short SMS+link for immediate recovery, then richer e-mail with product images and incentives.
- Some shoppers ignore e-mail but respond to SMS or push. More channels = more chances to convert.
High-level architecture
Here’s a simple architecture to implement in Magento 2:
- Identify abandoned carts (cron): run a job to find active quotes older than X hours with items but no commande.
- Classify & segment: apply rules by cart valeur, product categories, utilisateur behavior (visited pages, coupon history).
- Personalize message templates: inject top product, cart valeur, dynamic code promo.
- Deliver via channel: SMS (Twilio/MessageBird) or Push (Service Worker + web-push VAPID) or fallback to e-mail.
- Track clicks / recoveries with tagged URLs and Google Analytics events.
- Automate flux de travails with temporal & behavioral triggers and stop after conversion.
Part 1 — Finding abandoned carts in Magento 2
Core idea: use the quote table (Magento quote) to find active carts that haven’t been converted into commandes. Typical criteria:
- is_active = 1
- items_count > 0
- updated_at older than threshold (1 hour, 6 hours, 24 hours)
- no corresponding sales_commande
Cron is the right place for periodic checks. Example snippet of a Magento 2 cron class that locates candidate quotes (simplified):
<?php
namespace Vendor\AbandonedCart\Cron;
use Magento\Quote\Model\ResourceModel\Quote\CollectionFactory as QuoteCollectionFactory;
use Psr\Log\LoggerInterface;
class FindAbandonedCarts
{
private $quoteCollectionFactory;
private $logger;
public fonction __construct(
QuoteCollectionFactory $quoteCollectionFactory,
LoggerInterface $logger
) {
$this->quoteCollectionFactory = $quoteCollectionFactory;
$this->logger = $logger;
}
public fonction execute()
{
// threshold exemple: updated more than 2 hours ago
$threshold = (new \DateTime())->modify('-2 hours')->format('Y-m-d H:i:s');
$collection = $this->quoteCollectionFactory->create()
->addFieldToFilter('is_active', 1)
->addFieldToFilter('items_count', ['gt' => 0])
->addFieldToFilter('updated_at', ['lt' => $threshold]);
foreach ($collection as $quote) {
// do a quick conversion check or push to queue for processing
$this->logger->info('Found possible abandoned cart: ' . $quote->getId());
}
return $this;
}
}
From here you can push quote IDs into a job queue or process them immediately to decide channel and message.
Part 2 — Integnote SMS: Twilio and MessageBird with Magento 2
SMS is super effective for short, time-sensitive messages. Below are two realistic integrations for Magento 2: Twilio and MessageBird. In each case you’ll create a small Magento 2 service that wraps the provider SDK and expose an admin config to store API clés.
1) Twilio (PHP SDK)
Install Twilio PHP SDK via composer in your Magento project (inside your repo):
composer require twilio/sdk
Create a service class (Vendor/AbandonedCart/Model/Sms/TwilioClient.php):
<?php
namespace Vendor\AbandonedCart\Model\Sms;
use Twilio\Rest\Client;
use Psr\Log\LoggerInterface;
class TwilioClient
{
private $client;
private $from;
private $logger;
public fonction __construct($sid, $token, $from, LoggerInterface $logger)
{
$this->client = new Client($sid, $token);
$this->from = $from;
$this->logger = $logger;
}
public fonction send($to, $message)
{
try {
$this->client->messages->create($to, [
'from' => $this->from,
'body' => $message,
]);
return true;
} catch (\Exception $e) {
$this->logger->erreur('Twilio send failed: ' . $e->getMessage());
return false;
}
}
}
From your cron processor, call this service with a personalized message and a recovery link (see personalization later). Keep the SMS message under 160 characters or handle multi-part SMS costs.
2) MessageBird
MessageBird is similar; install their PHP library:
composer require messagebird/php-rest-api
<?php
namespace Vendor\AbandonedCart\Model\Sms;
use MessageBird\Client;
use Psr\Log\LoggerInterface;
class MessageBirdClient
{
private $client;
private $originator;
private $logger;
public fonction __construct($accessKey, $originator, LoggerInterface $logger)
{
$this->client = new Client($accessKey);
$this->originator = $originator;
$this->logger = $logger;
}
public fonction send($to, $message)
{
try {
$messageObject = new \MessageBird\Objects\Message();
$messageObject->originator = $this->originator;
$messageObject->recipients = [$to];
$messageObject->body = $message;
$this->client->messages->create($messageObject);
return true;
} catch (\Exception $e) {
$this->logger->erreur('MessageBird send failed: ' . $e->getMessage());
return false;
}
}
}
Admin config: add admin champs for SID / token / originator so non-devs can update credentials.
Best practices for SMS
- Always require explicit opt-in for marketing SMS (GDPR/CTIA rules). For abandoned-cart transactional messages some regions allow them if they’re operational, but check local rules.
- Use short URLs (or UTM-tagged full URLs) that point back to the store and restore the cart (deep link that loads quote ID).
- Avoid sending too frequently; typical cadence: 1st SMS after 1-2 hours, 2nd after 24 hours (if no conversion), optionally final 48–72 hrs with small incentive.
Part 3 — Bligneser Push: Service Workers, subscriptions, and sending notifications
Bligneser push is great for both logged-in and anonymous utilisateurs (if they opt-in). The flow:
- Front-end registers a Service Worker and requests push permission.
- On subscribe, the bligneser provides a subscription objet (JSON with endpoint + clés).
- Save subscription server-side (linked to client or guest quote token).
- When an abandoned cart is eligible, send a push notification to the saved subscription via VAPID-signed web-push requests.
Service Worker (sw.js)
// sw.js
self.addEventListener('push', fonction(event) {
const data = event.data ? event.data.json() : { title: 'Cart reminder', body: 'You left something in your cart' };
const options = {
body: data.body,
icon: '/favicon-192.png',
badge: '/badge-72.png',
data: data.url
};
event.waitUntil(self.registration.showNotification(data.title, options));
});
self.addEventListener('notificationclick', fonction(event) {
event.notification.close();
const url = event.notification.data || '/';
event.waitUntil(clients.openWindow(url));
});
Front-end subscription code (register & subscribe)
if ('serviceWorker' in navigator && 'PushManager' in window) {
navigator.serviceWorker.register('/sw.js').then(async fonction(reg) {
const permission = await Notification.requestPermission();
if (permission !== 'granted') return;
const subscription = await reg.pushManager.subscribe({
utilisateurVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array('')
});
// send subscription to server
await fetch('/rest/V1/abandonedcart/save-subscription', {
méthode: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.chaîneify({ subscription })
});
});
}
fonction urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
Server-side: sending push with PHP (Minishlink/web-push)
Install web-push library:
composer require minishlink/web-push
<?php
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;
$auth = [
'VAPID' => [
'subject' => 'mailto:ops@yourdomain.com',
'publicKey' => 'YOUR_VAPID_PUBLIC',
'privateKey' => 'YOUR_VAPID_PRIVATE',
],
];
$webPush = new WebPush($auth);
$subscription = Subscription::create(json_decode($savedSubscriptionJson, true));
$payload = json_encode([
'title' => 'You left items in your cart',
'body' => 'Tap to complete your purchase — we reserved your cart for 24 hours',
'url' => 'https://yourstore.com/paiement/cart/?quote=' . $quoteId . '&utm_source=push&utm_medium=abandoned_cart'
]);
$rapport = $webPush->sendOneNotification($subscription, $payload);
// handle rapport / clean subscriptions if expired
Linking subscriptions to carts
When the front-end sends the subscription JSON, store it with the client_id (if logged in) or with the quote token for guests. That way your cron can look up the relevant subscription for the cart and send a targeted push.
Part 4 — Personalizing messages basé sur behavior & cart valeur
Personalization drives higher conversion. Keep the contenu simple for SMS and more descriptive for e-mail. Personalization variables to consider:
- Cart valeur (grand total)
- Top product(s) — first item name, SKU, image URL
- Customer name / last seen page
- Number of items
- Behavioral signals: coupon applied previously, viewed page produit, high-intent events (paiement started)
Example: building a simple message template
$templateSms = "Hey {name}, you left {items_count} item(s) in your cart (Total: {cart_total}). Complete commande: {link}";
$vars = [
'name' => $clientName ?: 'there',
'items_count' => $quote->getItemsCount(),
'cart_total' => $quote->getGrandTotal(),
'link' => $this->buildRecoveryLink($quote)
];
$message = strtr($templateSms, tableau_combine(tableau_map(fonction($k){return '{'.$k.'}';}, tableau_clés($vars)), tableau_valeurs($vars)));
echo $message;
For e-mails you can render product images and add a limited-time coupon in the template. Generate code promos on demand using Magento's coupon APIs and insert them into the message if you want to incentivize high-valeur carts.
Segmentation exemples
- High-valeur carts (> $150): send a personalized SMS within 1 hour + e-mail with 10% coupon after 24 hours if not converted.
- Low-valeur carts (< $50): wait 6–12 hours, send push first (if subscribed), e-mail later.
- Product-specific: if cart contains fragile/expensive categories, include trust and shipping info in the message.
Part 5 — Automation flux de travails and triggers
Two types of triggers are useful:
- Temporal triggers: after 1 hour, after 24 hours, 72 hours.
- Behavioral triggers: utilisateur returned to page produit, applied coupon, abandoned during paiement, or cross-device activity.
Implementing flux de travails: build a simple state machine for each quote with states tel que NEW, SENT_SMS_1, SENT_PUSH_1, SENT_EMAIL_1, RECOVERED, EXPIRED. Your cron or queue worker updates state when sending messages and checks for recovery (commande placed) to stop further messaging.
// pseudo-flux de travail
if (quote.state == 'NEW' && quote.updated_at < 1 hour ago) {
if (quote.client_phone && !opted_out_sms) sendSms(quote);
if (pushSubscriptionExists(quote)) sendPush(quote);
quote.setState('SENT_1');
}
if (quote.state == 'SENT_1' && quote.updated_at < 24 hours ago && cartValue > 150) {
// send coupon + e-mail
sendEmailWithCoupon(quote);
quote.setState('SENT_2');
}
Behavioural trigger exemple: if the utilisateur returns and views product >= 3 times after abandonment, escalate with SMS. Vous pouvez track behavioral events via a front-end tracker that stores counts on the quote (via AJAX) or by tracking page views in your analytics and triggering via webhook.
Part 6 — Measuring effectiveness & ROI with Google Analytics
Measuring the performance of each channel (SMS vs push vs e-mail) is critical. Use tagged recovery links, GA events, and e-commerce revenue data to compute recovery rate, incremental revenue and ROI.
1) Tagging recovery links
In your messages include URLs that restore the cart and include UTM paramètres:
https://yourstore.com/paiement/cart/?quote=12345&utm_source=sms&utm_medium=abandoned_cart&utm_campaign=abandoned_2025_05
For push, add utm_source=push; for e-mail, utm_source=e-mail. This will allow GA to attribute the commande to the proper channel.
2) GA4 implémentation: event and e-commerce tagging
When a tagged link is clicked and leads to an commande, ensure the commande event is recorded in GA with purchase valeur and commande_id. In GA4 you will see revenue attributed to the session that had the UTM tags (first non-direct click rules apply).
// exemple: fire a GA4 event on restored cart landing page
window.gtag('event', 'abandoned_cart_recovery_click', {
méthode: 'sms', // sms | push | e-mail
quote_id: '12345'
});
When purchase completes, make sure the e-commerce 'purchase' event is sent with commande valeur and commande_id so GA can tie revenue back to the session that had the UTM.
3) Calculating ROI
Define the costs:
- SMS cost per message (e.g., $0.01 – dépend de region)
- Push cost (usually free besides dev/hosting time)
- Email cost (ESP fees allocated per message)
Basic ROI calculation for a period:
recovery_revenue_channel = SUM(revenue attributed to channel)
cost_channel = number_of_messages * unit_cost
roi = (recovery_revenue_channel - cost_channel) / cost_channel
Example: SMS campaign sent 10,000 messages, 1% recovered commandes, average commande valeur $80
- Recovered commandes = 100
- Revenue = 100 * 80 = $8,000
- Cost = 10,000 * $0.02 = $200
- ROI = (8,000 - 200) / 200 = 38x
That’s simple math but you doit être careful to attribute correctly: if an e-mail click later converted, GA rules might attribute differently. Using first touch UTM on recovery click est recommandé when measuring the immediate effect of that channel.
4) Use BigQuery or GA4 Explorations for deeper analysis
Export GA4 to BigQuery (if available) and join purchase events with campaign UTM to build a precise attribution table. Track metrics tel que:
- Recovery rate per channel
- AOV per recovered commande
- Cost per recovered commande
- Net profit from recovered commandes (subtract coupon discounts and message costs)
Part 7 — Practical exemples and full flow
Let’s stitch everything together with a simple exemple flow for a non-logged-in shopper:
- User adds items to cart and leaves. A guest quote is created with quote_id=12345.
- Front-end asks permission for push. User accepts; subscription saved connected to quote token.
- Après 2 hours cron finds quote 12345 as abandoned and checks: push subscription present, no phone number. The system sends a push with a deep link: https://yourstore.com/paiement/cart/?quote=12345&utm_source=push&utm_medium=abandoned_cart
- User clicks push within 30 minutes and completes commande. GA logs purchase with utm_source=push and revenue is attributed to push campaign.
- If the utilisateur hadn’t clicked push, the flux de travail would wait 24 hours and then send an e-mail with dynamic images and 5% coupon for carts over $100.
Example: PHP snippet to create a deep-recovery link
private fonction buildRecoveryLink($quoteId, $channel)
{
$base = 'https://yourstore.com/paiement/cart/';
$utm = sprintf('?quote=%s&utm_source=%s&utm_medium=abandoned_cart', $quoteId, $channel);
return $base . $utm;
}
Part 8 — Privacy, compliance, and deliverability
Two important non-fonctional items:
- Consent: Always ensure you have proper consent for marketing SMS/push. For transactional (commande-related) SMS some regions allow them without explicit marketing consent but check local law.
- Unsubscribe: Provide easy opt-out for SMS (e.g., reply STOP) and allow revocation of push subscriptions via site settings.
Deliverability conseils:
- Use a reputable provider (Twilio/MessageBird) and set up proper sender IDs and registration where required.
- Monitor bounce and unsubscribe rates; prune bad numbers and expired push subscriptions.
- For push, handle push endpoint expirations and remove invalid subscriptions from DB.
Part 9 — Operational conseils and recommended cadence
Suggested starting cadence (you can A/B test):
- 0h: no message (give a short cooling-off)
- 1–2h: Push (if opted in) + short SMS (if phone available) — friendly reminder
- 24h: Email with product images and suggested urgency — optionally a small coupon for high-valeur carts
- 72h: Final SMS or e-mail with last-chance coupon (if cost justified)
Always include a stop condition: when commande is created for the quote, stop sending messages, and mark the flux de travail as RECOVERED.
Part 10 — A/B tests and KPIs
Run experiments on:
- Channel commande: SMS first vs push first
- Timing: 1 hour vs 6 hours vs 24 hours
- Incentives: percentage coupon vs free shipping vs no coupon
KPIs to track:
- Recovery rate (commandes / abandoned carts)
- Recovered revenue
- Cost per recovered commande
- Net margin after coupon cost and message cost
- Subscriber opt-out rate
Closing notes and next étapes
If you’re running Magento 2 on magefine hosting, think about the operational side: put cron workers on reliable nodes, secure your API clés in env or Magento config with proper ACL, and ensure your queue workers can scale during a peak. Start with a small test segment and measure impact before broad rollout.
Résumé checklist before launching:
- Implement quote finder cron and flux de travail state machine.
- Add Twilio / MessageBird wrappers and admin config.
- Implement Service Worker + subscription endpoint + push send logic.
- Personalization templates and coupon integration for high-valeur carts.
- UTM tagging and GA4 purchase event validation.
- GDPR / consent and opt-out flows for SMS/push.
- Monitoring and cleanup for expired subscriptions / invalid phone numbers.
Si vous want, I can help sketch the full Magento 2 structure du module with fichiers (module.xml, di.xml, etc.), or provide a ready-to-adapt sample repository layout you can drop into a magefine-hosted store. Want that next?
Keywords to keep in mind for SEO: Magento 2 abandoned cart recovery, SMS Twilio Magento, MessageBird Magento 2, bligneser push Service Worker Magento, abandoned cart automation, measure abandoned cart ROI, magefine.