How to send transactional HTML emails with Mailgun, Handlebars and Firebase
Tags: firebase, webdev, mailgun
Published: 2020-11-27 09:28:28

---
title: How to send transactional HTML emails with Mailgun, Handlebars and Firebase
published: true
date: 2020-11-25 00:00:00 UTC
tags: firebase,webdev,mailgun
canonical_url: https://codechips.me/send-html-emails-with-mailgun-and-firebase-functions/
---

If you are building any serious app chances are big that you have to send some emails. I needed to send transactional emails for a web app I am building, so I reached for the trusted Mailgun.

The app itself is based on Firebase and Firebase Functions was an obvious choice for sending emails. What was not so obvious is how to put everything together. I went through the pain and finally figured it out.

Below are my notes on how I got everything to work with [TypeScript](https://www.typescriptlang.org/) using [Mailgun](https://www.mailgun.com/), [Handlebars](https://handlebarsjs.com/) templates for HTML emails and [Firebase Functions](https://firebase.google.com/docs/functions).

## Firebase Functions Setup

I assume here that you already have a Firebase project set up with the local Firebase emulator running. If not, you can follow my guide - [Smooth local Firebase development setup with Firebase emulator and Snowpack](https://codechips.me/local-development-with-firebase-emulator-and-snowpack/). It targets [Svelte](https://svelte.dev/), but you can skip those parts if you are using some other framework or technology.

## Mailgun Setup

There are many transactional email providers, but I chose [Mailgun](https://www.mailgun.com/). Mostly because of their cool name and logo, and also because they have a generous free quota (5K emails/month) and a nice Javascript SDK.

I won't go through setting and configuring Mailgun, DNS and all that jazz. They already have good documentation on how to do it. Instead, I will concentrate on wiring everything up in code.

There are several Mailgun libraries on NPM, but we will go with the official one. It's written in Javascript and if you are using Typescript you have to install the types.

```text
$ npm add mailgun.js @types/mailgun-js
```

Because Mailgun SDK is written in Javascript you have to add `esModuleInterop` to your `tsconfig.json`.

```json
{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "esModuleInterop": true,
    "outDir": "lib",
    "sourceMap": true,
    "strict": true,
    "target": "es2017"
  },
  "compileOnSave": true,
  "include": [
    "src"
  ]
}
```

All right! We are ready to roll!

## Writing the email sender function

To keep code clean we will keep the email sending logic in a separate file. Create a new `email.ts` file in the `src` directory.

```ts
// email.ts

import * as functions from 'firebase-functions';
import Mailgun from 'mailgun-js';

const apiKey = 'mailgun-api-key';
const domain = 'mg.example.com';

const mg = new Mailgun({ apiKey, domain });

export const send = (subscribers: string[]) =>
  mg.messages().send({
    from: 'Mailgun Test <noreply@mg.example.com>',
    to: subscribers,
    subject: 'Mailgun test',
    text: 'Hello! How are you today?',
    html: '<h1>Hello!</h1><p>How are you today?</p>'
  });
```

So far, so good. With this code we can send an email to a bunch of subscribers whose email addresses are passed in as a list of strings.

## Firebase Remote Config

Mailgun SDK requires an API key to send email. While you can hardcode it in your code, as we did in the example above, you should never do it. Instead, we can leverage Firebase Remote Config for this and keep our Maingun API key securely stored in it.

```text
$ firebase functions:config:get > .runtimeconfig.json

```
If you already don't have anything there you will get and empty JSON file. Let's add our Mailgun API key to it.

```json
{
  "mg": { "key": "your-mailgun-api-key" }
}
```

**NOTE**: to set this value in production later you can use the command below.

```text
$ firebase functions:config:set mg.key="your-mailgun-api-key"
```

Also, make sure to keep all your config keys lowercase or Firebase will complain!

Now, when the Firebase emulator starts it will pickup the local config. This means that we can get the key from the functions config.

```ts
// email.ts

import * as functions from 'firebase-functions';
// ...

const apiKey = functions.config().mg?.key;
```

Our basic email function is now done. Let's continue with setting up our Handlebars templates.

## HTML email templates with Handlebars

HTML email design is hard. It's like going back to the 90s again. Tables and all. Luckily there are plenty of apps that can help you with that. Maybe you even have an old copy of Adobe Dreamweaver laying around somewhere? If you a feeling adventurous.

Jokes aside, if you want to learn more about HTML email design I can highly recommend reading [An Introduction To Building And Sending HTML Email For Web Developers](https://www.smashingmagazine.com/2017/01/introduction-building-sending-html-email-for-web-developers/) on Smashing Magazine.

But, we are aiming for the MVP here as this article's focus is on the technical implementation and not on email design.

My goal was to keep the email templates separate from code and Handlebars is a good fit for that. With that said, it was not as straight forward as I first thought.

Handlebars.js is one of the templating languages that allows you to precompile your templates, so you can later import them in code. It took me a while to get it right, but here is how you do it.

First, let's install [Handlebars](https://handlebarsjs.com/) and [npm-run-all](https://github.com/mysticatea/npm-run-all) utility package that we will use for the precompile step.

```text
$ npm add handlebars npm-run-all
```

Next we need to create two Handlebars templates. One for html emails and one for text emails.

Create an `emails` folder in the root of the project and then create two files in it - `html.handlebars` and `text.handlebars`.

```html
{{!-- html.handlebars --}}

<h1>{{title}}</h1>

<p>{{body}}</p>

<p>All the best, John</p>
```

Text template is needed for email clients that can't display HTML. I am looking at you [Mutt](http://www.mutt.org/)!

```text
{{!-- text.handlebars --}}

{{title}}

{{body}}

All the best, John
```

Now that we have created the templates we need to precompile them with this command.

```text
$ npx handlebars emails/ -f src/templates.js -c handlebars/runtime
```

It compiles the templates into `templates.js` module and also includes the reference to `handlesbars/runtime` module that is needed for some reason.

Now you can import `templates.js` in your code and both `text` and `html` templates will be available.

```ts
// email.ts

import * as functions from 'firebase-functions';
import Mailgun from 'mailgun-js';

import * as Handlebars from 'handlebars/runtime';
import './templates';

const apiKey = functions.config().mg?.key;
const domain = 'mg.example.com';

const mg = new Mailgun({ apiKey, domain });

// import our email and text precompiles templates
const html = Handlebars.templates['html'];
const text = Handlebars.templates['text'];

export const send = (subscribers: string[]) => {
  const data = { title: 'Mailgun Test', body: 'How are you?' };

  return mg.messages().send({
    from: 'Mailgun Test <noreply@mg.example.com>',
    to: subscribers,
    subject: data.title,
    text: text(data),
    html: html(data),
  });
};
```

Our email send function is now complete, but let's streamline our development environment a bit, so that Handlebars templates are precompiled automatically before we build our functions.

Change the `scripts` section in functions' `package.json` to this.

```json
"scripts": {
    "build": "run-s build:templates build:tsc",
    "build:tsc": "tsc",
    "build:templates": "handlebars emails/ -f src/templates.js -c handlebars/runtime",
    "serve": "npm run build && firebase serve --only functions",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
```

When we now execute `npm run build` Handlebars will compile the templates first before we compile function. This is done with `run-s` (s is for serial) which is a part of the `npm-run-all` utility package.

## Wiring everything together

Now that we have all the different parts done, let's wire them all together.

```ts
// functions/src/index.ts

import * as functions from 'firebase-functions';
import { send } from './email';

export const sendEmail = functions.https.onCall(async data => {
  const { subscribers } = data;

  try {
    await send(subscribers);
    return { ok: true };
  } catch (err) {
    return { ok: false, error: err.message };
  }
});
```

That's it! Now you can send transactional emails with Mailgun through Firebase Functions. I kept the example minimal in order to keep things simple, but you can fetch data from Firestore or send the email in the Firebase trigger functions. Imagination is the limit!

## BONUS: Firebase Functions Emulator Startup

If you what to start both Firebase emulator and function compilation watch, when you start the project, change the `scripts` section of your main project's  `package.json` to this.

```json
"scripts": {
  "build": "run-p build:*",
  "build:functions": "cd functions && npm run build",
  "firebase:deploy:functions": "firebase deploy --only functions",
  "firebase:start": "firebase emulators:start --only functions",
  "start": "run-p watch:* firebase:start",
  "watch:functions": "cd functions && npm run watch"
},
```

## Libraries Mentioned

- https://github.com/mailgun/mailgun-js
- https://www.npmjs.com/package/@types/mailgun-js
- https://github.com/handlebars-lang/handlebars.js
- https://github.com/mysticatea/npm-run-all

## Conclusion

This is just one way to send transactional emails from Firebase functions. To keep templates separate from code we had to precompile our email templates as Firebase functions can't deal with file system very well. I am sure there are other templating languages that support precompilation, but Handlebars is good enough for the task.

You can find complete example code on Github.

[https://github.com/codechips/firebase-functions-mailgun-handlebars-example](https://github.com/codechips/firebase-functions-mailgun-handlebars-example)

Thanks for reading!