Back to main menu


Delivering HTML emails with Mailgun-Go

In this tutorial, I will demonstrate how you can send HTML emails with embedded images with mailgun-go. Before we dive into the code, lets first define the problem space and how we can use Mailgun to enhance the user experience of our application.



In this tutorial, I will demonstrate how you can send HTML emails with embedded images with mailgun-go. Before we dive into the code, lets first define the problem space and how we can use Mailgun to enhance the user experience of our application.

Introducing Channel-stats

Channel-stats is a Slack bot for collecting statistics on messages sent to a Slack channel. In addition to collecting counts of emoji’s and links shared in the channel, it also performs sentiment analysis of the messages and provides a positive or negative score for the messages which can later be reviewed by users and graphed as a percentage of total messages.

Image of statistics collected from Slack bot "Channel-stats"

We want to expand on this capability with a weekly email report on the statistics of a channel to our users. Since our email will include graphed data, plain text emails would be pretty boring, instead, we want to send rich HTML email with graphs to our user’s inbox. To accomplish this we need to craft some HTML, with inline CSS and images to make it visually appealing.

HTML email

A lot has been written on the subject of sending HTML in emails but here are a few good rules to follow:

  • DO use inline CSS

  • DO use HTML TABLES for layout

  • DO use images (prefer .png)

  • DO inline images

  • DON’T use HTML5

  • DON’T use animation CSS

  • DON’T  link to an external stylesheet

  • DON’T use CSS styles in the HEAD

  • DON’T use javascript

  • DON’T use flash

It’s often not enough to follow the above rules as there are no established standards on how HTML in emails is rendered. If you are committed to having your emails rendered correctly on as many clients as possible, you might consider using a service like Litmus to build, preview, and test your email across a variety of clients. However, for our purpose and since channel-stats is an open source project, I’m keeping production costs low and using some free templates provided by Mailgun (I did get some help from one our UX/UI designers). The result looks like the following:

Channel report designed using Mailgun's free templates

Now that we have our HTML and CSS, we need to inline the CSS so the majority of email clients will render our email properly. There are a variety of online tools to accomplish this, however we recommend Dialect Premailer for this purpose.

The code

Since we want the email to be sent on a weekly basis we use a cron library to create a function that will run every Sunday night at midnight. Next, we need to generate the images that will go into our email. Channel-stats already uses go-chart to render .png chart images for the UI, so we can just adapt that for our purposes.

Additionally, when shipping our final project we don’t want to distribute HTML and CSS files separately from our final compiled golang binary, so channel-stats uses the go-bindata project to bundle HTML and CSS into a single channel-stats binary.

Now let’s take a look at the render code.

In the Start() method we iterate through all the channels the bot is a member of and generate a report for each channel, We then make a call to genHtml() which retrieves our HTML email as a template called templates/email.tmpl from our compiled asset store in the HTML package. We then run the template through golang standard HTML/template engine to produce the final HTML. Next genImage() calls the render function with the range of hours and the type of counter we want to retrieve from the data store. Once ReportData is complete, we pass the data to mail.Report() for delivery.

Now that we have our images and HTML, let’s pause and talk a little about HTML MIME and image encoding. MIME is the format which email bodies are encoded to when sent via the SMTP protocol. It is the format that allows email clients to encode HTML, attach and retrieve files and images in an email.

In order for our images to display properly in HTML, we have to encode the images into the MIME. For this we have 2 options: we could add the images as an attachment, or we could inline the images. The RFC on Content disposition says that inline indicates the entity should be immediately displayed to the user, whereas attachment means that the user should take additional action to view the entity. Since our images are to be displayed immediately to the user via HTML — we choose inline.

At this point, we could use any number of MIME libraries for golang to inline our images and generate the body of the email in MIME format, but with Mailgun, we don’t have to. Mailgun will generate the MIME for us and provides options to inline files and images via the public API.

Now that we know how to inline images into the MIME, we have to reference them from our HTML. To do this, we use the cid: prefix in our <img> tags. Such that if our inlined image is called most-active.png our image tag would be <img src="cid:most-active.png">

With our HTML ready, let’s look at how we send the email and images with mailgun-go.

First, we create a new instance of Mailgun using our domain name and API key in NewMailgunNotifier(). Next in the Report() method we call NewMessage() to craft an object to which we will add our HTML and images. Notice the text argument to NewMessage() is empty string. While it is possible to encode both plain text and HTML into the MIME message, we only provide HTML here because inline chart images would be useless to a text-only client. Next, we call SetHtml() and append our inline images via a read closer object which we create on the fly from our []byte buffer. Finally, we ship our crafted request to the mailgun API for MIME construction and delivery using the Send() method.


Hopefully, this tutorial has given some insight on how to deliver high-quality HTML based emails using mailgun-go and the Mailgun API. If you have feedback or find bugs in either project the complete code and library can be found below.

Interested in working at Mailgun? We’re hiring! And there are several dev positions available. Check out our current openings here.

Featured Webinar – Predictions & Resolutions: Sending in 2019

Miss out on this webinar at the beginning of 2019? Don't worry, we recorded it! Rewatch Nick and Natalie talk about some things that happened in email in 2018, and what they thought was ahead for 2019. Technical and marketing met in the middle on this one, and you can rewatch it here.

Mailgun's Predictions & Resolutions webinar banner featuring Nick Schafer and Natalie Hays

Sign Up

It's easy to get started. And it's free.

See what you can accomplish with the world’s best email delivery platform.

Related readings

Building out our email template features

Email templates are the first thing your recipients notice about your emails. Great emails speak for themselves in the ROI they generate and the engagements they drive, let alone...

Read more

What toasters and distributed systems might have in common

A few months ago we released automatic IP Warm Up, but we never got to talk about how it’s implemented. Today, we’re going to peek...

Read more

Introducing a cross-platform debugger for Go

We use Go for a lot of our server development here at Mailgun, and it’s great. Coming from Python, though, there is one thing I really...

Read more

Popular posts

Email inbox.

Build Laravel 10 email authentication with Mailgun and Digital Ocean

When it was first released, Laravel version 5.7 added a new capability to verify user’s emails. If you’ve ever run php artisan make:auth within a Laravel app you’ll know the...

Read more

Mailgun statistics.

Sending email using the Mailgun PHP API

It’s been a while since the Mailgun PHP SDK came around, and we’ve seen lots of changes: new functionalities, new integrations built on top, new API endpoints…yet the core of PHP...

Read more

Statistics on deliverability.

Here’s everything you need to know about DNS blocklists

The word “blocklist” can almost seem like something out of a movie – a little dramatic, silly, and a little unreal. Unfortunately, in the real world, blocklists are definitely something you...

Read more

See what you can accomplish with the world's best email delivery platform. It's easy to get started.Let's get sending
CTA icon