Relance paniers abandonnés : séquences email+SMS et segmentation PrestaShop

Identifier et relancer les bons paniers dans PrestaShop : segmentation pragmatique, orchestration email+SMS, idempotence, monitoring et conformité RGPD.

Écran d'ordinateur affichant des graphiques et des données de sécurité.

Un “panier abandonné” n’est pas un concept fonctionnel unique dans PrestaShop : c’est un état reconstruit à partir de ps_cart, de l’existence (ou non) d’une commande liée, et du niveau d’identification du visiteur. Tant que vous ne figez pas une définition opérationnelle (délai, signaux d’intention, exclusion des faux paniers), vos séquences email+SMS vont arroser au hasard — et vos métriques seront ininterprétables.

Dans la pratique (et c’est particulièrement vrai sur des boutiques FR/EU très “mobile-first”), la récupération de panier est un problème de données + orchestration : identifier les bons paniers, au bon moment, avec le bon canal… sans dégrader les performances ni vous exposer RGPD/délivrabilité. L’objectif n’est pas de “tout relancer”, mais de relancer ce qui a une chance raisonnable de convertir avec un coût (et une pression marketing) acceptable.

Table des matières :

  1. Définir un panier abandonné exploitable dans PrestaShop (et accepter les limites du cœur)
  2. Segmentation PrestaShop : extraire les bons signaux sans exploser MySQL
  3. Séquences email + SMS : timing, idempotence, et anti-spam (sans se mentir)
  4. Implémentation PrestaShop : module, hooks, cron/CLI, et orchestration externe
  5. Délivrabilité, sécurité et observabilité : la relance peut casser votre prod
  6. RGPD/ePrivacy et mesure : consentement prouvable, rétention courte, A/B test propre

Définir un panier abandonné exploitable dans PrestaShop (et accepter les limites du cœur)

Dans PrestaShop 8.1.x et 9.x (PHP 8.2/8.3), la donnée “panier” vit principalement dans ps_cart (métadonnées) et ps_cart_product (lignes). Un panier “abandonné” est généralement un panier non converti (ps_orders.id_cart absent) et inactif depuis un seuil T basé sur ps_cart.date_upd (ou date_add selon votre logique). La première erreur classique consiste à déclencher une relance sur des paniers “bruit” : paniers tests, bots, paniers vides, ou paniers modifiés par un retour arrière navigateur.

Le cœur PrestaShop ne vous donne pas un pipeline prêt pour : (1) capturer l’email tôt, (2) segmenter finement, (3) orchestrer une séquence multi-étapes idempotente. Et surtout, un panier n’est pas forcément rattaché à un email : tant que le client n’a pas créé de compte / commencé le checkout, vous n’avez souvent que id_guest et un cookie. En checkout multi-étapes “classique”, l’email arrive tard. Si vous voulez relancer des paniers “top funnel”, il faut explicitement implémenter une capture d’email (avec consentement) ou adapter le checkout (voir aussi l’impact produit/UX dans Passage en caisse PrestaShop : checkout en une page et express checkout).

Pour cadrer, prenez une définition pragmatique (et documentez-la dans un README interne : elle deviendra votre “contrat” de mesure) :

  • Paniers éligibles : au moins 1 ligne (ps_cart_product), total TTC ≥ un seuil, id_shop connu, id_lang connu.
  • Non convertis : aucun ps_orders associé (et idéalement aucune commande “échouée” si vous la considérez comme conversion partielle).
  • Inactifs : TIMESTAMPDIFF(MINUTE, c.date_upd, NOW()) >= X.
  • Contactables : email valide et/ou MSISDN (téléphone) avec consentement.

Ajoutez ensuite des exclusions simples qui évitent 80% du bruit, sans complexifier votre modèle :

  • Exclure les paniers “vides” fonctionnellement : lignes = 0, ou uniquement des produits indisponibles/supprimés (cas fréquent quand un produit passe hors stock entre-temps).
  • Exclure les paniers “trop frais” sur mobile : un utilisateur peut interrompre son achat (appel, métro, batterie) et revenir 10 minutes après. Un seuil T trop bas crée de la surpression (et des plaintes).
  • Exclure certains moyens de paiement si vous avez des parcours asynchrones : par exemple, si votre PSP crée une commande “en attente de paiement” (selon configuration), vous ne voulez pas lancer une relance panier si c’est désormais une relance “paiement en attente”. Dans ce cas, votre logique doit regarder aussi l’état de commande (pas seulement l’existence d’une commande).

Un point souvent oublié : le “temps” doit être cohérent avec votre contexte serveur/boutique. Si vos jobs s’exécutent en UTC mais que vos équipes pilotent en heure de Paris (CEST/CET), documentez-le et standardisez (idéalement : stocker/traiter en UTC, afficher en local).

Côté benchmark, ne partez pas d’une “norme” floue : Baymard Institute publie un taux moyen documenté d’abandon autour de 70% (moyenne multi-études, mise à jour régulièrement) :

“The average documented online shopping cart abandonment rate is 70.19%.” (Baymard Institute, Cart Abandonment Rate Statistics)

Baymard — Cart Abandonment Rate

Ce chiffre n’est pas un objectif ; c’est un ordre de grandeur qui vous rappelle qu’une relance bien faite peut avoir un impact… mais que la majorité des paniers abandonnés ne sont pas récupérables (mauvaise intention, simple repérage de prix, comparaison, etc.).

Segmentation PrestaShop : extraire les bons signaux sans exploser MySQL

Une segmentation utile en relance paniers abandonnés doit être calculable (coût SQL acceptable), stable (pas de segments qui changent à chaque exécution) et actionable (message différent, canal différent, incentive différent). Typiquement : valeur panier, récence, marge/stock, catégories, historique client (RFM), device/canal d’acquisition si vous l’avez.

Sur un site à volume, la première contrainte est MySQL/MariaDB : les relances sont souvent déclenchées par cron toutes les 5–15 minutes, donc vous allez scanner ps_cart très souvent. Si votre extraction fait des JOIN lourds + sous-requêtes sur ps_cart_product et ps_product, vous allez finir par l’observer dans le slow query log. Pour cadrer la méthode de diagnostic et d’indexation, référez-vous à Requêtes MySQL lentes PrestaShop : activer slow query log et à l’approche plus large dans Développeur MySQL : optimiser requêtes, schémas et performances en production.

Exemple de requête de base (à adapter à votre multi-boutique, monnaie, règles de taxes et panier) pour récupérer des paniers non convertis, inactifs depuis 60 minutes, avec un client identifié :

SELECT
  c.id_cart,
  c.id_customer,
  c.id_shop,
  c.id_lang,
  c.date_upd,
  cu.email,
  cu.id_default_group
FROM ps_cart c
JOIN ps_customer cu ON cu.id_customer = c.id_customer
LEFT JOIN ps_orders o ON o.id_cart = c.id_cart
WHERE
  o.id_order IS NULL
  AND c.id_customer > 0
  AND TIMESTAMPDIFF(MINUTE, c.date_upd, NOW()) >= 60
  AND EXISTS (SELECT 1 FROM ps_cart_product cp WHERE cp.id_cart = c.id_cart)
ORDER BY c.date_upd ASC
LIMIT 500;

Points à noter : (1) EXISTS évite un JOIN + GROUP BY pour juste vérifier qu’il y a des lignes ; (2) le LIMIT force une consommation progressive (vous aurez besoin d’un curseur/watermark, voir section orchestration) ; (3) selon vos volumes, vous finirez par créer un index composite du type (id_customer, date_upd) ou (id_shop, date_upd) — à tester, pas à appliquer “par principe”. Les index inutiles sur une table très écrite comme ps_cart peuvent dégrader les performances d’écriture.

Pour que la segmentation reste actionable, commencez petit et explicite. Par exemple, une matrice simple (facile à discuter côté marketing et support) :

Signal Comment le calculer (simple) Décision relance
Valeur panier total TTC (caché/denormalisé) SMS réservé aux paniers élevés
Client connu id_customer > 0 + historique commandes Ton plus direct, moins d’explications, pas de remise immédiate
Nouveau client pas d’historique Reassurance (livraison/retours), preuve sociale
Risque stock drapeau calculé sur produits du panier Angle “disponibilité”, pas “promo”
Récence minutes/heures depuis date_upd Timing différent (T+1h vs T+24h)

Ne cherchez pas à calculer tout “à la volée” à chaque run cron : matérialisez dans une table dédiée (par exemple ps_abandonedcart_candidate) des champs dénormalisés (total, nbitems, topcategory, lowstockflag, updated_at) mis à jour sur événement panier (hook) et consommés par le dispatcher.

Deux détails concrets qui évitent des douleurs en prod :

  • Watermark/curseur : plutôt que de rescanner “tout ce qui est inactif”, traitez par fenêtre (date_upd croissant) et stockez votre dernier id_cart/timestamp traité par shop. Ça réduit les lectures répétitives et rend votre exécution plus prévisible.
  • Verrouillage sans blocage : si vous avez plusieurs workers (ou un cron qui peut se chevaucher), faites un verrouillage applicatif via statut (pending → processing → done) avec mise à jour atomique côté SQL, au lieu de compter sur des “verrous” implicites.

Pour aller au-delà du “tout le monde reçoit la même relance”, calculez un score de priorité. Exemple concret :

  • Panier à forte valeur (ex. > 150€) + client existant : séquence email + SMS.
  • Panier faible + client nouveau : email uniquement, pas d’incentive immédiat.
  • Risque stock (produit faible stock) : message sans réduction, angle “disponibilité limitée”.

Mini-scenario (très réaliste en e-commerce FR) : un client ajoute une paire de chaussures en taille 42, quitte, puis revient 8h après ; entre-temps la taille passe “faible stock”. Votre deuxième message (T+24h) a plus d’impact s’il met en avant la disponibilité et les retours faciles, plutôt qu’une remise générique qui grève votre marge.

Séquences email + SMS : timing, idempotence, et anti-spam (sans se mentir)

Une séquence efficace n’est pas seulement “J+1 / J+3”. Elle doit coller au cycle d’achat de votre secteur, à votre logistique, et au comportement device. En pratique, une base robuste (à A/B tester) ressemble souvent à :

  • T+1h : email rappel simple (panier + CTA), sans réduction.
  • T+24h : email avec preuve sociale/FAQ livraison/retours (lever les objections).
  • T+48–72h : SMS uniquement pour segments à forte probabilité (valeur élevée, client connu, consentement explicite), ou email “dernier rappel”.

L’erreur fréquente est de penser “promo = conversion”. Dans beaucoup de verticales, le frein n’est pas le prix mais une objection claire : frais de port, délai, retours, moyen de paiement, confiance. Une relance utile ré-expose les informations décisionnelles au bon moment :

  • frais et délais de livraison (et éventuel Click & Collect si vous en avez un),
  • modalités de retour (durée, coût, échange),
  • disponibilité / stock,
  • support (contact, horaires), surtout si vos clients sont en France métropolitaine avec une attente de réponse “rapide”.

L’idempotence est non négociable : votre cron peut se lancer deux fois, un worker peut retry après timeout, un webhook peut être rejoué. La bonne pratique consiste à enregistrer chaque “action de relance” dans une table avec une contrainte d’unicité, par exemple (id_cart, step_code) ; si l’insertion échoue (duplicate key), vous sautez l’envoi. Sans ça, vous enverrez des doublons, vous augmenterez les plaintes spam et vous polluerez vos stats.

Ajoutez aussi une limite de pression (frequency cap) simple, sinon vous “spammez” vos meilleurs clients :

  • maximum X relances par panier,
  • maximum Y messages par client sur Z jours (tous paniers confondus),
  • stop immédiat à la conversion et à la désinscription/STOP (sur email et sur SMS).

Le SMS doit rester un canal “cher” (coût et irritation utilisateur). Un taux d’ouverture théorique élevé ne suffit pas à justifier un blast. Le point technique est surtout la conformité + la traçabilité (opt-in, STOP, horaires) et la fiabilité du fournisseur (DLR, retries, callbacks). Si vous partez sur OVHcloud, le volet callbacks/accusés de réception est détaillé dans API SMS OVHcloud : réponses SMS, callbacks temps réel et mode Pull.

Enfin, instrumentez vos liens (UTM + identifiant de relance) mais sans bricoler : stockez un message_id interne et passez-le dans les URLs. Évitez d’exposer un identifiant incrémental brut qui facilite l’énumération. Si vous exposez un endpoint de “restore cart”, signez le token (HMAC) et expirez-le.

Checklist “anti-spam” minimaliste (souvent oubliée dans les implémentations maison) :

  • SPF/DKIM/DMARC alignés sur le domaine d’envoi.
  • Header List-Unsubscribe + idéalement “one-click” (spécification IETF : RFC 8058)
  • Gestion des bounces/complaints : suppression automatique des adresses en erreur durable.
  • Segmentation “qualité” : ne pas relancer des emails non vérifiés / typos évidentes (si vous collectez tôt, c’est fréquent).

Implémentation PrestaShop : module, hooks, cron/CLI, et orchestration externe

Ciblez une implémentation testable sur PrestaShop 8.1.7+ et 9.0+ : un module qui (1) écoute les événements panier, (2) alimente une table de “candidats”, (3) exécute un dispatcher via cron/CLI, (4) appelle un ESP/SMS provider, (5) journalise.

Hooks utiles (et ce que le cœur ne fait pas pour vous)

Les hooks pertinents varient selon votre thème/checkout, mais on retombe souvent sur : actionCartSave, actionCartUpdateQuantity, actionValidateOrder (pour stopper la séquence dès conversion), et éventuellement des hooks front pour capter un email (si votre parcours le permet). La référence la plus fiable côté mécanisme et bonnes pratiques reste la documentation développeur PrestaShop : https://devdocs.prestashop-project.org/.

Le piège : faire du “send email” directement dans le hook (donc dans le thread HTTP du client). C’est le meilleur moyen de dégrader le TTFB et de créer des pannes en chaîne. Les hooks doivent écrire (queue/table) et sortir vite.

Concrètement, visez un modèle en 2 tables (vous pouvez les préfixer avec _DB_PREFIX_ comme le reste) :

  • une table “candidate” (état courant du panier, denormalisée),
  • une table “message log” (historique des steps envoyés + réponses provider).

Même sans donner tout le DDL ici, l’idée clé est : distinguer l’état courant (mutable) de l’historique (append-only). C’est ce qui rend l’idempotence et l’audit possibles.

Pour les commandes en ligne de commande, la base est de passer par bin/console (surtout en PS 8/9) ; voir les conventions dans Commandes CLI PrestaShop : liste, catégories et options d’aide.

Dispatcher via CLI : un squelette réaliste

Implémentez une commande du style abandonedcart:dispatch qui :

  1. sélectionne les candidats “à traiter” (avec verrouillage optimiste : status = pending → processing),
  2. calcule le step à envoyer (T+1h, T+24h…),
  3. génère payload + token de restauration,
  4. appelle l’API email/SMS,
  5. écrit un log d’envoi (réponse provider, latence, code).

Deux patterns d’orchestration qui passent bien en production :

  • Traitement en batch (prévisible) : vous traitez N candidats max par run (ex. 200/500), et vous laissez le cron tourner souvent. Avantage : faible variance, meilleure maîtrise du load MySQL.
  • Traitement “near real-time” : vous poussez des événements vers une orchestration externe (webhook/queue) et vous ne faites que de la consolidation dans PrestaShop.

Si vous externalisez l’orchestration (ce qui est souvent plus propre), n8n est un bon candidat pour chaîner “webhook → enrichissement → envoi provider → log”. Vous avez un exemple de patterns webhook côté e-commerce dans n8n : automatiser la gestion des commandes e‑commerce via webhooks. Dans ce cas, PrestaShop se contente de pousser des événements (ou d’exposer un endpoint consommé par n8n) et vous réduisez la complexité dans le cœur.

APIs PrestaShop : Webservice legacy vs Admin API (PS9)

Pour des intégrations “lecture/écriture” sans exécuter du code dans PrestaShop, le Webservice legacy reste la voie la plus standard (ressources carts, customers, etc.), avec ses limites (auth basique, exposition à durcir). L’activation et le modèle de clé sont couverts dans Webservice PrestaShop : activer l’API et créer une clé d’accès. Quoi qu’il arrive, appliquez un minimum de sécurité API (moindre privilège, rate limiting, rotation) : API : sécuriser apikey, limiter le débit et renforcer la conformité.

Un rappel “terrain” : même si vous avez une Admin API plus moderne (PS9), une relance panier touche vite des données sensibles (emails, téléphones, contenu de panier). Pour réduire l’exposition, évitez de donner des clés capables de lire tous les clients si votre use-case se limite à “pousser un événement pour un panier donné”.

Délivrabilité, sécurité et observabilité : la relance peut casser votre prod

Sur l’email, 2024 a été un tournant : Google et Yahoo ont durci les exigences pour les “bulk senders” (authentification, taux de spam, désinscription). Le billet officiel Google (Workspace) détaille les exigences et le calendrier : Gmail email sender guidelines (Google Workspace). En 2026, ignorer SPF/DKIM/DMARC + désinscription (et une hygiène de listes minimale) revient à saboter la délivrabilité, même si votre logique de segmentation est parfaite.

À ajouter dans votre “definition of done” avant de scaler :

  • authentification alignée (SPF/DKIM/DMARC) + monitoring,
  • “List-Unsubscribe” (et traitement effectif des désinscriptions),
  • cohérence From/Reply-To (éviter les changements brutaux de domaine),
  • montée en charge progressive si nouveau domaine/IP.

Côté sécurité, les relances créent souvent de nouveaux endpoints : callbacks SMS (DLR), webhooks ESP, endpoint de restauration panier, tracking. Chaque endpoint est une surface d’attaque (scan, flood, tentatives d’énumération token). Si vous avez un WAF en frontal, vous devez régler finement les faux positifs sur le checkout et sur ces endpoints, sinon vous allez bloquer de vrais clients et vous auto-DDoS (voir WAF PrestaShop : réduire les faux positifs et sécuriser le checkout).

Enfin, instrumentez. Une relance qui “marche” mais génère des 503 ou des timeouts côté API provider est un échec opérationnel. Loggez latence, taux d’erreur, taille des batches, et exceptions côté PrestaShop. Pour la mise en place d’un socle de logs/alerting applicatif, vous pouvez vous appuyer sur PrestaShop monitoring d’erreurs : logs PHP, MySQL, JavaScript et alertes e-mail et, si vous observez des symptômes front, sur Erreur HTTP 503 : diagnostic serveur, logs et ressources.

Un bon “pack” d’observabilité spécifique à la relance paniers abandonnés :

  • métriques d’exécution : durée du cron, nb candidats scannés, nb envoyés, nb ignorés (déjà envoyé / opt-out / converti),
  • métriques provider : taux d’erreur par code, latence p95, taux de DLR (SMS) si disponible,
  • métriques business : conversions incrémentales (via holdout), revenu incrémental, taux de désinscription/STOP,
  • signaux de dérive : hausse soudaine des paniers éligibles (bot, tracking cassé), hausse des duplicates (idempotence rompue), hausse des timeouts (API ou DB).

RGPD/ePrivacy et mesure : consentement prouvable, rétention courte, A/B test propre

La relance panier touche à la prospection électronique (email/SMS) et à la gestion de données personnelles (identifiants, contenu de panier, potentiellement des catégories sensibles selon votre catalogue). Vous devez être capable de prouver : base légale, consentement quand requis, et mécanisme d’opposition. La CNIL rappelle le principe d’opt-in pour la prospection électronique et l’exception “clients existants” (produits similaires, possibilité de s’opposer) : https://www.cnil.fr/fr/la-prospection-commerciale-par-email. Pour le SMS, les exigences sont encore plus strictes en pratique (STOP, identification de l’émetteur, horaires raisonnables), et la tolérance utilisateur est plus faible.

Techniquement, ça implique une modélisation claire : conservez un journal de consentement (date, source, version du texte, IP si pertinent), un statut “opt-out”, et reliez-le à vos canaux (email ≠ SMS). Votre table de relance doit aussi gérer une politique de rétention : inutile de stocker les payloads complets 24 mois. Stockez des empreintes (hash) et des métadonnées (step, providermessageid, timestamps).

Un compromis souvent accepté en audit : conserver le minimum nécessaire pour investiguer un incident (ex. “pourquoi ce client a reçu 2 SMS ?”) sans conserver le détail du contenu de panier au-delà d’une courte fenêtre. Exemple de stratégie simple :

  • payload détaillé (lignes panier) : 7 à 30 jours max,
  • logs d’envoi (message_id, step, timestamps, statut) : plus long si besoin de preuve opérationnelle,
  • consentement : selon vos obligations internes, mais toujours traçable et accessible.

Pour la partie “automatisation avec moindre privilège et journaux d’audit”, la méthode est détaillée dans Automatisation PrestaShop : sécurité RGPD, moindre privilège et journaux d’audit.

La mesure doit éviter l’auto-illusion : un panier peut se convertir sans que la relance soit la cause. Faites au minimum un holdout (ex. 10% des paniers éligibles ne reçoivent rien) et comparez le taux de conversion incrémental, pas seulement le “recovered revenue” attribué par clic.

Pour que le holdout reste propre :

  • tirage stable (ex. hash de id_cart modulo 10) pour éviter que le même client passe de “test” à “contrôle” selon le run cron,
  • mêmes règles d’éligibilité (seul l’envoi diffère),
  • analyse par segment (un SMS peut être incrémental sur “client connu panier élevé”, et négatif sur “nouveau client panier faible”).

KPI utiles :

  • taux de récupération incrémental (email seul vs email+SMS),
  • temps médian jusqu’à conversion après step 1,
  • taux de désinscription/STOP,
  • taux de plaintes spam (si vous avez accès aux feedback loops),
  • impact perf (latence cron, queries, erreurs).

À partir de là, votre segmentation devient un système itératif : vous coupez les segments toxiques (fort taux de plainte), vous renforcez ceux qui convertissent (valeur/marge), et vous adaptez le timing au cycle d’achat réel. Le cœur PrestaShop ne vous “donnera” pas cette maturité : c’est votre instrumentation, votre modèle de données et votre discipline de déploiement qui la rendent possible.


À lire aussi