Articles in this section

Advanced Personalization in MessageGears (Using FreeMarker)

Overview

Please note before starting that MessageGears uses Apache FreeMarker (version 2.3.23) for both Content Templates and SQL templates. For more detailed information on Freemarker and its uses, the official handbook from Apache can be found by clicking on the following link. This article discusses using FreeMarker at an Advanced level. An understanding of basic and intermediate concepts of FreeMarker is recommended before using the processes detailed in this article. Additionally, this article will focus on best practices in FreeMarker for content templating. You can find more information on building Campaigns with MessageGears, in the FreeMarker Basic and Intermediate sections. 

Table of Contents

Advanced Topics (Developers)

Localization

Advanced Built-ins

Interpreting Strings as FreeMarker

Evaluating semi-structured data

Writing Macros & Functions

Dynamic Content Strategy

Separate Variables and Display

Include vs. Import

Content Architecture Design

Advanced Topics (Developers)

Localization

Before beginning, please familiarize yourself with the following two articles:

This documentation will provide you with examples for localization using FreeMarker.

A popular way to handle localization in MessageGears is the use of the Local Snippet Library. The library would have a different translation of any content copy/text that you would need to localize.

The following example involves creating an English/US (Default) snippet and a Spanish/Spain version snippet. This example assumes that the correctly formatted locale (i.e. ‘es_SP’) is going to come in the Recipient/Audience data under the “locale” attribute.

This default may not always be the standard case. Users might need a custom function to return the correctly formatted locale based on another identifier, such as “country name” or “country code”.

The following example would be used for an import, not an include:

<#-- Local Snippet: localization -->
<#assign subject>Open this email!</#assign>
<#assign greeting>Hello ${Recipient.first_name}, you’re our favorite customer!</#assign>
<#-- Local Snippet: localization_es_SP -->
<#assign subject>¡Abra este correo electrónico!</#assign>
<#assign greeting>Hola ${Recipient.first_name}, eres nuestro cliente favorito!</#assign>

HTML Template:

<#setting locale = Recipient.locale>
<#import '/local/localization' as lang>
<p>${lang.greeting}</p>

Recipient / Audience Data:

<Recipient>
<first_name>George</first_name>
<locale>es_SP</locale>
</Recipient>

Rendered Output:

<p>Hola George, eres nuestro cliente favorito!</p>

Because FreeMarker’s localized lookup automatically adds the ‘_es_SP’ based on the locale setting, the import does not need to be specified. Additionally, because FreeMarker automagically converts date formats, number formats, and currency (see note below), users don’t need to worry about specifying different assignments, for those, only for the content copy/text.

FreeMarker does not have the current currency exchange rates. Another function is likely to be needed to insert them into the process. Ask a member of the support team for best practices on currency conversions if it’s something that is required.


Advanced Built-ins

Interpreting Strings as FreeMarker

One of the most powerful features of FreeMarker, interpreting strings as FreeMarker code can allow you to include FreeMarker in your data (Audience, Supplemental, or Campaigns) and render that in your template. It’s recommended that you only interpret display logic or links in this manner. Example with XML-based Supplemental (formerly called Context) Data:

For additional information on Interpreting Strings, you can access the Apache FreeMarker Manual section here.

Data:

<ContextData>
<Entry>
<key>123</key>
<subject>New deals for ${Recipient.first_name} inside!</subject>
<preheader>${Recipient.first_name}, you have ${Recipient.points?number} points to spend.</preheader>
</Entry>
</ContextData>
<Recipient>
<first_name>George</first_name>
<middle_initial>P.</middle_initial>
<last_name>Burdell</last_name>
<points>3133</points>
</Recipient>

HTML Template:

<#assign preheader = ContextData.Entry[0].preheader?interpret>
<p>Preheader: <@preheader /></p>

Rendered Output:

<p>Preheader: George, you have 3,133 points to spend.</p>

Evaluating semi-structured data

You can ignore the warnings on this page if you are only using first-party data.

This feature is especially useful for data warehouses that can easily store and build semi-structured data. FreeMarker can evaluate a JSON-string or array from your data (Audience, Supplemental, or Campaigns) to be a hash or sequence, respectively. For a more in-depth look at Evaluating semi-structured data, you can find the official Apache FreeMarker section detailing it here.

JSON Example:

Works with escaped or un-escaped JSON strings.

<Recipient>
<first_name>George</first_name>
<middle_initial>P.</middle_initial>
<last_name>Burdell</last_name>
<points>3133</points>
<extra_data>{"code": "123","link": "https://www.messagegears.com","description": "Example Description"}</extra_data>
<favorite_items>[7,10,13]</favorite_items>
</Recipient>

HTML Template:

<#assign json_data = Recipient.extra_data?eval>
<p>${first_name} has this code: ${json_data.code}</p>

Rendered Output:

<p>George has this code: 123</p>
MessageGears will be upgrading the version of FreeMarker to support ?json_eval, which should be used over regular ?eval once it is available.

Array / Sequence Example:

HTML Template:

<#assign arr_data = Recipient.favorite_items?eval>
<#list arr_data as x>
<p>Showing item: ${x}</p>
</#list>

Rendered Output:

<p>Showing item: 7</p>
<p>Showing item: 10</p>
<p>Showing item: 13</p>

As you can see, FreeMarker easily recognizes the array and turns it into a sequence. For some databases, they will outout array/variant data types with new lines between each item, but don't worry, it's also not a problem for FreeMarker.

Lastly, when you get really creative, you'll want to evaulate an array of JSON strings. When doing this, FreeMarker should automatically make this a sequence+hash, meaning you eval once for everything. You will not need to eval the array to a sequence, and then eval each json string individually.

Writing Macros & Functions

Rule of thumb: Macros are mostly used for printing output, while Functions are mostly used for returning a result; however, there is some gray area as each can be used to do the other.

You can find more information on Macros and Functions and their use in FreeMarker below:

Generally, functions are used for a set of operations that can be reused across different templates. Here’s an example function for finding the number of days in the next month:

<#-- Get the first day of the month 2 months from today, subtract one day, which is the last day of the next month. Returns 28, 29, 30, or 31 as a number.  -->
<#function nextMonthDays>
  <#assign newMonth = .now?string("MM")?number + 2>
  <#assign newDateString = newMonth?string + "/01/" + .now?string("yyyy")>
  <#assign newDate = newDateString?date("MM/dd/yyyy")
  <#assign lastDay = newDate?long - 86400000>
  <#assign numDays = lastDay?number_to_date?string("dd")?number >
<#return numDays>
</#function>    
Regarding the function above and how FreeMarker handles dates: FreeMarker automatically interprets “incorrect” dates for you. For example, if this function were to run in December 2020, then you would be looking at “14/01/2020” as your new date string, but FreeMarker interprets this as “02/01/2021” for you automatically.

What if you wanted last month instead of next month, or two months from now? Let’s take this same code and add a variable to make the number of months dynamic:

<#-- Get the first day of the month x months from today, subtract one day, which is the number of days in the next month -->
<#function nextMonthDays numMonths>
    <#assign newMonth = .now?string("MM")?number + numMonths>
  <#assign newDateString = newMonth?string + "/01/" + .now?string("yyyy")>
  <#assign newDate = newDateString?date("MM/dd/yyyy")
  <#assign lastDay = newDate?long - 86400000>
  <#assign numDays = lastDay?number_to_date?string("dd")?number >
<#return numDays>
</#function>

Another note to keep in mind is that for functions, you can't overload them. You can specify optional parameters to your function by adding defaults, but only as items at the end (not the start or middle). For instance:

<#function display style text burl="defaulturl" bname="defaultname">

That will make a function called display and allow 2, 3, or 4 parameters (style, text, burl, bname). This will not work though

<#function display style text="defaulttext" burl bname>

All the custom parameters have to be at the end whereas in this example, burl, and bname don't have defaults defined and occur after a "defaulted" parameter.

Macros, on the other hand, are more of a “module” or “template fragment” which prints text (i.e. HTML and CSS) and can also be shared across different templates. Here is an example of a footer macro:

<#macro footer brandName>
<p>This is the footer for ${brandName}. Click <a href="${Gears.unsubscribe()">here</a> to unsubscribe.</p>
</#macro>

Macros are printed by calling the macro:

<@footer brandName="MessageGears" />

Dynamic Content Strategy

Separate Variables and Display

IMPORTANT! Make sure to separate variable templates and display templates. Mixing them will cause a number of errors that will be problematic going forward. A variable will be missing according to the render error, and users will then have to go searching through display code for where the variable is set. Another issue that can occur is that another template is expecting a variable that doesn’t exist because the template isn’t being referenced at all.

Example:

<#-- Global Snippet: global_vars -->
<#-- This snippet only has assignments -->
<#assign foo = "123">
<#assign bar = "abc">
<#-- Global Snippet: header -->
<#-- This snippet has no assignments -->
<p>This is where we print both ${foo} and ${bar}</p>
<#-- Main HTML Content -->
<#include '/global/global_vars'>
<#include '/global/header'>
<#-- Rendered Output -->
<p>This is where we print both 123 and abc</p>

Include vs. Import

Macros and functions can be created or used in any FreeMarker template. Macros and functions can also reference other functions and templates as well. Previously, we’ve used <#include> to reference snippets, but we’ll now discuss <#import> and the differences between the two. While you might end up using both, it is very likely that you will want to stick with one or the other based on who is managing content day-to-day.

For a more detailed look at include and import functions, you can reference the FreeMarker manual sections:

Include: An "Include" will reference another FreeMarker template and add the code to the template that is being referenced. The referenced template and its functions, macros, and variables will automatically inherit any variables, macros, or functions that were created in the main template. The referenced template can include both FreeMarker + HTML/CSS as plain text.

In the example below, the “starting_number” variable is set in the main template, but referenced in the included function:

<#-- Global Snippet: add_five -->
<#assign snippet_var = "Typically used for including">
<p>HTML line to demonstrate that HTML can be included, but not imported</p>
<#function addFive>
 <#assign five_added = starting_number + 5>
 <#return five_added>
</#function>
<#-- Main HTML Template -->
<#assign starting_number = 3>
<#include '/global/add_five'>
<p>Adding five to ${starting_number} results in ${addFive()}</p>
<p>Printing: ${snippet_var}</p>
<#-- Rendered Output -->
<p>HTML line to demonstrate that HTML can be included, but not imported</p>
<p>Adding five to 3 results in 8</p>
<p>Printing: Typically used for including</p>

Import: An import will reference another FreeMarker template, which then effectively becomes a template within a template with a given namespace. If an imported reference template has macros and functions with variables, then those variables must be specified as parameters when the macro is called, the variables are not inherited like import. The referenced template can only be FreeMarker, any HTML/CSS must be inside a created macro or function.

<#-- Global Snippet: add_five -->
<#assign snippet_var>Used for importing</#assign>
<#function addFive starting_number>
 <#assign five_added = starting_number + 5>
 <#return five_added>
</#function>
<#-- Main HTML Template -->
<#assign starting_number = 3>
<#import '/global/add_five' as com>
<p>Adding five to ${starting_number} results in ${com.addFive(starting_number)}</p>
<p>Printing: ${com.snippet_var}</p>
<#-- Rendered Output -->
<p>Adding five to 3 results in 8</p>
<p>Printing: Used for importing</p>

Importing gives you some nice advantages, but has drawbacks. With namespaces, you don’t have to worry about overwriting variables with the same name. You also get more control on what gets printed (i.e. no accidental whitespace from include). Overall though, import is harder to use and understand for less technical marketers.

Content Architecture Design

What are the best ways to design content architecture within Accelerator? It depends, we usually see two scenarios: an “Include-Only” design, and a more “Advanced” design.

Include-Only: No import, only include. This makes layouts simpler, and less-technical users can follow designs easier. It forces you to break out individual modules into separate shared content snippets. It also allows the use of the drag-n-drop template editor to drag in the modules that they want to use.

Advanced: Using mostly import, with include as needed. This method is almost entirely macro driven. Templates are almost strictly code. Allows the most flexibility. This removes the ability to use the template drag-n-drop editor.

Include-Only Design

Advanced Design

Best for:
  • 1-2 person (small) email developer teams
  • Less technical users managing content (i.e. users besides developers can make updates)
  • Creating a straightforward tree structure in Accelerator
  • Larger email developer teams that are solely managing the content
  • Advanced display use-cases
Was this article helpful?
0 out of 0 found this helpful

Comments

0 comments

Please sign in to leave a comment.