Templating HTML Emails in Azure Functions

John Kilmister, · 7 min read
This page was published 2 years ago. While the content may still be relevant there is a high chance things may have changed and the content may no longer be accurate.

Sending emails in Azure Functions is relatively straightforward using the SendGrid output binding. There is even a visual studio SendGrid template connecting a message queue with the SendGrid email sending capability. The emails in this template are plain text however, we can make them more attractive and richer using HTML.

In this post we will cover two approaches to templating HTML emails: RazorLight and Handlebars.net, however I have also included a list of other options towards the end of the post.

It is worth noting while we are using the templating engines to create HTML emails you can use this approach for templating other file types such as XML documents. You can also use these techniques in any other type of C# project such as a windows service or desktop app.

Why Template

For simple scenarios, string interpolation may work. When you need to add loops or complex logic however, templates can make working with the code easier. In addition by using raw string replacement there is the risk of making mistakes and not escaping characters and other challenges that have been solved before by templating engines.

Common Sample Code

To showcase both templating engines I chose to extend the SendGrid template to include a table of formatted items. The base template is available to pick when you create a new Azure Function inside visual studio.

Screenshot of Visual Studio Create Function

This planned change introduces the complexity of loops and the need of a HTML email template. I have created two Azure Functions, one using a Razor Light and the other a Handlebars template covered later in this post. In these, both will be using the same Model objects.

    public class Order
    {
        public string OrderId { get; set; }
        public string CustomerName { get; set; }
        public string CustomerEmail { get; internal set; }
        public Item[] Items { get; set; }
 
    }

    public class Item
    {
        public string Name { get; set; }
        public string Price { get; set; }
    }

This means we can use a JSON Payload…

{
      "orderId" : "12345",
       "customerName" : "David",
       "customerEmail" : "David@domain.com",
       "items" : [
            {"name":"product one",
            "price":"1.99"
            },
            {"name":"product two",
            "price":"2.99"
            },
            {"name":"product three",
            "price":"3.99"
            }
        ]
}

Sending the email is also common across both so I have extracted this into a shared method which we will use later.

 private static SendGridMessage SendEmail(string customerEmail, string htmlContent)
        {
            var message = new SendGridMessage();
            message.AddTo(customerEmail);
            message.AddContent("text/html", htmlContent);
            message.SetFrom(new EmailAddress(“FromEmailAddress@Domain.com”));
            message.SetSubject("Your Order");
            return message;
        }

Once this is set up and we have configured a send grid account we can look at the code for the two templating engines.

Razor Light

Razor is the templating engine used in ASP.net. The Microsoft Razor assemblies depend on much of the HTTP and MVC pipeline making them hard to use in non ASP.net applications, RazorLight aims to solve this. The RazorLight library supports both .net core 2 and 3.

Like ASP.net Razor, Razor Light supports partial views and helper methods in the templates.

Razor Light and Azure Functions

The readme of the Razor Light project mentions that “Serverless solutions are not supported yet. However, for Azure Functions, some users have reported success on Azure Functions 3.0.3. As of 6/3/2020”. There are also a number of closed GitHub issues around getting support to work.

When I tried with Azure Function 3 and .net core 3.1 I encountered none of the configuration issues mentioned.

One small issue I did have however was around performance. The Razor Light code works on compiled templates that are then cached in memory. If you are using a consumption plan function that are stopping and starting it makes the cache redundant. Without the cache a run took around 5 seconds to compile the template on my local machine.

Sample Code

To use Razor Light you first need to add the Nuget Package. There is an older RazorLight package however we need to use RazorLight.NetCore3 to work with out .net core application.

Razor Light supports reading embedded resources in addition to templates in strings as part of the library. In this sample I added a simple HTML template as an embedded resource named razor.cshtml. If you are looking at making a more complex HTML email template, mail chimp have a good guide to Limitations of html emails.

@using RazorLight
@inherits TemplatePage<Order>

<html>
<body>
<div>
    <h1>Order: @Model.OrderId</h1>
    <div>
        <p>hi @Model.CustomerName,</p>

        <p>Here is your order</p>

        <table>
            @foreach (var item in Model.Items)
            {
                <tr>
                    <td>@item.Name</td>
                    <td>@item.Price</td>
                </tr>
            }
        </table>
    </div>
</div>
</body>
</html>

The body of the function is then simply a few lines. This example expects the embedded resource to be called Razor and never reads from the template cache. You can see how to read from the cache on the Razor Light GitHub page though as mentioned this may be of limited use in a consumption plan Azure Function.

var engine = new RazorLightEngineBuilder()
.UseEmbeddedResourcesProject(System.Reflection.Assembly.GetEntryAssembly())
.UseMemoryCachingProvider()
.Build();

var htmlContent = await engine.CompileRenderAsync("Razor", order);
return SendEmail(order.CustomerEmail, htmlContent);

For more examples see the Razor Light GitHub page

Handlebars.Net

I have used HandlebarsJS on a number of projects where I did not want to use a full JS framework like React but needed some quick lightweight templating. Handlebars like Razor is a templating framework and has many parallels supporting expressions, partials and helpers. The syntax is a little different to Razor however the handlebars JS website covers this in great detail.

Recently I was told about Handlebars.net. As you may have guessed this is a port of the JS library that runs in .net. It uses the same API as the JavaScript version however compiles down to IL.

Handlebars.Net and Azure Functions

There is no explicit mention of Handlebars.net and Azure functions on the project page. That said however, it is .net standard compatible and in my limited tests I found no issues. I have also found examples of others successfully using Handlebars.net with Azure Functions in online discussions.

The templates compile like they do in Razor Light, however I found it took only 253ms to compile the template in comparison to the 5 seconds of Razor Light. With a larger or complex template, you may have longer compile times. For more example see the Handlebars.Net GitHub project page

Sample Code

To use Handlesbar.net you need to add the Nuget Package. Again I will use an embedded resource this time named handle.hbs.

<html>
<body>
<div>
    <h1>Order: {{OrderId}}</h1>
    <div>
        <p>hi {{CustomerName}},</p>

        <p>Here is your order</p>

        <table>
            {{# each Items}}
            <tr>
                <td>
                    {{this.Name}}
                </td>
                <td>
                    {{this.Price}}
                </td>
            </tr>
            {{/each}}
        </table>
    </div>
</div>
</body>
</html>

The body of the function is then simply a few lines. Unlike Razor Light we have to read the embedded resource ourselves. In this case I have added it to a variable messageTemplate which has been populated from the embedded resource.

var messageTemplate = System.Text.Encoding.Default.GetString(HtmlTemplateSample.Properties.Resources.handlebar);
var template = Handlebars.Compile(messageTemplate);
var htmlContent = template(order);

return SendEmail(order.CustomerEmail, htmlContent);

Other Options

Since I posted this article I have received some other suggestions that you may want to look at:

Summary

We looked at the need for templates in Azure Functions and at the Razor Light and Handlebar.net templating Nuget Packages.

Both Razor Light and Handlebar.net support a range of templating syntax, partial views, and helper methods in a .net standard assembly that can be used in an Azure Function.

In my small sample the Razor Light template took significantly longer to initially compile compared to Handlebar.net which may be an issue in an Azure Function.

This post was featured as part of the C# Advent 2021, please checkout all the other great content.

Title Photo by Justin Morgan

Recent and Related Articles