Fork me on GitHub

Rendering e-Mails

Note: The purpose of this tutorial is to demonstrate how the HTML template and the binding code can be re-used from this example and how simple metadata eases formatting of a plain text variant.

A common problem in different types of applications is the rendering of e-mails. Of course it's a really solvable task. However, I want to present how an order confirmation mail might be created using the Snippetory Template Engine. To make it not too simple I will create a plain text and a HTML variant, based on user preference. Let's start with the plain text template:

$salutation $name, $text $name(pad='40') $quantity(pad='10' pad.align='right') $price(pad='12' pad.align='right') $sum(pad='14' pad.align='right') $line(pad='87' pad.fill='=') $cart_entry{ $name(pad='40') $quantity(pad='10' pad.align='right') $price(pad='12' pad.align='right') $sum(pad='14' pad.align='right') $currency }$ $line(pad='87' pad.fill='-') $total-label( pad='62' pad.align='right') $total(pad='14' pad.align='right') $currency $footer

This shows a number of interesting facts. The most obvious is the direct support for formatting the cart table. The formats stretch and shorten give you convenient control over the length of strings. Another thing to mention is the white space handling on region delimiters i.e. The t:cart_entry tag elements. Such an element will span the entire line if the line only contains this element and white space. No additional line breaks will be added by the demonstrated construct.

Now, that we have the template, we need some code to bind the data. But luckily, we have written a part of it in the page pattern example. Of course I'll reuse this! And the rest is quite the same as any mail.

The code for creating an email to a user might look like this:


public class MailRenderer {
  ResourceBundle msg;
  
  public MailRenderer(Locale locale) {
    msg = ResourceBundle.getBundle("messages",locale);
  }
  public void renderMail(Template mail, User user) {
    mail
      .set("salutation", getSalutation(user))
      .set("name", user.getDisplayName());
    renderFooter(mail);
  }
  protected String getSalutation(User user) {
    String key = "user.salutation.male";
    if (user.getGender() == Gender.FEMALE) key = "user.salutation.female";
    return msg.getString(key);
  }
  protected void renderFooter(Template mail) {
    mail.set("footer", msg.getString("mail.footer")):
  }
}

Now we just have to load the right template, set the main text (which is different per mail) and call our mail and cart rendering methods and send the result. That's all.


puplic class OrderConfirmationRenderer {
    protected final ResourceBundle msg;
    protected final Locale locale;
    protected final MailRenderer mailRenderer;

    public OrderConfirmationRenderer(Locale l) {
        this.msg = ResourceBundle.getBundle("messages", l);
        this.locale = l;
        this.mailRenderer = new MailRenderer(l);
    }
  public Template render(User user, Cart cart) {
    return user.isPlainTextMail() ? renderPlainText(user, cart) : renderHtml(user, cart);
  }
  public Template renderHtml( User user, Cart cart ) {
        Template mail = Repo
                .readResource("org/jproggy/examples/minishop/OrderConfirmation.html")
                .locale(locale).encoding(Encodings.html).parse();
        mailRenderer.renderMail(mail, user);
        new Cartpage(locale, null, cart).renderContent(mail);
        return mail;
  }
  public Template renderPlainText( User user, Cart cart ) {
        Template mail = Repo
                .readResource("org/jproggy/examples/minishop/OrderConfirmation.txt")
                .syntax(Syntaxes.FLUYT).locale(locale).encoding(Encodings.plain).parse();
        mailRenderer.renderMail(mail, user);
        // by using the renderCart method we bypass the loading of the html
        // template. This is needed. Additional the empty cart handling is skipped.
        // This is no problem as we avoid ordering of empty carts.
        new Cartpage(locale, null, cart).renderCart(mail);
        return mail;
  }
}

Sometimes this is really all. Sometimes we do it the generous way and provide the choice between a plain text and a html version of the mail. In that case there's still some work to do. Let's start with the html template. This is easy. As we can reuse the cart template, too, we don't need too much:


<html>
<body>
<p>$salutation $name,</p>
<p>
$text
</p>
$content
<p>
$footer
</p>
</body>
</html>

The last effort is to modify our java code. In fact this is split into two parts. First we obviously didn't provide an HTML footer. We implement a class that's a little bit more sophisticated:

Finally the routine to send the mail needs some enhancement. First we have to select the template to load and the mail rendering method to call. And in the end we call the other methods:

We can see that the Snippetory Template Engine provides a number of interesting features:

  • a simple approach for plain text formatting
  • pretty fine grained code reuse
  • the possibility to organize the logic in methods and classes
  • an abstraction layer that frees the logic from technical issues of the output format.

That's all for now. Have fun.

Bernd Ebertz

Head, Founder and chief technology evangelist of
jproggy.org
Java, and all Java-based marks are trademarks or registered trademarks of Oracle.
This site is not affiliated in any way with Oracle.