Comment envoyer des emails responsive avec Quarkus et MJML

Régulièrement, lorsque nous développons des applications Java nous avons besoin d’envoyer des mails « responsive » qui sont bien pris en compte par les différents clients de mails. Vous me direz que nous avons la possibilité de le faire via l’api JavaMail mais personnellement je ne trouve pas « user friendly » la façon de l’implémenter, de plus cette api ne fournit pas de moteur de templates et encore moins une façon d’écrire simplement et rapidement des templates HTML responsive qui s’affichent correctement dans toutes les mailboxs. Certains me diront que c’est pas nouveau, effectivement envoyer des mails responsive avec une application Java est tout de même un classique, des frameworks et librairies existent déjà. Oui !

Dans ce billet j’aimerai modestement faire un retour d’expérience sur des outils qui permettent de répondre aux besoins simplement et rapidement. Nous allons donc voir comment utiliser Quarkus ainsi que le moteur de templating que fournit l’extension quarkus-qute, le tout enrobé de MJML pour écrire des templates html responsive. Cet article s’appuie sur un retour d’expérience qui, je l’espère, vous montrera avec quelle simplicité nous arrivons au résultat escompté. Ce billet est accompagné d’une petite application de démonstration disponible ici.

Commençons par le commencement :).

Création de l’application Quarkus

Pour ce faire j’ai choisi d’utiliser l’interface web de génération d’une application Quarkus.

Comme vous pouvez le constater ci-dessus, je sélectionne donc les extensions Quarkus que je veux utiliser à savoir :

Par la suite je crée donc directement mon projet sur Github, je clone le projet sur mon poste en local et je lance mon IDE favori.

Comme vous pouvez le voir ci-dessus j’ai directement lancé mon application à l’aide de la commande

Résultat, mon application est fonctionnelle et je peux la valider très facilement avec swagger

A cela je rajouterai dans mon application Lombok pour gérer les Getter/Setter et le constructeur de mes beans et aussi injecter facilement un logger.

OK, nous avons notre application fonctionnelle, il est temps de rentrer dans le vif du sujet.

Quarkus mailer

La première étape va consister à configurer notre mailer quarkus en ajoutant ces propriétés au fichier application.properties

Je vais par la suite créer un fichier docker-compose.yml afin de pouvoir lancer un mail box local de test pour recevoir les mails. Je vais m’appuyer sur MailHog.

Le server smtp de MailHog écoutera sur le port 1025 et l’interface web de consultation des mails sera disponible à l’adresse http://localhost:8025

L’initialisation est en place, nous sommes outillés pour commencer à coder.

Commençons par créer un endpoint REST MailResource qui nous permettra de faire des appels à nos services de mail très facilement.

Comme vous pouvez le voir ci-dessus, je démarre très simplement en injectant le io.quarkus.mailer.Mailer dans une Resource Jax-RS afin de pouvoir tester immédiatement, nous structurerons plus tard le code si le test est concluant (à noter que dans cet exemple j’utilise le Mailer synchrone mais il existe aussi un ReactiveMailer, asynchrone, qui s’utilise de la même manière).

Envoyons donc un mail en texte brut, pour se faire j’utilise l’objet io.quarkus.mailer.Mail sur le quel j’appel la méthode withText(…). Celle-ci a trois paramètres en entrée, le destinataire, le sujet et le corps du mail. A noter que je peux passer autant d’objet Mail que je veux, le mailer est vraiment celui qui envoie et on peut lui faire envoyer plusieurs mails à plusieurs destinataire sur plusieurs sujets et contenus.

Retournons sur swagger et lançons un appel à http://localhost:8080/mail (vous pouvez taper l’url directement dans votre navigateur) puis allons voir ce qu’il en est du côté de MailHog

Super ! Mon mail est bien arrivé dans ma mail box. Ouvrons le pour voir le détail

Tout est correct, cela fonctionne comme attendu.

Comme vous pouvez le constater nous pouvons aussi envoyer un mail au format html. Nous le verrons plus tard avec l’utilisation de template html.

Qute

Qute est un moteur de templating spécialement conçu pour s’associer avec Quarkus. Cela va donc nous permettre de générer des mails au format html (ou txt) en injectant des données de façon dynamique selon les besoins.

Templates

La façon la plus simple de déclarer un template au sein de votre application est de créer un fichier de template dans le répertoire /src/main/resources/templates (c’est le répertoire sur lequel Qute s’appuie, par la suite on verra qu’on peut créer des sous dossier), par exemple créons un template hello.txt

et vous pouvez voir ci-dessous l’implémentation Java pour générer une instance de template à partir de ce dernier

comme vous pouvez le constater dans le code ci-dessus pour instancier notre template, il suffit simplement de l’injecter comme vous pouvez le voir à la ligne 3 et 4, le type est io.quarkus.qute.Template, et le nom du template est le nom du fichier qui est positionné dans le répertoire de templates par défaut sans l’extension .txt.

Qute nous donne aussi la possibilité de spécifier un autre dossier que celui par défaut grâce à l’annotation @Location si on veut structurer un peu l’organisation de nos templates et le nom des variable Java, comme suit

Cette fois-ci je peux donner le nom que je veux à ma variable Template (myTemplate) puisque dans @Location j’écris en préfixe le sous répertoire dans le quel mon Template se trouve (/src/main/resources/templates/my_sub_directory) et en suffixe le nom du fichier sans l’extension (pas obligatoire, on y revient dans la suite de l’article). Dans cet exemple c’est hello. Ainsi le template utilisé sera /src/main/resources/templates/my_directory/hello.txt

Note : si je définis plusieurs templates dans plusieurs format (txt, html…) dans le même répertoire sans spécifier l’extension dans @Location, c’est le premier fichier lu qui sera utilisé. Si j’ai hello.txt et hello.html, hello.html sera lu en premier (ordre alphabétique) et donc utilisé. Pour choisir vous devez le spécifier de la façon suivante @Location(« my_directory/hello.txt »)

A présent nous pouvons générer facilement des instances de templates. Seulement, si je fais une erreur dans l’injection de données dans mon Template, je constaterai l’erreur uniquement au runtime. Reprenons l’exemple avec notre hello.txt ou. je modifie le nom de la donnée à injecter, je remplace name par myName

Le code Java ne change pas et cherche toujours à injecter une valeur dans la variable « name » comme ceci myTemplate.data(« name », « NAME »), alors que dans le template le nom de la propriété est « myName », cela ne match évidemment plus et l’erreur ne sera remontée/constaté que au moment de l’exécution du code

La troisième ligne du log identifie clairement le problème.

Type-safe templates

Pour palier ce problème, c’est à dire constater trop tard dans la chaine de développement qu’on a fait une erreur dans un template, Qute offre aussi la possibilité de créer des templates avec validation des expressions. C’est à dire que nous allons injecter un objet dans notre template, qui contient les données à positionner dans notre fichier généré à partir du template, et au Build si une propriété n’est pas correcte ou inexistante, Quarkus va lever une exception. Voici l’erreur qui va être levée : j’ai positionné un objet context avec une faute de frappe sur la propriété logoEmailHeader (logoEmailHeaderrrr) et l’erreur est levée dès le build comme vous pouvez le constater avec la trace suivante

Qute vient avec la convention suivante pour la gestion des dit Type-safe templates

  • Vos templates seront écrit dans le répertoire /src/main/resources/templates, en les regroupant dans un répertoire par classe. Ainsi, si votre classe EmailTemplates fait référence à des templates, vous aurez à les écrire dans /src/main/resources/templates/EmailTemplates. Le regroupement de modèles par classe facilite l’organisation et la navigation vers ceux-ci.
  • Dans chacune de vos classes, déclarez une classe @CheckedTemplate static class Templates {}.
  • Déclarez une méthode public static native TemplateInstance method(); pour un template classique ou bien public static native MailTemplate.MailTemplateInstance method(); afin de pouvoir envoyer un mail de façon asynchrone directement sur une instance de templates. Une méthode correspond à un template.
  • Ces méthodes statiques vont donc vous permettre de créer un instance de Template ou une instance de MailTemplate selon vos choix/besoins.

Voici l’exemple de notre définition de template dans le projet Github que j’ai initié afin d’avoir un exemple concret pour imager la convention précédente

Comme vous pouvez le constater, j’ai donc implémenté une classe EmailTemplates dans laquelle je centralise la définition de mes différents templates et des objets que je voudrais injecter dans mes instances de templates (ici c’est un bean EmailNotification qui est passé en paramètre de la méthode statique). Mes templates html ou txt vont donc être écrit dans le répertoire /src/main/resources/templates/EmailTemplates

A présent nous pouvons envoyer des mails en nous appuyant sur un système de templating donc nous pouvons commencer à structurer notre code. Commençons par implémenter notre classe de service qui sera en charge d’envoyer des mails à partir d’un template Qute. Nous avons deux possibilités pour le faire : s’appuyer sur le Mailer + TemplateInstance ou s’appuyer uniquement sur le MailTemplateInstance.

TemplateInstance

Ci-dessous vous pouvez lire le code qui a été implémenté dans notre Classe de MailService. Cette classe met à disposition une première méthode d’envoie de mail en s’appuyant sur la classe TemplateInstance fournit par Qute.

Le cas le plus intuitif est d’utiliser le Mailer fournit par Quarkus et de positionner dans le corps du message une instance de Template Qute. Nous injectons donc une instance de Mailer dans notre classe de service et nous implémentons la méthode sendTypeSafeTemplate(…) qui prend en paramètre

  • from : l’adresse de celui qui envoie le mail
  • toEmails : la liste des destinataires
  • subject : le sujet du mail
  • ccEmails : les copies cachées
  • template : l’instance de template Qute, donc le corps du mail en html (ou en txt)

Comme nous l’avons vu précédemment nous avons dans cette méthode juste besoin d’appeler la méthode send() pour envoyer le mail, dans laquelle on appelle une méthode statique (Mail.withHtml ou Mail.withText) qui va prendre en paramètre le to, le subject et enfin le corps du message simplement en appelant la méthode render() sur l’objet TemplateInstance. Cet objet Mail peut aussi se voir injecter d’autres informations grâce au setter mis à disposition par exemple

  • Mail(…).setFrom(…) : donne la possibilité de ne pas utiliser le from défini dans le application.properties
  • Mail(…).setTo(…) : surcharge le to du mail et donne la possibilité d’envoyer à une liste de destinataires
  • Mail(…).setAttachements(…) : ajouter des pièces jointes

A présent, nous n’avons plus qu’à appeler cette méthode depuis une autre classe. Dans mon exemple sur Github je l’appel depuis une Resource REST afin de pouvoir lancer facilement l’appel via swagger.

J’injecte mon MailService dans ma resource et j’appel la méthode sendTypeSafeTemplate avec les bons paramètres dont l’instanciation du Template grâce à la méthode statique que nous avons défini précédemment à savoir

Cette méthode va donc prendre en paramètre l’objet contenant les données que je veux injecter dans mon template (EmailNotification) et me retourner l’instance TemplateInstance.

Nous pouvons à présent tester, lançons un « ./mvnw compile quarkus:dev » sur le projet Github, appelons l’url via swagger, ou un navigateur, http://localhost:8080/mail/template et regardons ce qui arrive dans notre MailHog

Nous avons bien envoyé un mail au format html avec les bonnes données injectées dans celui-ci. A présent voyons comment simplifier encore légèrement le code.

MailTemplateInstance

Dans l’exemple précédent j’envoie donc un mail à l’aide du mailer Quarkus et de Template, mais Qute introduit aussi le MailTemplateInstance, cela permet de ne pas avoir à « manipuler le mailer » (même si c’est lui qui est utilisé en dessous) et cela permet aussi de faire de l’envoie de mail asynchrone. Tout ça de manière complètement transparente ! Comme vous pouvez le voir ci-dessous la seul chose que je vais devoir manipuler sera le MailTemplate.MailTemplateInstance. Cet objet porte à la fois l’instance de template et le mailer. Voici comment l’utiliser :

Cette fois la méthode send() de mon template retourne un Uni, il faut donc appeler subscribe pour souscrire à cette Uni et déclencher l’appel, sinon rien ne se passera.

Vérifions à présent que le mail est bien envoyé et contient les bonnes données, démarrons notre application et allons appeler l’url http://localhost:8080/mail/mail-template et regardons dans notre MailHog

A présent nous savons envoyer des emails avec Quarkus et créer des instances de templates en nous appuyant sur Qute. Cependant, Il reste un point à évoquer afin d’envoyer des mails au format html de façon « professionnelle », c’est comment générer facilement des templates html responsive.

MJML

Ecrire de l’html à la main peut s’avérer laborieux (adaptation aux différents clients mails, gestion du responsive etc…) et c’est là que MJML rentre en compte : Il va vous permettre d’écrire du code html compatible avec la majorité des clients web et responsive avec du code « html » simplifié et un outil de wysiwig vous permettant de visualiser ce que vous écrivez. MJML va vous donner la possibilité de générer de l’html à partir de code moins verbeux et d’une syntaxe plutôt simple.

Je ne ferai pas un cours magistral sur MJML, je vais juste introduire le principal pour écrire du MJML et surtout montrer comment on l’intègre dans ce projet Java.

Voici à quoi va ressembler notre fichier MJML qui sera ensuite converti en template Qute html. A droite vous pouvez voir le plugin MJML dans IntelliJ. Vous voyez ce que vous codez en temps réel et comme vous pouvez le constater c’est plus succinct que du HTML.

Voici les principales balises MJML utilisées

  • <mjml> : qui correspond à la balise <html>
  • <mj-head> : qui correspond à la balise <head>
  • <mj-body> : qui correspond à la balise <body>
  • <mj-section> : qui correspond à des portions de <body> c’est à dire des balises <div>
  • <mj-include> : cela permet de faire des imports d’autres fichiers mjml (dans le code exemple, on importe un header et un footer)
  • <mj-text> : qui correspond à la balise <div>
  • plus d’explications et de balises voir https://documentation.mjml.io/#overview

Vous pouvez faire un essai directement en ligne en allant sur https://mjml.io/try-it-live, et si vous voulez récupérer de jolis templates, il y en a des disponible à cette adresse https://mjml.io/templates.

Nous avons vu à quoi ressemble la syntaxe du MJML, et comment visualiser facilement ce qu’on écrit, à présent il faut pouvoir générer notre futur template Qute html à partir de celui-ci.

Et bien pour cela il suffit d’utiliser une librairie javascript qui se nomme simplement MJML. Mais comment cela peut s’intégrer dans notre projet Quarkus ?

Simplement : nous avons créé un répertoire de travail MJML dans notre dossier /src/main, dans lequel vont se trouver bien évidemment les fichiers MJML mais aussi un script (en fait 2, un .bat et un .sh) qui nous permettra de générer les fichiers templates html à partir des fichiers MJML.

Comme vous pouvez le voir dans la copie d’écran ci-dessus, notre script va commencer par installer MJML à l’aide de npm (il faut au préalable installer nodejs) puis nous n’avons plus qu’à lancer la commande MJML avec en paramètre d’entrée le fichier MJML et en paramètre de sortie, le répertoire et le nom du fichier html que nous voulons générer.

Pour s’inscrire dans la logique de notre projet qui repose sur le templating Qute, nous allons donc écrire nos fichiers générés dans le répertoire Qute, à savoir /src/main/resources/templates/EmailTemplates.

ci-dessous vous pouvez voir la comparaison entre le fichier MJML de départ et le fichier html d’arrivée.

Pour gérer les variables en entrée qui seront injectées par le moteur de template Qute il suffit juste d’écrire « {ma_variable} » dans le fichier MJML et cela sera retranscrit tel quel dans le fichier html de destination.

And voilà ! Dans cet article nous aurons vu comment associer Quarkus, Qute et MJML pour produire des emails de qualités, simplement et rapidement.

Conclusion

Tout n’est pas détaillé dans ce billet, mais cela vous donne une base de travail grâce au code sur Github et aux explications de cet article. Pour aller plus loin dans le détail d’utilisation de ces outils voici les liens vers les documentations de référence qui m’ont aidé à écrire ce billet.

Références

You may also like...