Envoyer des emails HTML avec Mailgun-Go
Dans ce tutoriel, je vais vous montrer comment envoyer des emails HTML avec des images intégrées grâce à mailgun-go. Avant de nous plonger dans le code, définissons d’abord le problème et comment utiliser Mailgun pour améliorer l’expérience utilisateur de notre application.
Présentation de Channel-stats
Channel-stats est un bot Slack qui permet de collecter des statistiques sur les messages envoyés sur un canal Slack. En plus de comptabiliser les emojis et les liens partagés dans le canal, il effectue également une analyse de sentiment des messages et attribue un score positif ou négatif. Ce score peut ensuite être examiné par les utilisateurs et représenté sous forme de pourcentage du total des messages.

Nous voulons développer cette fonctionnalité en envoyant un rapport email hebdomadaire sur les statistiques d’un canal à nos utilisateurs. Étant donné que notre email inclura des données sous forme de graphique, les emails en texte brut seraient plutôt ennuyeux. Nous voulons plutôt envoyer un email HTML enrichi de graphiques dans la boîte de réception de nos utilisateurs. Pour y parvenir, nous devons créer du code HTML, avec du CSS en ligne et des images pour le rendre visuellement attrayant.
email HTML
Beaucoup de choses ont été écrites sur l’envoi de HTML dans les emails, mais voici quelques bonnes règles à suivre :
- À FAIRE : utiliser du CSS en ligne
- À FAIRE : utiliser des TABLEAUX HTML pour la mise en page
- À FAIRE : utiliser des images (de préférence au format .png)
- À FAIRE : intégrer les images en ligne
- À NE PAS FAIRE : utiliser le HTML5
- À NE PAS FAIRE : utiliser les animations CSS
- À NE PAS FAIRE : créer de lien vers une feuille de style externe
- À NE PAS FAIRE : utiliser des styles CSS dans le HEAD
- À NE PAS FAIRE : utiliser du javascript
- À NE PAS FAIRE : utiliser du flash
Il ne suffit souvent pas de suivre les règles ci-dessus, car il n’existe pas de normes établies sur la façon dont le HTML dans les emails est rendu. Si vous tenez à ce que vos emails s’affichent correctement sur autant de clients que possible, vous pourriez envisager d’utiliser un service tel que Litmus pour créer, afficher la prévisualisation et tester votre email sur divers clients de messagerie. Cependant, pour notre objectif et puisque channel-stats est un projet open source, je maintiens les coûts de production à un niveau bas et j’utilise des modèles gratuits fournis par Mailgun (J’ai reçu de l’aide de l’un de nos designers UX/UI). Voici le résultat obtenu :

Maintenant que nous avons notre HTML et notre CSS, nous devons mettre le CSS en ligne afin que la majorité des clients de messagerie affichent correctement notre email. Il existe une multitude d’outils en ligne pour y parvenir, mais nous recommandons Dialect Premailer à cet effet.
Le code
Puisque nous voulons que l’email soit envoyé de façon hebdomadaire, nous utilisons une bibliothèque cron pour créer une fonction qui s’exécutera tous les dimanches soirs à minuit. Ensuite, nous devons générer les images qui apparaîtront dans notre email. Channel-stats utilise déjà go-chart pour rendre les images de graphiques .png pour l’interface utilisateur, nous pouvons donc simplement l’adapter à nos besoins.
De plus, lors de la livraison de notre projet final, nous ne voulons pas distribuer les fichiers HTML et CSS séparément de notre binaire golang final compilé. C’est pourquoi channel-stats utilise le projet go-bindata pour regrouper le HTML et le CSS dans un seul binaire channel-stats.
Examinons maintenant le code de rendu.
func NewReporter(conf Config, list ChanLister, notify Mailer, store Storer) (Reporter, error) {rn r := Report{rn log: GetLogger().WithField("prefix", "reporter"),rn cron: cron.New(),rn mail: notify,rn store: store,rn conf: conf,rn list: list,rn}rn return &r, r.start()rn}rnrnfunc (r *Report) start() error {rn err := r.cron.AddFunc(r.conf.Report.Schedule, func() {rn timeRange := toTimeRange(r.conf.Report.ReportDuration.Duration)rn r.log.Debugf("Creating report for %s to %s", timeRange.Start, timeRange.End)rnrn for _, channel := range r.list.Channels() {rn // Skip channels the bot is not inrn if !channel.IsMember {rn continuern }rnrn html, err := r.genHtml("html/templates/email.tmpl", channel.Name)rn if err != nil {rn r.log.Errorf("during email generate: %s", err)rn returnrn }rnrn data := ReportData{rn Images: make(map[string][]byte),rn Html: html,rn }rnrn // Generate the images for the reportrn data.Images["most-active.png"] = r.genImage(RenderSum, timeRange, channel.Id, "messages")rn data.Images["top-links.png"] = r.genImage(RenderSum, timeRange, channel.Id, "link")rn data.Images["top-emoji.png"] = r.genImage(RenderSum, timeRange, channel.Id, "emoji")rn data.Images["most-negative.png"] = r.genImage(RenderPercentage, timeRange, channel.Id, "negative")rn data.Images["most-positive.png"] = r.genImage(RenderPercentage, timeRange, channel.Id, "positive")rnrn // Email the reportrn if err := r.mail.Report(channel.Name, data); err != nil {rn r.log.Errorf("while sending report: %s", err)rn }rn }rn })rn if err != nil {rn return errrn }rnrn r.cron.Start()rn return nilrn}
Dans la méthode Start(), nous parcourons tous les canaux dont le bot est membre et générons un rapport pour chacun d’eux. Nous appelons ensuite genHtml(), qui récupère notre email HTML sous forme de modèle appelé templates/email.tmpl depuis notre magasin d’assets compilé dans le package HTML. Nous exécutons ensuite le modèle via le moteur standard de golang HTML/template pour produire le HTML final. Ensuite, l’appel à genImage() déclenche la fonction de rendu avec la plage d’heures et le type de compteur que nous souhaitons récupérer du magasin de données. Une fois que ReportData est terminé, nous transmettons les données à mail.Report() pour la livraison.
Maintenant que nous avons nos images et notre HTML, faisons une pause pour parler un peu du format MIME HTML et de l’encodage des images. MIME est le format dans lequel le corps des emails est encodé lors de l’envoi via le protocole SMTP. C’est le format qui permet aux clients de messagerie d’encoder le HTML, de joindre et de récupérer des fichiers et des images dans un email.
Pour que nos images s’affichent correctement en HTML, nous devons les encoder dans le MIME. Pour cela, nous avons deux options : nous pourrions ajouter les images en tant que pièce jointe, ou nous pourrions les intégrer en ligne. La RFC sur la disposition du contenu (Content-Disposition) indique que inline signifie que l’entité doit s’afficher immédiatement pour l’utilisateur, tandis que attachment signifie que l’utilisateur doit effectuer une action supplémentaire pour consulter l’entité. Puisque nos images doivent s’afficher immédiatement pour l’utilisateur via le HTML, nous choisissons l’option en ligne (inline).
À ce stade, nous pourrions utiliser n’importe quelle bibliothèque MIME pour golang afin d’intégrer nos images en ligne et générer le corps de l’email au format MIME, mais avec Mailgun, ce n’est pas nécessaire. Mailgun générera le MIME pour nous et fournit des options pour intégrer des fichiers en ligne et des images via l’API publique.
Maintenant que nous savons comment intégrer des images en ligne dans le MIME, nous devons les référencer depuis notre HTML. Pour ce faire, nous utilisons le préfixe cid: dans nos balises . De telle sorte que si notre image en ligne s’appelle most-active.png, notre balise d’image serait
Notre HTML étant prêt, voyons comment envoyer l’email et les images avec mailgun-go.
func NewMailgunNotifier(conf Config) (Mailer, error) {rn return &Mailgun{rn mg: mailgun.NewMailgun(conf.Mailgun.Domain, conf.Mailgun.APIKey),rn log: GetLogger().WithField("prefix", "mailer"),rn conf: conf,rn }, nilrn}rnrn// Send a report to the designated email address (could be mailing list)rnfunc (m *Mailgun) Report(channelName string, data ReportData) error {rn if m.conf.Mailgun.ReportAddr == "" {rn m.log.Errorf("mailgun.enabled = true; however mailgun.report-address is empty; skipping..")rn return nilrn }rnrn // Create a subject for the reportrn subject := fmt.Sprintf("[channel-stats] Report for %s", channelName)rn // Create a message with no text bodyrn message := m.mg.NewMessage(m.conf.Mailgun.From, subject, "", m.conf.Mailgun.ReportAddr)rn // Send the HTML to mailgun for MIME encodingrn message.SetHtml(string(data.Html))rnrn for file, contents := range data.Images {rn message.AddReaderInline(file, ioutil.NopCloser(bytes.NewBuffer(contents)))rn }rnrn ctx, cancel := context.WithTimeout(context.Background(), m.conf.Mailgun.Timeout.Duration)rn defer cancel()rnrn _, id, err := m.mg.Send(ctx, message)rn if err != nil {rn return errrn }rn m.log.Infof("Sent report via mailgun (%s)", id)rn return nilrn}
Tout d’abord, nous créons une nouvelle instance de Mailgun en utilisant notre nom de domaine et notre clé API dans NewMailgunNotifier(). Ensuite, dans la méthode Report(), nous appelons NewMessage() pour créer un objet auquel nous ajouterons notre HTML et nos images. Remarquez que l’argument text de NewMessage() est une chaîne vide. Bien qu’il soit possible d’encoder à la fois du texte brut et du HTML dans le message MIME, nous ne fournissons que du HTML ici, car les images de graphiques en ligne seraient inutiles pour un client de messagerie uniquement textuel. Ensuite, nous appelons SetHtml() et ajoutons nos images en ligne via un objet read closer que nous créons à la volée à partir de notre tampon []byte buffer. Enfin, nous envoyons notre requête préparée à l’API de Mailgun pour la construction du MIME et la livraison en utilisant la méthode Send().
Conclusion
Nous espérons que ce tutoriel vous a donné un aperçu de la manière d’envoyer des emails HTML de haute qualité à l’aide de mailgun-go et de l’API de Mailgun. Si vous avez des commentaires ou trouvez des bugs dans l’un ou l’autre des projets, le code complet et la bibliothèque se trouvent ci-dessous.
Envie de travailler chez Mailgun ? Nous recrutons ! Et plusieurs postes de développement sont à pourvoir. Découvrez nos offres d’emploi actuelles ici.
Webinar à la une – Prédictions et résolutions : les envois en 2019
Vous avez raté ce webinar début 2019 ? Pas d’inquiétude, nous l’avons enregistré ! Revoyez Nick et Natalie discuter des événements survenus dans le domaine de l’email en 2018, et de ce qu’ils anticipaient pour 2019. Les équipes technique et marketing se sont retrouvées sur ce point, et vous pouvez le revoir ici.

