Or, How To Put Templates In Your Templates

Removing code duplication is one of the most satisfying parts of refactoring and programming in general. The past few decades are littered with a variety of ways to detect and reduce duplication for many different types of code. Solutions range from IDE-assisted refactoring on the more complex side of things to various strategies for pulling in content fragments like header and/or footer, so common content can be centrally managed.

By now it’s perhaps obvious what we’re going to talk about in this article: how to support includes in SparkPost Templates. A quick disclaimer, this support will be provided as part of a client library, instead of directly in our service.

I’ll be modifying our Go client library (which I maintain) with this new functionality. While we’re in there, it’s only slightly more work to allow writing your own macros, with the function signature func(string) string – so let’s make some Macros!

SparkPost Templates: How do they work?

The short answer is they’re basically like Handlebars templates (docs here on our variant). The longer answer is that under the hood, while they technically support more stuff (like user-defined macros), it would be, let’s say, “unwise” to allow our users to write their own macro code that would execute on our system.


As our company has evolved over the past few years from traditional software to become a cloud-based service provider, parts of our mindset have shifted too. Before our cloud shift, a feature like Template includes would have been implemented “inside the box”, meaning as a customization inside the email server itself.

While this approach is certainly still possible, the system stays simpler and more flexible when functionality can be pushed outwards. Instead of building one specific “in the box” feature, when we can empower customers to build the features they want most, that lets us innovate elsewhere.

The Search For Template Includes

My first thought when starting to build this feature was: “Ok, what library am I going to use?” – however, it turned out to not be quite that simple. What we’re going for here is to allow users of our client library to pre-process templates before calling our API. This means only Handlebars blocks with locally-registered macros should be interpreted and replaced, not every block in the template. None of the templating libraries I found supported this kind of partial template execution.

Thankfully, the code to detect Handlebars blocks (see Tokenize) isn’t too out there, which makes partially executing templates quite a bit easier. Here’s an example of a made-up template you might want to partially execute before sending an email:

Hi {{ FirstName }},

Thanks for shopping with SparkPost today! Your order will be processed shortly.

{{{ sp_invoice https://{{user}}@{{pass}}:cms.int.sparkpost.com/invoice/{{invoice_id}} }}}

Have a great day!

An advantage of this kind of approach is ease of integration between existing systems and SparkPost, letting users continue to use the same tools they’ve always used to build content. As long as the system can export over HTTP, content can be pulled into an email template. This is a somewhat contrived example, and I’m sure you’ll come up with some clever ideas for macros that we haven’t even considered. Let us know what you build!

The Template For Templating Templates

That’s all well and good, but how do you use the thing? The client library’s default behavior doesn’t change, so you’ll need to explicitly add calls to pre-process strings with your shiny new Macros. Here’s a list of the types and functions added for this feature:

type Macro struct {
	Name string
	Func func(string) string

type ContentToken struct {
	Type TokenType // int, StaticToken=0 or MacroToken=1
	Text string

// Enable your Macros here
func (c *Client) RegisterMacro(m *Macro) error

// Probably won't need this one, but it's there just in case.
// Used by Client.ApplyMacros and Recipient.Apply, below
func Tokenize(str string) (out []ContentToken, err error)

// Process registered Macros with optional Recipient
// user, pass, invoice_id in the example above come from Recipient
func (c *Client) ApplyMacros(in string, r *Recipient) (string, error)

// Probably won't need this one either.
// This is how user, etc gets pulled into the Macro argument
// Called automatically by ApplyMacros if Recipient != nil
func (r *Recipient) Apply(in string) (string, error)

Usage is simpler than the four (4) exported functions would imply. Check out the tests for examples. You’ll need to register some Macros with the appropriately-named Client.RegisterMacro , and then call Client.ApplyMacros to perform pre-processing on whichever strings might contain your Macros. The other two functions are there in case you’d prefer to do things differently than ApplyMacros does – like having Metadata take precedence over SubstitutionData , for example.

What’s Next For Template-y Templates?

You’ll notice the triple-curlies in the example template above. That’s a bit of a head fake. As of this writing, client-side macros treat double- and triple-curlies identically, with no escaping. For consistency with how SparkPost (and Handlebars) templates work, doubles should escape HTML in the substitution value, and triples should turn off the safety.

The careful reader will also notice that the feature, as written, is suitable only for single-Recipient Transmissions, or for multi-Recipient Transmissions using Templates that don’t have per-Recipient placeholders nested inside custom Macros. Supporting this use case would likely require modifying the Transmission payload to contain dynamic_html for the per-Recipient pieces, or unrolling the Transmission API call so the client library makes one call per Recipient.

Did you enjoy this post? Got a clever Macro to share? Let us know by commenting, or start a conversation on Github or send us a tweet.