Emailing Made Easy | Part 2 | Templating with Razor Views from ASP.NET

Email templating tends to end in choosing the lesser of two evils.
Templates in code: Not dynamic, requires deployment for changes, not catered for A/B testing.
Templates in data store: Not static so tends to breaks at runtime(Hello [FNAME]), merging is not maintainable, not testable.

Razor was designed for HTML generation using a code-focused templating approach. Basically designed for backend developers who don't wanna blow things up by missing out a div tag.
RazorEngine allows you use Razor outside of a web project.


If you're planning on sending emails to users using Outlook, read up on Vector Markup Language and come back. Lets start with the basics of a HTML email template.

All templates have a similar core, you're not building an extravagant web page here.
An HTML table with a width of 600px. That's it! Creating a layout is basically rows and columns. No JavaScript, No SVG, depending the email client some CSS properties might not work.

If you're wondering how do I know what works and what doesn't? Copy someone else's.

  • Really Good Emails is your best source for layout designs and content copy to use.
  • Font-Awesome-SVG-PNG this repo has the Font Awesome icons as png files.
  • Emojipedia for hitting up those millennials 🔫 I recommend keeping emoji use strictly for subjects and preheaders. Email clients tend to render them as images which messes up your design.

Lastly you want your template layout to be semantic/consistent. You'll understand why when we get to the code.


Desktop Email Template View Mobile Email Template View Outlook Email Template View

We aren't trying to win awards for designing templates that render perfectly in Outlook 🙈


The reference source code for this post is on GitHub(.Net v472 C# v7.1)
static async Task Main(string[] args) 💜 Okay I'm done with the emojis.

I'm not going to explain everything, I'll just go over some of the important code snippets.

I'm using the RazorEngine with no caching, you can choose whether to cache the compiled template, also I'm loading my templates from a .cshtml file but you can load a string into the RazorEngine with a dynamic model.

ResolvePathTemplateManager means templates will be loaded from a path, but you can also do strings or embedded resources in an assembly.

$@"{dirPath}\EmailPartialViews", $@"{dirPath}\EmailViews"} here I'm explicitly giving the path to the template, since ASP.NET web projects output to the \bin folder and not \bin\$(Configuration).

AppConfiguration.cs

Each template I create will have a corresponding View Model, this will inherit from EmailTemplateBase, EmailTemplateBase has all the common template properties.
My view model will also share the same name as my Razor view.
View Model: WelcomeEmailTemplate.cs
Razor View : WelcomeEmailTemplate.cshtml

WelcomeEmailTemplate.cshtml

_Footer.cshtml

In my template I can render Partial Views, which means I can slot the footer into my template if I want, my footer is defined somewhere else and can be inherited(very OOP).
My footer template has a loop for creating my social network links, in a code or merge field scenario I'd be building up a string with HTML tags and it becomes very messy.

EmailTemplateHelper.cs

My generic RenderEmailTemplate method takes in a type that inherits from IEmailTemplateBase, RazorEngine will find the Razor view based on the model name.
If it doesn't, it will break, so it is all very static and easy to test.

So let's recap the advantages to this approach.

  • It's type checked so it breaks at compile time.
  • I can change templates without redeploying.
  • I can introduce some A/B testing into my emailing.
  • If I miss out a value, such as someone's last name. The template will be rendered as Hello Lionel and not Hello Lionel [LNAME].

Lastly, let's talk semantic design. People will argue best practice not from a proven perspective but from a generally accepted perspective.
Maintainable code > best practice code.

When you template in code, you can document the template. When the template is stored elsewhere and is dynamically rendered you lose the context of what the template is. Semantic design being the description of how it looks.

If I use a CSS class called 'heading' in my template, semantically I know it is a heading, I don't care if it is a h1 or h2 HTML element but I know it should look like an heading.

Here is why it is important, when you send emails, you need a plain text version along with the HTML version. Now you could store an plain text template, but we want to work smarter not harder.

EmailTemplateHelper.cs

If you're like me and JQuery is your best friend, you'll love AngleSharp. It's the Cheerio for .Net.

Using AngleSharp I can parse my HTML template and create a plain text version, based on my HTML classes which describe the look of my template.
Now I just have to maintain my template in the Razor View and everything else will just work!