Skip to main content

Campagne marketing - Réservations non converties

Contexte

Cette campagne concerne les clients ayant réservé un produit mais n'ayant pas finalisé leur achat malgré les relances existantes.

Logidav reste responsable de la cible et des données client. Menzzo/Magento reste responsable de la destination d'achat via le module Axelites_Restock.

Objectif

Cette étape met à disposition un espace de travail pour préparer et valider les deux maquettes emails :

  • produit de retour en stock
  • fin imminente de validité du code

Elle permet de valider les objets, les textes, les maquettes desktop/mobile, les CTA, les variables dynamiques et le comportement de preview avant intégration finale.

Règle de ciblage

Une réservation non convertie est une ligne mz_product_reservation :

  • status = confirmed
  • is_test = 0
  • aucune commande processing ou complete
  • même customer_email + sku
  • commande recherchée après reserved_at

Les réservations sont exclues de la campagne si elles sont déjà appelées et :

  • called_status = promo_code, c'est-à-dire "A récupéré le code promo"
  • called_status_reason = not_interested, c'est-à-dire "N'est pas intéressé"

Les autres statuts d'appel ne sont pas exclus pour l'instant afin de garder un changement minimal.

Fichiers principaux

  • src/AppBundle/Repository/ProductReservationRepository.php
  • src/AppBundle/Command/Product/DebugUnconvertedProductReservationsCommand.php
  • src/AppBundle/Command/Product/PreviewUnconvertedReservationMarketingEmailsCommand.php
  • src/AppBundle/Command/Product/SendTestUnconvertedReservationMarketingEmailCommand.php
  • src/AppBundle/Command/Product/GenerateUnconvertedReservationMagentoCtaCommand.php
  • src/AppBundle/Services/Marketing/UnconvertedReservationCtaBuilder.php
  • src/AppBundle/Services/Marketing/UnconvertedReservationMarketingEmailService.php
  • src/AppBundle/Services/Restock/MenzzoRestockApiClient.php
  • src/AppBundle/Resources/views/UnconvertedReservationMarketing/_layout.html.twig
  • src/AppBundle/Resources/views/UnconvertedReservationMarketing/stock_available.html.twig
  • src/AppBundle/Resources/views/UnconvertedReservationMarketing/expires_soon.html.twig

Email 1 - Produit de retour en stock

Objet :

Vite, votre produit est EN STOCK (Zéro attente + votre remise expire) ⏰

Objectif :

Informer le client que son produit réservé est disponible et l'inciter à finaliser son achat.

CTA label :

Expédier mon produit tout de suite avec mon code

Variables utilisées :

  • customerFirstName
  • productName
  • productPhotoUrl
  • couponCode
  • discountLabel
  • deliveryDelayLabel
  • expirationLabel
  • ctaUrl
  • ctaLabel
  • unsubscribeUrl

Email 2 - Fin imminente de validité du code

Objet :

⏳ Plus que 4 heures pour vos -25% (Après, il sera trop tard)

Objectif :

Créer un sentiment d'urgence avant expiration du code.

CTA label :

Ajouter au panier avec mon code

Variables utilisées :

  • customerFirstName
  • productName
  • productPhotoUrl
  • couponCode
  • discountLabel
  • remainingValidityLabel
  • ctaUrl
  • ctaLabel
  • unsubscribeUrl

Variables dynamiques

VariableSource actuelleFallbackCommentaire
customerFirstNameEmail client de la réservation ou données injectées en previewChaîne videPermet une personnalisation légère si disponible.
productNameRéservation enrichie via les données produitvotre produit réservéNom affiché dans le contenu de l'email.
productPhotoUrlImage produit ou image de réservationChaîne videL'image est masquée si aucune URL n'est disponible.
couponCodeCTA dry-run, CTA Magento fourni, ou short URL Magento générée manuellementCode démo dry-runLe code réel doit venir de Magento/Axelites_Restock.
discountLabelConfiguration de la campagne-25%Libellé de remise affiché dans les maquettes.
deliveryDelayLabelConfiguration de la campagnemoins de 5 joursUtilisé dans l'email produit de retour en stock.
expirationLabelexpiresAt du CTA Magento/dry-rundans 48 heuresTexte d'expiration affiché dans l'email 1, derive du meme timestamp que l'expiration reelle du coupon/CTA.
remainingValidityLabelConfiguration de la campagne4 heuresTexte d'urgence affiché dans l'email 2.
ctaUrlCTA dry-run ou short URL Magento déjà généréeURL de previewDestination du bouton principal.
ctaLabelType d'emailLabel par défaut du templateTexte du bouton principal.
unsubscribeUrlCTA dry-run ou short URL unsubscribe déjà généréeURL de previewLien de désinscription à valider avant activation.

Commandes de validation

Preview HTML :

php bin/console menzzo:product-reservation:unconverted:email-preview

Preview avec réservation réelle :

php bin/console menzzo:product-reservation:unconverted:email-preview --reservation-id=1

Lint Twig :

php bin/console lint:twig src/AppBundle/Resources/views/UnconvertedReservationMarketing

Envoi manuel de test en dry-run :

php bin/console menzzo:product-reservation:unconverted:email-send-test --reservation-id=1 --type=stock_available --to=EMAIL_TEST --dry-run

Envoi manuel de test après validation :

php bin/console menzzo:product-reservation:unconverted:email-send-test --reservation-id=1 --type=stock_available --to=EMAIL_TEST

Vidage manuel du spool après un envoi de test :

php bin/console swiftmailer:spool:send --message-limit=1 -vvv

Vérification read-only de la cible :

php bin/console menzzo:product-reservation:unconverted:debug --limit=10

Génération manuelle du CTA Magento :

php bin/console menzzo:product-reservation:unconverted:cta-generate --reservation-id=1 --type=stock_available --website-ids=1

Cette commande est dry-run par défaut. L'option --execute crée un vrai coupon Magento et une vraie short URL Menzzo. Elle ne doit pas être utilisée sans validation explicite.

CTA Magento

Le CTA final ne doit pas être inventé côté logidav.

Logidav prépare la cible et les variables nécessaires à l'email. La destination d'achat est fournie par Menzzo/Magento via Axelites_Restock, qui gère la short URL, la redirection, l'ajout panier, le coupon et la désinscription.

Le type de route attendu pour le bouton principal est cart_redirect.

Les commandes CLI restent des points d'entrée manuels. La logique de ciblage, preview, résolution CTA, envoi de test et génération CTA Magento est portée par des services dédiés sous AppBundle\Services\Marketing\UnconvertedReservation.

Historique des envois email

Chaque envoi manuel de test non dry-run via menzzo:product-reservation:unconverted:email-send-test est historise dans mz_product_reservation_email_log.

Le statut enregistre est queued apres retour positif de SwiftMailer, car le projet utilise le spool SwiftMailer : cela ne signifie pas que le message est deja livre.

L'historique est consultable depuis /product/product-reservations, via le bouton Historique emails de la ligne de reservation.

Aucun envoi automatique n'est ajoute dans cette etape. Aucun cron n'est ajoute.

La creation de table est documentee dans docs/sql/create_product_reservation_email_log.md.

Les champs ajoutes pour le batch manuel (cta_expires_at, source_email_log_id) sont documentes dans docs/sql/alter_product_reservation_email_log_campaign.md.

Lancement manuel de campagne

La commande batch manuelle est :

php bin/console menzzo:product-reservation:unconverted:campaign-send

Elle n'a pas d'option --execute. Sans --type, elle traite les deux campagnes dans le meme run :

  • stock_available
  • expires_soon

L'option --type=stock_available ou --type=expires_soon reste disponible uniquement pour cibler un seul flux.

Modes disponibles :

  • --dry-run : simulation uniquement, aucun email, aucun coupon reel, aucune short URL reelle, aucun historique.
  • --test-recipient=EMAIL sans --dry-run : envoi reel controle vers une seule adresse de test, jamais vers les clients.
  • sans --dry-run et sans --test-recipient : envoi reel vers les emails clients.

Garde-fous production :

  • sans --limit, la commande traite tous les eligibles.
  • --limit=N limite le batch et reste plafonne a 200.
  • en mode reel client sans --limit, la commande affiche le warning : Real customer mode without --limit will process all eligible reservations.
  • stock_available ne selectionne que les reservations dont le SKU pointe vers un produit Logidav marque En_Stock, is_available = 1, avec inventory_qty ou qty positif.
  • le mode reel sans --type exige --website-ids, car stock_available cree un coupon Magento reel.
  • stock_available seul en mode reel exige aussi --website-ids.
  • expires_soon reutilise le dernier historique stock_available queued et ne cree pas de nouveau coupon par defaut.
  • La commande continue le batch si une reservation echoue et historise l'echec quand c'est possible.
  • Le spool SwiftMailer n'est jamais lance automatiquement.

Dry-run complet stock_available + expires_soon :

php bin/console menzzo:product-reservation:unconverted:campaign-send --dry-run

Envoi reel complet vers clients eligibles :

php bin/console menzzo:product-reservation:unconverted:campaign-send \
--website-ids=1

Envoi reel stock_available seul vers tous les clients eligibles :

php bin/console menzzo:product-reservation:unconverted:campaign-send \
--type=stock_available \
--website-ids=1

Envoi reel expires_soon seul vers tous les clients eligibles :

php bin/console menzzo:product-reservation:unconverted:campaign-send \
--type=expires_soon

Batch limite :

php bin/console menzzo:product-reservation:unconverted:campaign-send \
--website-ids=1 \
--limit=50

Envoi reel controle vers adresse de test :

php bin/console menzzo:product-reservation:unconverted:campaign-send \
--website-ids=1 \
--test-recipient=malek.kadri100@gmail.com

Vidage manuel du spool apres envoi reel :

php bin/console swiftmailer:spool:send --message-limit=<queued_count> -vvv

Timing Email 1 / Email 2

Email 1 stock_available est envoye quand le produit reserve est revenu en stock. Il cree un CTA/coupon Magento avec une validite de 48 heures (cta_expires_at = now + 48 hours).

Email 2 expires_soon cible les historiques stock_available queued dont cta_expires_at est entre maintenant et maintenant + 4 heures. Il reutilise le coupon/CTA de l'email 1 et ne cree pas de nouveau coupon par defaut.

Pour respecter la regle metier "a 20h la veille de l'expiration", un cron futur devrait lancer expires_soon autour de 20h. Aucun cron n'est ajoute dans cette PR.

Exemple de cron futur, non installe par cette PR :

0 20 * * * php bin/console menzzo:product-reservation:unconverted:campaign-send --type=expires_soon

Hors scope volontaire

  • pas de cron ajoute dans cette PR
  • pas d'envoi automatique planifie
  • envoi client reel possible uniquement par lancement manuel explicite de la commande
  • sans --limit, la commande traite tous les eligibles
  • utiliser --limit pour un batch controle si necessaire
  • validation marketing nécessaire avant intégration finale

Checklist validation marketing

  • Objet email 1 validé
  • Objet email 2 validé
  • Texte email 1 validé
  • Texte email 2 validé
  • CTA email 1 validé
  • CTA email 2 validé
  • Version desktop validée
  • Version mobile validée
  • Variables dynamiques validées
  • Désinscription validée