E-Mail-Templates mit Thymeleaf (Teil 3 von 1)

Im dritten der bisher nur einteiligen Serie über E-Mail-Templates mit Thymeleaf zeige ich, wie sich HTML-basierte E-Mails erzeugen lassen, die vom Anwender mit seinem E-Mail-Programm der Wahl vor dem Versenden noch bearbeitet werden können (im ersten Teil wird es um die rein server-seitige Generierung von E-Mails gehen und der zweite Teil ist der Vorläufer zu diesem, bei dem es um Text-E-Mails geht).

Wunsch der Fachabteilung war es, zwei Datensätze zu vergleichen und die Unterschiede hervorzuheben. Die E-Mail sollte eine Tabelle enthalten, in dem diese Werte gegenübergestellt werden:

Vom Server vor-generierte E-Mail

Über einen mailto-Link lässt sich im body-Parameter nur Text übergeben (siehe Teil 2 dieser Reihe) und bei der Variante aus Teil 1 verschickt der Server selbst die E-Mail, ohne das der Anwender noch eingreifen könnte.

Die Lösung besteht nun darin, dass der Server eine .eml-Datei für den Download erzeugt. Die damit verknüpfte Anwendung ist das E-Mail-Programm, dass die Nachricht dann sendebereit anzeigt.

Controller-Code

    @GetMapping(path = "/{id}/vergleichen/{candidate}")
    public void mailvorlageZeitreihenVergleichen(
            @PathVariable("id") Long id,
            @PathVariable("candidate") Long candidateId,
            HttpServletResponse response
    ) throws IOException {
        Context context = new Context();
        context.setVariable("original", service.zeitreihe(id));
        context.setVariable("candidate", service.zeitreihe(candidateId));
        context.setVariable(ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME,
                new ThymeleafEvaluationContext(applicationContext, conversionService));
        String body = templateEngine.process("intern/zeitreihen/mailvorlageZeitreihenVergleichen", context);

        response.setContentType("text/plain");
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"Negativliste_"
                + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm")) + ".eml\"");
        BufferedWriter out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream()));
        out.write(body);
        out.flush();
    }

Der Code besteht aus drei Abschnitten:

  1. Ermitteln der Daten und übertragen in den Context.
  2. Verarbeiten des Thymeleaf-Templates.
  3. Schreiben der Daten in den OutputStream der HttpServletResponse.

In den Context schreiben wir die Daten, auf die wir später im Template zugreifen wollen. Einzige Besonderheit ist hier der ThymeleafEvaluationContext. Diesen brauchen wir, damit ggf. eigene Converter genutzt werden können, z. B. für die Formatierung von Datumswerten (die Syntax mit den doppelt-geschweiften Klammern: ${{zeitraum}}).

Über den Header CONTENT_DISPOSITION teilen wir dem Browser zum einem mit, dass er die empfangenen Daten als Download anbieten soll und zum anderen legen wir hier auch den Dateinamen fest, den wir mit .eml enden lassen. Das zeigt sich dann für den Anwender wie folgt:

Download der vom Server gerenderten E-Mail-Vorlage

Das Template mit der E-Mail-Vorlage

Bei einer Spring-Boot-Anwendung liegt das Template in der Datei src/main/resources/templates/intern/zeitreihen/mailvorlageZeitreihenVergleichen.html und sieht so aus:

To:
Subject: Zeitreihen-Vergleich: <th:block th:text="${original.bilanzkreis}"></th:block> vs. <th:block th:text="${candidate.bilanzkreis}"></th:block>
X-Unsent: 1
Content-Type: text/html

<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>E-Mail-Vorlage: Zeitreihen vergleichen</title>
  <style>
    body {...} 
    th {...}
    .abweichung {
      color: red !important;
      font-weight: bold !important;
    }
  </style>
</head>
<body>
  <table>
    <thead>
    <tr>
      <th></th>
      <th>Originale Zeitreihe</th>
      <th>Vergleichs-Zeitreihe</th>
    </tr>
    </thead>
    <tbody>
    <tr>
      <th>Bilanzkreis</th>
      <td th:text="${original.bilanzkreis}"></td>
      <td th:text="${candidate.bilanzkreis}" th:classappend="${original.bilanzkreis != candidate.bilanzkreis} ? 'abweichung' : ''"></td>
    </tr>
    <tr>
      <th>Lieferant</th>
      <td th:text="${original.lieferantName}"></td>
      <td th:text="${candidate.lieferantName}" th:classappend="${original.lieferantName != candidate.lieferantName} ? 'abweichung' : ''"></td>
    </tr>
...

Das Template beginnt mit den Steuer-Header für die E-Mail. Ein Empfänger (To:) ist hier weggelassen und muss vom Anwender in seinem Mail-Programm ergänzt werden. Das X-Unsent sorgt dafür, dass das Mail-Programm weiß, dass die E-Mail noch nicht versendet wurde (BTW: Ich habe das Ganze nur mit Outlook ausprobiert). Schließlich teilen wir dem E-Mail-Programm noch mit, das der Inhalt (Body) HTML ist und nach der Leerzeile kommt auch schon der Body. Hier kann wie gewohnt die Thymleaf-Syntax verwendet werden (das klappt auch in den Header-Zeilen wie beim Betreff (Subject) zu sehen ist).