Mailgun API + NodeJS + ExpressJS


Why?

Mailgun lets you measure the business impact of your email campaigns with a/b testing, using tags on your email templates for real-time analytics.

Know how your messages are rendering on different desktop clients like Outlook, Gmail, and more.

Today we’ll create sample API based on Node + Express for sending emails.

As a first step - create account on Mailgun.

https://signup.mailgun.com/new/signup

Project setup

Add dependencies as described and we’re ready to start!

{
  "dependencies": {
    "csv-parser": "^3.0.0",
    "dotenv": "^16.0.0",
    "express": "^4.17.3",
    "express-validator": "6.14.0",
    "form-data": "^4.0.0",
    "handlebars": "4.7.7",
    "jade": "^1.11.0",
    "js-yaml": "4.1.0",
    "mailgun.js": "^5.0.1"
  }
}

Email template

Create template.hbs file with html and add your message somewhere in that file:

<h1> { { message } } </h1>

Add your CSV file with random data:

email,name,surname
lola@gmail.com,Lola,Birkin
ivan@gmail.com,Ivan,Ivanov
petr@gmail.com,Petr,Petrov

Adjust your ENV variables in .env file:

API_KEY=XXXXXXXXXXXXXXXXXXXX
DOMAIN=XXXXXXXXXXXXXXXXXXXX

You can set bunch of configuration settings in config.yaml file, like this:

from: example@mail.com
subject: "Attention please!"
csv_file: "emails.csv"

Building application

You can see main application file app.js.

It’s super simple, but let’s go through main parts of this app:

  • /SEND POST endpoint, with this endpoint you can send email to specific email address (json payload)
  • /LIST POST endpoint, with this endpoint you can parse emails from CSV file, add them to the Maingun email list and send it (json payload)
  • Port and listener
require('dotenv').config();

const express = require('express');
// PREPARE MAILGUN CLIENT
const formData = require('form-data');
const Mailgun = require('mailgun.js');
const mailgun = new Mailgun(formData);
// INITIALIZE APP
const app = express();
// REQUIRE LIBRARIES
const { body, validationResult } = require('express-validator');
const yaml = require('js-yaml');
const bodyParser = require('body-parser');
const jsonParser = bodyParser.json();
const path = require("path");
const handlebars = require("handlebars");
const fs = require("fs");
const csv = require('csv-parser');
// PREPARE MAILGUN CLIENT
const apiKey = process.env.API_KEY;
const domain = process.env.DOMAIN;
const mailgunClient = mailgun.client({ username: 'api', key: apiKey || '' });
// READ CONFIG
const fileContents = fs.readFileSync('config.yaml', 'utf8');
const CONFIG = yaml.load(fileContents);
// PREPARE TEMPLATE
const emailTemplateSource = fs.readFileSync(path.join(__dirname, "/template.hbs"), "utf8");
const template = handlebars.compile(emailTemplateSource)
const htmlToSend = template({message: "Hello"})
const fromWho = CONFIG.from;

app.post('/send', jsonParser, body('email').isEmail().withMessage('should be email'), async function (req, res) {
  const errors = validationResult(req);

  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  
  const recipient = req.body.email;

  const data = {
    from: fromWho,
    to: recipient,
    subject: CONFIG.subject,
    html: htmlToSend
  };

  try {
    await mailgunClient.messages.create(domain, data);
    return res.json({ status: 'ok', email: req.params.mail });
  } catch (error) {
    return res.json({ status: 'error', error: error });
  }
});

app.post('/list', jsonParser, body('listName').isLength({ min: 2 }).withMessage('should exist and be min 2 characters length'), async (req, res) => {
  const errors = validationResult(req);

  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  
  const listName = req.body.listName;
  const validListName = `${listName}@${domain}`;

  let mailingList = '';
  let members = [];

  try {
    mailingList = await mailgunClient.lists.get(validListName).catch(async (err) => {
      if (err.status === 404) {
        const createdMailingList = await mailgunClient.lists.create({ address: validListName });

        console.info(`New mailing list ${createdMailingList.address} was created`);
        return createdMailingList;
      }
      throw new Error(err);
    });

    const msgData = {
      from: fromWho,
      to: mailingList.address,
      subject: CONFIG.subject,
      html: htmlToSend
    };

    fs.createReadStream(CONFIG.csv_file)
    .pipe(csv())
    .on('data', async function(row) {
      try {
        members.push({
          address: row.email,
          name: row.name,
          subscribed: true
        });
        const message = `New member ${row.email} was added to mailing list: ${mailingList.address}`;
        console.info(message);
      }
      catch(err) {
        console.log(err);
      }
    })
    .on('end', async function() {
      await mailgunClient.lists.members.createMembers(mailingList.address, { members: members });

      try {
        await mailgunClient.messages.create(domain, msgData);
      } catch (error) {
        return res.json({ error: error });
      }

      return res.json({status: 'ok'});
    });
  } catch (error) {
    let transformedError = error;
    if (error.status === 400 && error.details) {
      transformedError = error.details;
    }
    return res.json({ error: transformedError });
  }
});

const port = 3030;
app.listen(port, () => {
  console.info(`server is listening on ${port}`);
});

Send one email request example

POST /send/

curl -i -H 'Accept: application/json' -d 'email=petr@gmail.com' http://localhost:3030/send

Response

{ "status": "ok" }

Create email list and send emails request example

POST /list/

curl -i -H 'Accept: application/json' -d 'listName=myList' http://localhost:3030/list

Response

{ "status": "ok" }

That’s it, super simple and easy. Stay tuned!