Nachverfolgen von geöffneten E-Mails und geklickten Links in E-Mails mit Java und Twilio SendGrid

April 23, 2020
Autor:in:

Nachverfolgen von geöffneten E-Mails und geklickten Links in E-Mails mit Java und Twilio SendGrid


Hallo und Danke fürs Lesen! Dieser Blogpost ist eine Übersetzung von Open and Click Tracking for Email using Java and Twilio SendGrid. Während wir unsere Übersetzungsprozesse verbessern, würden wir uns über Dein Feedback an help@twilio.com freuen, solltest Du etwas bemerken, was falsch übersetzt wurde. Wir bedanken uns für hilfreiche Beiträge mit Twilio Swag :)

Ich habe bereits in einem älteren Blog das Senden von E-Mails in Java mit der Twilio SendGrid-API behandelt, aber bei wichtigen E-Mails kann es erforderlich sein zu wissen, ob Empfänger die E-Mail geöffnet und auf die darin enthaltenen Links geklickt haben. Wir können das mit dem Event-WebHook in SendGrid veranlassen. Dabei werden HTTP-Anfragen an eine bereitgestellte URL gesendet. In diesem Blog zeige ich, wie die entsprechende Konfiguration mit Twilio SendGrid aussieht und wie wir eine Java-Webanwendung erstellen, die die WebHooks verarbeitet und die Aktivität der Empfänger aufzeichnet.

Vorbereitung

Wir benötigen Folgendes:

  • Java Version 8 oder höher. Ich verwende gern SDKMAN! zum Verwalten von Java-Installationen.
  • ngrok
  • Ein Twilio SendGrid-Konto. Melde dich hier an, falls du noch keines hast.

Erstellen der Anwendung

Wenn du die Codierung überspringen und stattdessen lieber gleich mit dem Testen loslegen möchtest, findest du den Code für diese Anwendung auf GitHub. Gehe in diesem Fall direkt zum Abschnitt weiter unten mit dem Titel Erstellen einer öffentlichen URL für eine Anwendung mit ngrok.

Die Anwendung erstellen wir mit Spring Boot und annotierten Spring MVC-Controllern. Wenn Twilio SendGrid-Ereignisse von der Anwendung empfangen werden, speichert sie open- und click-Ereignisse in einer Reihe von Map-Objekten, um aufzuzeichnen, welche Adressen eine bestimmte E-Mail geöffnet oder auf einen bestimmten Link geklickt haben. Außerdem gibt es ein paar Endpunkte, damit wir die Daten abrufen können, um sie z. B. in einem Dashboard zu verwenden.

Erstellen der Anwendungsvorlage

Wir können SDKMAN! zum Installieren des Spring Boot-CLI-Tools mit dem Befehl sdk install springboot verwenden. Mit dem folgenden Befehl erstellen wir ein neues Projekt in einem leeren Verzeichnis:

spring init \
  --dependencies web \
  --build maven \
  --groupId lol.gilliard \
  --artifactId sendgrid-event-hooks \
  --extract

Dieser Befehl richtet ein neues Spring Boot-Projekt in unserem aktuellen Verzeichnis ein. Wir können unsere eigene groupId und artifactId mithilfe der Maven-Namenskonventionen auswählen. Wenn wir das CLI-Tool nicht verwenden möchten, können wir dieselben Informationen unter start.spring.io eingeben und dann das Projekt, das dabei erstellt wird, herunterladen und entpacken.

Jetzt können wir das Projekt in unsere IDE importieren und mit der Codierung beginnen. Ich verwende hauptsächlich IntelliJ IDEA, aber Eclipse und NetBeans sind auch sehr beliebte IDEs.

Hinzufügen des Ereigniscontrollers

Wir erstellen in demselben Paket, in dem sich die Application-Klasse befindet und das von Spring Initializr erstellt wurde, eine Klasse mit dem Namen SendGridEvent. Darin wird eine Teilmenge der JSON-Daten gespeichert, die von SendGrid gepostet werden. Spring kümmert sich um das Umwandeln der JSON-Daten in Instanzen dieser Klasse.

Diese Klasse enthält einige final-Felder, deshalb benötigt sie einen Konstruktor. Aber wir kommen auch ohne Getter und Setter zurecht:

import com.fasterxml.jackson.annotation.JsonProperty;

public class SendGridEvent {

   public final String email;
   public final String eventType;
   public final String url;
   public final String sgMessageId;

   public SendGridEvent(@JsonProperty("email") String email,
                        @JsonProperty("event") String eventType,
                        @JsonProperty("url") String url,
                        @JsonProperty("sg_message_id") String sgMessageId) {
       this.email = email;
       this.eventType = eventType;
       this.url = url;
       this.sgMessageId = sgMessageId;
   }
}

Aufgrund der @JsonProperty-Annotationen können die Felder in dieser Klasse anders als die Felder in den JSON-Daten benannt werden.

Wir erstellen in demselben Paket eine Klasse mit dem Namen SendGridEventWebhookHandler, die mit @Controller annotiert wird. Darin enthalten sind Methoden zum Empfangen von Ereignissen durch POST-Anfragen und zum Bereitstellen von Daten durch GET-Anfragen:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

@Controller
public class SendGridEventWebhookHandler {

   private static final Logger logger = Logger.getLogger(SendGridEventWebhookHandler.class.getName());

   private final Map<String, Set<String>> openedEmails = new ConcurrentHashMap<>();
   private final Map<String, Set<String>> clickedLinks = new ConcurrentHashMap<>();

   @PostMapping("/events")
   @ResponseBody
   public String receiveSGEventHook(@RequestBody List<SendGridEvent> events) {

       logger.info(String.format("Received %d events", events.size()));

       events.forEach(event -> {

           switch (event.eventType) {
               case "open":
                   openedEmails.computeIfAbsent(event.sgMessageId, k -> new HashSet<>()).add(event.email);
                   break;

               case "click":
                   clickedLinks.computeIfAbsent(event.url, k -> new HashSet<>()).add(event.email);
                   break;
           }
       });

       return "ok";
   }


   @GetMapping("/opened")
   @ResponseBody
   public Map<String, Set<String>> getOpenedEmailData(){
       return openedEmails;
   }

   @GetMapping("/clicked")
   @ResponseBody
   public Map<String, Set<String>> getClickedLinksData(){
       return clickedLinks;
   }

}

Die receiveSGEventHook-Methode (hervorgehoben) benötigt einen List<SendGridEvent>-Parameter, der aus den von SendGrid gesendeten JSON-Daten erstellt wird. Dabei handelt es sich um eine List, da SendGrid Ereignisse in Batches zusammenfassen kann, wenn viele innerhalb kurzer Zeit eingehen.

openedEmail ist eine Map, die sg_message_id einem Set von E-Mail-Adressen zuordnet, die die E-Mail mit dieser ID geöffnet haben. Wir finden diese ID über eine „erweiterte Suche“ in unserem Aktivitätsfeed und durch Filtern nach der Nachrichten-ID. Die Nachrichten-ID kann für jeden Empfänger anders aussehen. Wenn wir unser eigenes Kategorisierungssystem für E-Mails verwenden möchten, dann nutzen wir die Funktion Eindeutige Argumente, die speziell darauf ausgelegt ist.

clickedLinks hat eine ähnliche Struktur und ordnet URLs einem Set von E-Mail-Adressen zu, deren Besitzer auf den Link geklickt haben. In beiden Fällen habe ich ConcurrentHashMap und computeIfAbsent verwendet, um Thread-Sicherheitsprobleme zu vermeiden, wenn mehrere Sets von Ereignissen gleichzeitig empfangen werden.

Wenn Ereignisse empfangen werden, durchläuft der Code diese mit forEach und fügt open- oder click-Ereignisse der entsprechenden Map hinzu. Es gibt zahlreiche weitere Ereignistypen, z. B. bounce und unsubscribe, die von dieser Anwendung ignoriert werden. Im weiteren Verlauf zeige ich, wie wir diese von SendGrid gesendeten Ereignistypen konfigurieren.

Zu guter Letzt benötigen wir noch ein paar Methoden, um die Daten von den openedEmails- und clickedLinks-Zuordnungen verfügbar zu machen. Ich habe jeder Zuordnung einen Endpunkt hinzugefügt:

@GetMapping("/opened")
@ResponseBody
public Map<String, Set<String>> getOpenedEmailData(){
   return openedEmails;
}

@GetMapping("/clicked")
@ResponseBody
public Map<String, Set<String>> getClickedLinksData(){
   return clickedLinks;
}

Das ist der vollständige Code, der für die Anwendung erforderlich ist. Wir führen ihn über das Terminal mit dem Befehl ./mvnw spring-boot:run im Stammverzeichnis des Projekts aus.

Erstellen einer öffentlichen URL für eine Anwendung mit ngrok

Bisher läuft die Anwendung auf dem localhost und hört dort HTTP-Anfragen ab. SendGrid benötigt allerdings eine öffentliche URL für den WebHook. Wir könnten diese Anwendung auf einem Cloudserver bereitstellen, aber um es hier einfach zu halten, empfehle ich, ngrok zu verwenden. Damit lassen sich öffentliche URLs erstellen, die über einen Tunnel mit unserem localhost-Server verbunden sind.

Nach der Installation von ngrok erstellen wir einen Tunnel mit dem Befehl ngrok http 8080:

Screenshot der ngrok-Ausgabe, die die HTTPS-Weiterleitungs-URL zeigt

Wir notieren uns die https-Weiterleitungs-URL, da sie für die Konfiguration von SendGrid erforderlich ist. Auf dem Screenshot oben sehen wir, dass meine WebHook-URL https://3b7e5043.eu.ngrok.io/events lautet. Deine ngrok-Domäne wird anders aussehen, aber achte unbedingt darauf, /events am Ende hinzuzufügen.

Aktivieren der Event-WebHooks und Festlegen der WebHook-URL

Nachdem wir unser SendGrid-Konto erstellt haben, müssen wir drei Aufgaben erledigen:

  • Hinzufügen von Open-Tracking und Click-Tracking zu ausgehenden E-Mails
  • Konfigurieren von SendGrid mit der ngrok-URL für WebHooks
  • Konfigurieren von SendGrid zum Senden von WebHook-Anfragen für open- und click-Ereignisse

Hinzufügen von Open-Tracking und Click-Tracking zu ausgehenden E-Mails

Im SendGrid-Dashboard aktivieren wir im Abschnitt Tracking Settings die Optionen „Open Tracking“ und „Click Tracking“.

Screenshot von „Tracking Settings“. Open Tracking und Click Tracking sind beide aktiviert.

Open-Tracking konfiguriert SendGrid so, dass jeder ausgehenden E-Mail ein unsichtbares Bild hinzugefügt wird. Anhand dieses Bilds lässt sich feststellen, ob der Empfänger die E-Mail geöffnet hat. Click-Tracking konfiguriert SendGrid so, dass es die URLs in den Nachrichten umschreibt, um nachzuverfolgen, welche Empfänger auf welche URLs geklickt haben. Lies dir am besten diese Best Practices zum Erstellen von Links durch, um sicherzustellen, dass sie auch nachverfolgt werden.

Konfigurieren von SendGrid zum Verwenden der Anwendung für WebHooks

Im SendGrid-Dashboard auf der Seite Mail Settings aktivieren wir Event-WebHooks. Dann konfigurieren wir die HTTP-Post-URL so, dass sie unserer ngrok-URL entspricht, und fügen /events an das Ende hinzu:

Event-Webhook-Konfiguration. Festlegen der HTTP-Post-URL

Konfigurieren von SendGrid zum Senden von Open- und Click-Ereignissen

Während wir uns auf der Konfigurationsseite für Event-WebHooks befinden, aktivieren wir unter Engagement Data die Kontrollkästchen für „Opened“ und „Clicked“.

Event-Webhook-Konfiguration. Auswahl von „Opened- und „Clicked“-Ereignissen, die gepostet werden sollen.

Zum Schluss aktivieren wir den Event-WebHook und speichern die Konfiguration.

Event-Webhook-Konfiguration. Aktivieren und Speichern der Webhook-Konfiguration

Testen der Anwendung im SendGrid-Dashboard

Auf der Event-WebHook-Konfigurationsseite befindet sich eine Schaltfläche, mit der wir die Integration testen können und die wir jetzt verwenden. Wenn wir darauf klicken, dann sehen wir in den Protokollen der Spring Boot-Anwendung Folgendes:

l.g.s.SendGridEventWebhookHandler            : Received 11 events

Wir können prüfen, ob die Daten von diesen Testereignissen in der Anwendung gespeichert wurden. Dazu senden wir eine HTTP-Anfrage an localhost:8080/opened und localhost:8080/clicked, entweder im Browser oder über ein Tool wie curl oder (mein Favorit) HTTPie.

Beispiel-JSON im Browser für den „/opened“-Endpunkt

Beispiel-JSON im Browser für den „/clicked“-Endpunkt

Verwenden der Anwendung in der Praxis

Anhand der Anweisungen in meinem Blog Senden von E-Mails in Java mit Twilio SendGrid lassen sich Tests durchführen, allerdings muss eine Sache geändert werden: Wir müssen der E-Mail einen Link hinzufügen, damit wir das Click-Tracking prüfen können. Wir müssen darauf achten, die Best Practices für das Click-Tracking zu befolgen, um sicherzustellen, dass unsere URLs auch richtig nachverfolgt werden. Der Code zum Senden von E-Mails sieht so aus:

import com.sendgrid.*;
import com.sendgrid.helpers.mail.Mail;
import com.sendgrid.helpers.mail.objects.*;

import java.io.IOException;

public class SendGridEmailer {

   public static void main(String[] args) throws IOException {

       Email from = new Email("test@example.com");
       Email to = new Email("matthew@<REDACTED>"); // use your own email address here

       String subject = "Sending with Twilio SendGrid is Fun";
       Content content = new Content("text/html", "and <em>easy</em> to do anywhere with <strong>Java</strong>. Here is <a href=\"https://www.sendgrid.com\">a link</a>");

       Mail mail = new Mail(from, subject, to, content);

       SendGrid sg = new SendGrid(System.getenv("SENDGRID_API_KEY"));
       Request request = new Request();

       request.setMethod(Method.POST);
       request.setEndpoint("mail/send");
       request.setBody(mail.build());

       Response response = sg.api(request);

       System.out.println(response.getStatusCode());
       System.out.println(response.getHeaders());
       System.out.println(response.getBody());
   }
}

Wir können diese Klasse unserem Projekt hinzufügen und sie in der IDE ausführen, während die Spring Boot-App noch im Terminal ausgeführt wird. Wir müssen nur darauf achten, dass wir die sendgrid-java-Abhängigkeit unserem Build hinzufügen, wie in diesem Blog beschrieben.

Wenn wir die main-Methode in dieser Klasse ausführen, senden wir eine E-Mail an uns selbst. Wenn wir diese E-Mail in unserem E-Mail-Client öffnen, meldet SendGrid ein open-Ereignis, und wenn wir auf den Link klicken, wird ein click-Ereignis gemeldet. Es dauert möglicherweise eine kleine Weile, bis diese Ereignisse eingehen, und sie werden möglicherweise als Batch zusammengefasst, aber unser Code kann damit gut umgehen.

Und das war es auch schon. Wir können jetzt nachverfolgen, welche Empfänger unsere E-Mails geöffnet und auf die darin enthaltenen Links geklickt haben.

Zusammenfassung

Wenn du mit Twilio oder SendGrid Anwendungen erstellst, würde ich mich freuen, davon zu hören. Nimm mit mir Kontakt auf: