Envoyer des e-mails transactionnels avec un service tiers sous Symfony

Fabien Lemoine
7 min readMar 13, 2022

--

Comment combiner la puissance du composant Mailer et les fonctionnalités offertes par un prestataire d’envoi d’e-mails.

Crédit : Brett Jordan via Unsplash

Les e-mails transactionnels sont des messages envoyés automatiquement par une application pour notifier leurs utilisateurs de certains événements les concernant directement. Il peut s’agir :

  • D’un e-mail de confirmation de commande
  • D’un e-mail de connexion par lien magique
    ou de réinitialisation de mot de passe
  • D’un e-mail de bienvenue suite à l’inscription sur une application

Ces messages sont cruciaux, car ils font partie intégrante de l’expérience utilisateur. Ne pas les recevoir entraine des conséquences directes sur votre activité : utilisateurs bloqués, sollicitation du support, baisse de réputation…

Dans ce contexte, la notion de délivrabilité des messages s’impose comme primordiale, et une solution toute trouvée est disponible : passer par un service tiers dont l’envoi d’e-mails est la spécialité.

Objectifs

Nous allons voir comment acheminer vos e-mails transactionnels via un service tiers grâce au composant Mailer de Symfony et comment anticiper une éventuelle indisponibilité de ce service.

Ce n’est pas tout : le prestataire dispose d’informations sur le statut d’envoi des messages : Ont-ils été délivrés ? Ouverts par le destinataire ? Pourquoi certains messages n’ont-ils pas pu être transmis ? Nous verrons comment redescendre ces précieuses informations vers votre application en utilisant le principe de webhook.

Choix d’un prestataire

Pour cet exemple, nous allons utiliser Brevo (anciennement Sendinblue). D’autres services sont disponibles et également officiellement supportés par Symfony.

Brevo propose une offre de base gratuite qui autorise 300 e-mails transactionnels par jour. C’est parfait pour une application modeste ou pour tester le potentiel de la solution avant d’envisager de passer à une offre payante.

Enfin, Brevo est une solution française en conformité avec la réglementation européenne sur la protection des données (RGPD).

Prérequis

L’exemple ci-dessous est conçu avec Symfony 6+ avec autowire et autoconfig activés, le code peut néanmoins être adapté pour fonctionner avec des versions antérieures. Vos e-mails doivent être envoyés via le composant Mailer de Symfony.

Il est nécessaire de créer un compte Brevo. L’adresse expéditeur de vos e-mails transactionnels (par exemple : no-reply@mon-application.com) doit exister et être déclarée au préalable sur votre compte Brevo.

Cliquez ci-dessus pour créer un compte

Sommaire

  • Configurer l’envoi via Brevo
  • Anticiper une indisponibilité du service
  • Recevoir le statut de délivrabilité des e-mails envoyés (8 étapes)

Configurer l’envoi via Brevo

Cette étape ne va nécessiter aucun développement. Il va suffire d’installer un connecteur avec Brevo pour le Mailer de Symfony, et de renseigner une clé d’API pour permettre l’envoi des e-mails.

Installez le connecteur avec Composer comme ceci :

Installation du connecteur avec Composer

Ensuite, il va falloir mettre à jour le DSN du Mailer. Il s’agit d’une chaine de caractères qui représente la connexion à une source de données, ici notre prestataire d’envoi d’e-mails. Le DSN est défini en tant que variable d’environnement et se retrouve donc dans les fichiers .env de Symfony.

Paramétrage du connecteur Brevo

Sur votre interface de gestion Brevo, la clé d’API sera indiquée dans la section “Transactionnel”. Si vous avez oublié votre clé, il est possible d’en générer une nouvelle en vous rendant dans les paramètres de votre compte, section “SMTP & API”. Vous pouvez trouver plus d’explications dans la documentation.

C’est déjà fini, vos e-mails sortants transiteront désormais par Brevo.

Anticiper une indisponibilité du service d’envoi

Nous allons maintenant tirer parti du composant Mailer pour augmenter la résilience de votre application face aux imprévus. Pour cela, nous allons définir une méthode d’envoi alternative des e-mails dans l’éventualité ou l’API de Brevo puisse être temporairement injoignable.

Une fois encore, aucun développement supplémentaire ne sera nécessaire, il va suffire d’adapter le DSN du Mailer.

Mise en place du plan B

Le système de failover permet une haute disponibilité de la fonctionnalité d’envoi d’e-mails. Le Mailer tentera un nouvel envoi avec le second service d’acheminement si le premier échoue (vous pouvez d’ailleurs jouer l’extrême prudence et définir davantage de services de secours).

D’autres mécanismes sont disponibles nativement dans le Mailer, comme la possibilité d’avoir recours à du load balancing (ou répartition automatique des envois entre plusieurs services en bon français).

Recevoir le statut de délivrabilité des e-mails envoyés

Maintenant que l’envoi de nos e-mails est délégué à des spécialistes, profitons de l’opportunité d’enrichir fonctionnellement votre application.

Il devient possible de savoir si un e-mail a été délivré, ouvert, et même si le destinataire à cliqué sur l’un des liens contenus dans celui-ci. Cela permet de proposer une fonctionnalité d’accusés de réception. De même, il est possible de savoir si l’e-mail n’a pas pu être acheminé. Il est donc envisageable de prévoir par exemple l’invalidation d’un compte utilisateur si l’adresse e-mail associée à celui-ci est inexistante ou encore de bloquer l’envoi ultérieur d’e-mails depuis votre application vers ce destinataire si une erreur a eu lieu.

Pour cela, nous allons enfin écrire un peu de code PHP.

1/ Création d’une entité Doctrine

Tout d’abord nous allons créer une entité qui représentera chaque e-mail envoyé et stockera l’identifiant unique associé. Cette entité contiendra aussi le nom du dernier événement relatif au message (délivré, ouvert, …).

L’entité Message

Les attributs created et updated, respectivement utilisés pour stocker la date d’envoi du message et la date du dernier événement dont votre application a été notifiée, sont volontairement gérés manuellement.

La méthode isUpdateable permet de garantir que l’entité n’est mise à jour que lorsque c’est légitime : elle gère le cas d’un léger décalage horaire entre le serveur de votre application et les serveurs de Brevo, et aussi le cas ou les notifications d’événements ne parviendraient pas dans l’ordre chronologique.

2/ Obtenir un identifiant unique pour chaque e-mail envoyé

Voici un exemple de code qui vous permettra de récupérer automatiquement un identifiant unique pour chacun de vos e-mails envoyés.

Récupération de l’identifiant unique de chaque e-mail

Le point important ici est d’injecter TransportInterface plutôt que MailerInterface qui n’est pas capable de retourner l’identifiant unique.

Il peut être préférable de prévoir un service centralisé pour tous vos envois d’e-mails, ainsi, la logique de récupération et de stockage des identifiants uniques ne sera pas dupliquée à plusieurs endroits dans votre code applicatif. Une autre alternative consiste à dispatcher un événement contenant l’objet SentMessage retourné par le Mailer.

3/ Créer un contrôleur pour recevoir les appels webhook de Brevo

Grâce au principe de webhook, Brevo va appeler automatiquement une adresse URL de votre application pour vous donner des nouvelles de chaque e-mail envoyé. Nous créons donc un contrôleur pour traiter ces requêtes.

Contrôleur pour enregistrer l’état de l’e-mail envoyé

Brevo envoie les informations sur les e-mails tracés sous forme d’un objet au format JSON. Or, notre contrôleur s’attend à directement recevoir notre entité Message à mettre à jour ainsi que le nom et la date de l’événement dont nous sommes notifiés. Voyons comment régler cela à l’aide d’un ParamConverter.

4/ Mise en place d’un ParamConverter

Le ParamConverter va traiter les données brutes au format JSON contenues dans la requête envoyée par Brevo et ajouter des attributs à l’objet Request de Symfony. Ainsi notre contrôleur -créé à l’étape précédente- recevra uniquement les données dont il a besoin.

Cachez ce JSON que je ne saurais voir !

5/ Restreindre l’accès à la route dédiée au webhook

Nous voulons maintenant nous assurer que l’accès à cette route sera public, mais que seuls les serveurs de Brevo, ou éventuellement vous lorsque vous réalisez des tests en local, pourrez y accéder.

Nous ajoutons une règle d’accès dans le fichier security.yaml de Symfony.

Sécurisation de l’accès au contrôleur webhook

Les plages IP 185.107.232.0/24 et 1.179.112.0/20 correspondent aux serveurs de Brevo. Les adresses 127.0.0.1 et ::1 correspondent au réseau local pour vos tests.

6/ Tester le webhook localement

Pour tester le comportement de votre application à la réception des notifications Brevo, vous pouvez tout simplement utiliser la console Javascript des outils de développement intégrés à votre navigateur web préféré pour émuler une requête webhook.

Ouvrez la console en pressant la touche [F12], puis copiez-collez et adaptez le code suivant à vos cas de test :

Emuler une notification Brevo avec la console Javascript

Tous les formats de réponses possibles sont détaillés dans cette documentation.

7/ Activer le webhook sur la plateforme Brevo

C’est presque terminé, il faut maintenant activer le webhook.

Sur votre interface de gestion Brevo, il faut vous rendre dans la section “Transactionnel” puis dans les “Paramètres”. Vous pouvez choisir au cas par cas les événements pour lesquels vous souhaitez que votre application soit notifiée.

Activation du weebhook

Pour plus d’informations sur ce paramétrage, vous pouvez vous référer à cette documentation.

8/ Bonus : fichier de traductions pour les statuts des e-mails

Voici un fichiers de traductions françaises pour les différents événements pour lesquels vous pouvez être notifié.

Traductions françaises des différents événements

Le mot de la fin

Ce petit tutorial vous a permis de déléguer l’envoi de vos e-mails à un prestataire spécialisé, et de tirer parti de fonctionnalités complémentaires comme la possibilité d’accuser la réception d’un e-mail pour enrichir le fonctionnel de votre application.

Il ne vous reste plus qu’à vous concentrer sur votre cœur de métier !

N’hésitez pas à me faire vos retours dans les commentaires.

Bibliographie

Documentation du composant Mailer de Symfony :
https://symfony.com/doc/current/mailer.html

DSN, Data Source Name :
https://fr.wikipedia.org/wiki/Data_Source_Name

Les “webhooks” ou liens de rappel HTTP :
https://fr.wikipedia.org/wiki/Webhook

Documentation Brevo sur la fonctionnalité de webhook :
https://developers.brevo.com/docs/how-to-use-webhooks

Création d’un ParamConverter personnalisé :
https://symfony.com/bundles/SensioFrameworkExtraBundle/current/annotations/converters.html#creating-a-converter

Les règles de contrôle d’accès de la sécurité Symfony :
https://symfony.com/doc/current/security/access_control.html

Photographie : Brett Jordan via Unsplash

Remerciements

Merci à Yoan Bernabeu d’avoir parlé de cet article dans la vidéo :

et sur le site : https://www.hebdoo.fr/

Merci à Javier Eguiluz d’avoir mentionné cet article sur le blog de Symfony :
https://symfony.com/blog/a-week-of-symfony-794-14-20-march-2022

--

--

Fabien Lemoine
Fabien Lemoine

Written by Fabien Lemoine

Développeur PHP Symfony indépendant

Responses (1)