Back to main menu

Building transactional email workflows for shipping notifications with Mailgun’s API

Customers expect real-time updates the moment their order moves. This guide walks you through building a reliable transactional email workflow for shipping notifications using Mailgun’s API and BullMQ, so your emails hit the inbox exactly when they should.

PUBLISHED ON

PUBLISHED ON

When you order something online, you expect to know exactly where it is and when it will arrive. Transactional email workflows help make this type of timely communication possible.

Unlike marketing emails that promote products, transactional emails are sent in response to a user's action, like placing an order or getting a shipping update. They provide real-time details that keep users in the loop every step of the way.

In this article, you'll learn how to build a transactional email workflow for shipping notifications using Mailgun's API. The workflow will incorporate BullMQ, a powerful job queue system, to queue and process email notifications separately from the main application logic and improve scalability and performance. This is useful for high-traffic applications where sending emails directly within a request-response cycle could slow down the system.

Implementing transactional email workflows for shipping notifications with Mailgun's API

To follow along, you need these tools and accounts:

Before diving into the implementation, let's take a look at the workflow:

transactional email workflows diagram

Here's how the process works:

  • The shipping team (or anyone managing shipping updates) sends an HTTP request to update the order status

  • The Express server updates the order status in the SQLite database and queues a job in BullMQ

  • A BullMQ worker processes jobs from the queue and interacts with the Mailgun API to send an email to the customer

  • The worker automatically retries failed jobs

  • Mailgun delivers the email to the customer

Mailgun account setup

To use the Mailgun API, you need a Mailgun domain and an API key for authentication. Mailgun provides two types of domains: sandbox domains for testing and custom domains for use in production environments.

In this guide, you'll use a sandbox domain. Each Mailgun account is automatically provisioned with one, so you don't have to create it manually. To obtain your sandbox domain, log in to your Mailgun account, then navigate to Send > Sending > Domains in the sidebar. Copy your domain:

Send > Sending > Domains Screen Image

By default, a sandbox domain can only send emails to authorized recipients. To set up an email as an authorized recipient, click your domain name on the Domains page to view its settings. On the Setup tab, under Add authorized recipients, add the email address that you want to send emails to. Make sure to follow the instructions sent to the email address you provided to set it up as an authorized recipient:

Add authorized recipients Screen Image

To obtain your API key, navigate to the API Keys page and click Add new key to create a new API key. Provide a name for the key and click Create Key, then make sure to copy the value of your key because it'll only be displayed once:

API Keys page Screen Image

Setting up the starter template

To keep this tutorial focused on building transactional email workflows, we've prepared a starter template for you to build on. Clone the template to your local machine by running the following command in your terminal:

git clone --single-branch -b starter-template https://github.com/kimanikevin254/mailgun-transactional-email-workflows.git

Here are the key files in the project:

  • src/controllers/order.controller.ts contains two methods:

    • index renders the admin UI, which is defined in src/views/admin.ejs

    • updateOrderStatus updates the order status in the database

  • src/database/entity/order.entity.ts defines the order entity

  • src/database/entity/user.entity.ts defines the user entity

  • src/database/seed.ts seeds the database with a sample user and order

  • docker-compose.yml runs Redis in Docker, which is required by BullMQ

Install all the dependencies using the following command:

npm install

Next, rename the .env.example file to .env and replace the placeholder values for MAILGUN_DOMAIN and MAILGUN_API_KEY with the credentials you obtained from the Mailgun dashboard. For the MAIL_FROM variable, provide your email address as the value.

Next, open the src/database/seed.ts file and replace the placeholder details in the userInfo constant with your details. For the email, make sure to provide one that you added as an authorized recipient.

Finally, seed the database by executing the command npm run db:seed in your terminal. You can now run the project using the command npm run dev and navigate to http://localhost:3000 in your browser to view and update the order status:

order status management screen image

In the next section, you'll implement a workflow that automatically sends an email to the user once the order status is updated.

Designing the email templates

Well-designed email templates improve readability, enhance user experience, and ensure your emails look professional across different devices. In this section, you'll create templates that make your transactional emails clear, engaging, and effective.

An order goes through three states (shipped, out for delivery, and delivered), as defined in the OrderStatus type in src/types/index.ts. You'll create a corresponding email template for each state to keep customers informed at every step.

To do this, create a file at src/email-templates/html/shipped.html and add the following code:

This email template is used to notify customers that their order has been shipped. It includes placeholders for the customer's name and tracking number, which will be dynamically filled by EJS when rendering the template. The templates for the other two order statuses will follow the same format.

Create a template to notify users that their order is out for delivery by creating a file named out_for_delivery.html in the src/email-templates/html folder and adding the following code:

Lastly, create a template to notify users that their order has been delivered by creating a file named delivered.html in the src/email-templates/html folder and adding the following code:

To manage and render email templates dynamically, you need to create a class that loads and organizes templates for different order statuses. This class will retrieve the right template and render it with customer-specific data. To do this, create a new file named index.ts in the src/email-templates folder and add the following code:

This class initializes a private variable named templates that stores email templates in a Map for quick lookup based on order status. Using a Map ensures efficient retrieval and avoids repeatedly reading files from disk. The class also defines the following methods:

  • loadTemplates (private) reads HTML templates from files and stores them in the templates Map, along with their corresponding subject lines.

  • getTemplate retrieves the email template for a given order status and throws an error if no template is found.

  • renderTemplate uses EJS to dynamically render a template with provided data.

Implementing the email-sending functionality

With the email templates set up, the next step is to implement the functionality for sending notifications. This involves queuing email jobs and processing them asynchronously.

But before you can do this, you need to install a few dependencies by executing the following command:

npm i mailgun.js@11.1.0 form-data@4.0.2 bullmq@5.41.2 ioredis@5.5.0

Here's an overview of each of these dependencies:

  • mailgun.js is a Node.js client for interacting with the Mailgun API. You'll use this to send transactional emails.

  • form-data is a package required by mailgun.js to handle form submissions when sending emails.

  • bullmq is a job queue library for handling asynchronous tasks. This allows you to queue email notifications and process them in the background.

  • ioredis is a Redis client for Node.js used by BullMQ to store and manage queued jobs.

Next, you need to define a Mailgun API client that you'll use to send emails. To do this, create a new file at src/config/mailgun.config.ts and add the following code:

This code sets up and exports a Mailgun client using the mailgun.js library and form-data, allowing your application to send emails through the Mailgun API with the API key stored in environment variables.

Now, you need to define a Redis connection that will be used by BullMQ to store and manage background jobs. To do this, create a new file named redis.config.ts in the src/config folder and add the code below:

You need to set up a BullMQ queue for the shipping notifications that will handle background email notification jobs using the Redis connection you defined in the previous step. To do this, create a new file named queues.config.ts in the src/config folder and add the following code:

This code also defines names for the queue and job type that can be used in other parts of the application to avoid hard-coded strings.

Lastly, create a new file at src/services/notifications.service.ts and add the following code:

This code defines a NotificationService class that handles email notifications using a job queue system with BullMQ and Mailgun. This class initializes three private properties: queue, which is the queue where email jobs are added; mailgunClient, which is the Mailgun client for sending emails; and templateManager, which is an instance of the TemplateManager class that you created previously to manage and retrieve email templates. This class also defines the following methods:

  • setupWorker creates a worker that listens for new email jobs and processes them. It logs job completion or failure.

  • processNotification fetches the correct email template, populates it with dynamic data, and sends the email using Mailgun.

  • queueNotification adds a new email job to the queue, with retry attempts and exponential backoff for failed attempts. This method can be called from other parts of the application to add jobs to the queue.

Integrating with application events

To send email notifications when an order status is updated, you need to queue a job in the notification system. To do this, open the src/controllers/order.controller.ts file and add the following import statement to import the NotificationService class:

import { NotificationService } from "../services/notifications.service";

Next, define a private property inside the OrderController class to hold an instance of the NotificationService class:

private notificationService: NotificationService;

Initialize this property by adding the following code inside the constructor method:

this.notificationService = new NotificationService();

Lastly, add the following code inside the updateOrderStatus method after await this.orderRepository.save(order); to queue a job once the order status is successfully updated:

The transactional email workflow is now complete. You can go ahead and test if everything is working as expected.

Testing the workflow

To ensure everything is working correctly, start Redis by running the following command in your terminal:

docker compose up -d

After running this command, you can verify that Redis is up and running by listing the containers:

docker ps

This command shows a list of active containers, and you should see the redis container running.

Next, run the Express server using the command npm run dev and navigate to http://localhost:3000 in your browser. On the application's dashboard, update the order status to any of the available options. Once updated, you'll receive an instant response:

Shipped Order Status Screen Image

In your terminal, you'll get a log that notifies you that the email-sending job has been queued successfully and is being processed:

Job queued successfully Processing job: 1

And when it's done:

Job 1 completed successfully

You'll then receive an email with the updated order status:

Your order has shipped screen image

Every time you update the status of an order, the system will automatically send a corresponding email to the user, keeping them informed:

Your Order has been delivered screen image 8

The full application code is available on GitHub.

Monitoring and managing emails

To ensure your transactional emails are reaching customers effectively, you need to monitor their delivery status and open rates and handle any failures. Mailgun provides a dashboard where you can track sent emails, check if they were delivered, opened, or clicked, and view bounce reports.

You can access this dashboard by navigating to Send > Reporting > Metrics in Mailgun:

Send > Reporting > Metrics Screen Image

However, open rates aren't tracked automatically, and you need to configure them manually via your sending domain's Settings tab:

Domain Settings Screen Tab image

More information on how to track open and click rates is available in the official documentation.

Additionally, you can use webhooks to receive real-time notifications about failed deliveries and take appropriate action, such as retrying the email.

By actively monitoring your email performance, you can improve deliverability, reduce bounces, and ensure important notifications reach your customers.

Wrapping up

In this article, you learned how to set up a transactional email workflow for shipping notifications using Mailgun, BullMQ, and Redis. You learned how to set up Mailgun for sending emails, configure a job queue with BullMQ, and integrate it into an order update system to ensure customers receive real-time email notifications. This approach improves communication and enhances the overall customer experience.

Was this helpful? Be sure to subscribe to our newsletter for more tutorial content and deep dives like this one.

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 transactional email workflows for order confirmations with Mailgun’s API

Unlike marketing emails, transactional emails (such as order confirmations, shipping notifications, and password resets) are triggered by specific user actions and provide real-time updates on their interactions with your platform. They help build trust, cut down on support questions, and make for a smooth shopping experience. In this tutorial, you'll learn how to build a transactional email workflow for order confirmations using Mailgun's API.

Read More

Building transactional email workflows for password resets with Mailgun’s API

In this tutorial, you'll learn how to build a transactional email workflow for password resets with Mailgun's API.

Read More

Introducing Mailgun’s open-source MCP server

Accessing email data just got easier. With Mailgun’s open-source MCP server, developers can query email performance metrics using natural language—no dashboards, no complex API calls. Built on Model Context Protocol (MCP), this solution makes AI-powered email analytics...

Read More

Popular posts

Email inbox.

Email

5 min

Build Laravel 11 email authentication with Mailgun and Digital Ocean

Read More

Mailgun statistics.

Product

4 min

Sending email using the Mailgun PHP API

Read More

Statistics on deliverability.

Deliverability

5 min

Here’s everything you need to know about DNS blocklists

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