Designing Magnolia e-mail templates

Mailing functionality is rather natural for any application. You often need to send e-mails about registration or order confirmation. This post describes a way which has been used to design e-mail templates for Magnolia CMS. It provides a nice module to work with the e-mails.

You can easily register a template and send it via the following code:

Map<String, String> parameters = ...;
final Context context = MgnlContext.getInstance();
final MgnlMailFactory factory =
  MailModule.getInstance().getFactory();
final MgnlMailHandler handler = factory.getEmailHandler();
final MgnlEmail email = factory.getEmailFromTemplate("orderConfirmation",
  parameters);
email.setToList(to);
email.setBodyFromResourceFile();
MgnlContext.setInstance(context);
handler.prepareAndSendMail(email);

We use FreeMarker templates usually since it provides a flexible way to customize email based on the parameters. These are registered within a repository and can be accessed via API easily.

It’s quite easy to test a small email but designing an email template(like order confirmation) with a great number of parameters can become a headache. So I’d like to propose the following technique. JUnit tests are very suitable for testing things quickly so why not use them here. Lets create a small test:

@Test
public void testSendEmail() throws IOException, TemplateException {
  // Prepare writer for writing file contents
  FileWriter writer = new FileWriter("test.html");
  // Lets build our complex object
  final ShoppingOrder order = buildDummyOrder();
  // A map of parameters is expected to fill template
  // with some dummy data.
  final Map orderEmailParameters
    = getOrderEmailParameters(order);
  // Generating contents into the FileWriter
  FreemarkerHelper.getInstance().
    render("orderConfirmation.html", orderEmailParameters, writer);
  writer.flush();
  writer.close();
  // Open file in the browser to see it immediately.
  if(Desktop.isDesktopSupported()) {
    Desktop.getDesktop().open(new File("test.html"));
  }
}

Please note that I’ve skipped all catch exception handling to increase readability.

As a result of running this test – an email contents will be generated and you’ll be able to test all of the conditions rather quickly.

Lets have a look at getOrderEmailParameters in more details. As you can see factory.getEmailFromTemplate(template, parameters) expects parameters in the form of Map<String, String>. Not very flexible, right? Especially if have a complex object graph. To make it much easier a small helper method has been written which dumps all public properties into the map:

void writeClassParameters(Object obj, Map parameters, String prefix) {
  // Iterate through every object field
  for(Field field : obj.getClass().getFields()) {
    // We need public fields only
    if((field.getModifiers() & Modifier.PUBLIC)
      != Modifier.PUBLIC) {
      continue;
    }
    // Lets get a value of the field
    final Object value = field.get(obj);
    final String name = field.getName();
    final String nameWithPrefix = prefix + "_" + name;
    // No need to put null values in to the map
    if(value == null) {
      continue;
    }

    // Lets put a string presentation of the object
    // into the map. It can be useful if you use
    // conditions in your template(like
    // [#if order_deliveryData??])
    parameters.put(nameWithPrefix, "" + value);

    // Lets dump an inner complex object also.
    if(!field.getType().isPrimitive() &&
      field.getType().getPackage()
        .getName().startsWith("com.softteco")) {
      writeClassParameters(value, parameters, nameWithPrefix);
    }
  }
}

As a result you’ll get a map of the parameters like:

  • order_status: NEW
  • order_price: 743.0
  • order_deliveryData_address_address: Line Address
  • order_paymentData_creditCardData_cardType: VISA
  • order_doctorData_address_country: Belarus

And later you’ll be able to use these parameters in the template. like
Card Type: ${order_paymentData_creditCardData_cardType!}