Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
10 Sep 2025 · 6 min read ·Article 94 / 110
Go

94. Case Study: Email Notification Microservice

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

Case Study: Email Notification Microservice

Email notifications are a crucial feature in many modern applications, whether for account verification, activity alerts, or important reminders to users. As system complexity grows, the monolithic pattern starts to struggle, especially when integrating external systems or meeting high scalability demands. This is exactly where the microservice architectural style offers a modular, flexible, and scalable solution.

In this article, we’ll work through a case study of building a microservice for email notifications. I’ll guide you through the architecture design, building the core service code, integrating a message queue, and a real-world simulation. Let’s start with the big picture.


Why Use a Microservice for Email Notifications?

There are several reasons why the microservice architecture is a good fit for this feature:

  1. Decoupling: Email delivery runs separately from the main process (for example, user registration or order handling). A failure in one process won’t affect the main system.
  2. Resilience: Queueing support ensures emails are still delivered if the mail server goes down or the service experiences spikes.
  3. Scalability: High traffic limited to the email notification service can be handled by scaling that microservice horizontally, without touching any other service.
  4. Maintainability: Email logic, templates, and auditing are easy to develop independently of the core domain.

Architecture Overview

How do the other services, the message broker, and the email notifier relate to one another? Take a look at the diagram below.

MERMAID
flowchart LR
    A(App Lain: User, Order, dsb) --|Kirim Event Notifikasi| B(Message Queue)
    B --|Dequeued| C(Microservice Notifikasi Email)
    C --|Kirim email| D[SMTP Server]
    C --|Update log status| E(DB Notifikasi)

Explanation:

  • A: Another service (for example, UserService) sends an event to the message broker (RabbitMQ, Kafka, Redis, etc.).
  • B: The message broker holds the email events as a buffer.
  • C: The email notification microservice picks up the event, sends the email, and writes a log to the database.
  • D/E: The external SMTP/email vendor, plus the DB used for status tracking.

Designing the Email Notification Schema

The following table can be used to track every email:

idemail_tosubjectbodystatuserror_messagecreated_atsent_at
1user@site.comAccount ActivationSuccessNULL2024-06-20 19:12:212024-06-20 19:12:23
2user2@site.comReset PasswordFailureSMTP timeout2024-06-20 19:14:10NULL

The status column can hold the values Pending, Success, or Failure.


Implementing the Email Microservice with Node.js & Express

Directory Structure

text
1microservice-email/
2├── src/
3│   ├── index.js
4│   ├── emailService.js
5│   ├── queueConsumer.js
6│   └── models/
7│       └── notification.js
8├── package.json
9└── README.md

Dependencies

json
1{
2  "dependencies": {
3    "express": "^4.18.2",
4    "amqplib": "^0.10.3",           // For RabbitMQ
5    "nodemailer": "^6.9.4",         // For SMTP Email
6    "sequelize": "^6.30.0",         // ORM
7    "mysql2": "^3.5.2"
8  }
9}

1. Creating the Email Service (emailService.js)

js
 1const nodemailer = require('nodemailer');
 2
 3const transporter = nodemailer.createTransport({
 4    host: 'smtp.mailprovider.com',
 5    port: 587,
 6    secure: false,
 7    auth: {
 8        user: 'your_email@provider.com',
 9        pass: 'password'
10    }
11});
12
13async function sendEmail(to, subject, body) {
14    const mailOptions = {
15        from: '"YourApp Notif" <your_email@provider.com>',
16        to,
17        subject,
18        html: body
19    };
20
21    return transporter.sendMail(mailOptions);
22}
23
24module.exports = { sendEmail };

2. Defining the Notification Model (models/notification.js)

js
 1// with Sequelize
 2const { Model, DataTypes } = require('sequelize');
 3const sequelize = require('../db');
 4
 5class Notification extends Model {}
 6
 7Notification.init({
 8  email_to: DataTypes.STRING,
 9  subject: DataTypes.STRING,
10  body: DataTypes.TEXT,
11  status: DataTypes.STRING,
12  error_message: DataTypes.STRING,
13  sent_at: DataTypes.DATE,
14}, { sequelize, modelName: 'notification' });
15
16module.exports = Notification;

3. Queue Consumer (queueConsumer.js)

js
 1const amqp = require('amqplib');
 2const { sendEmail } = require('./emailService');
 3const Notification = require('./models/notification');
 4
 5async function startConsumer() {
 6    const connection = await amqp.connect('amqp://localhost');
 7    const channel = await connection.createChannel();
 8    const queue = 'email_notification';
 9    await channel.assertQueue(queue);
10
11    channel.consume(queue, async (msg) => {
12        if(msg !== null) {
13            const { to, subject, body } = JSON.parse(msg.content.toString());
14            const notif = await Notification.create({
15                email_to: to, subject, body, status: 'PENDING', created_at: new Date()
16            });
17
18            try {
19                await sendEmail(to, subject, body);
20                notif.status = 'SUCCESS';
21                notif.sent_at = new Date();
22            } catch (err) {
23                notif.status = 'FAILURE';
24                notif.error_message = err.message;
25            }
26            await notif.save();
27            channel.ack(msg);
28        }
29    });
30}
31
32module.exports = { startConsumer };

4. Running the Microservice (index.js)

js
1const express = require('express');
2const { startConsumer } = require('./queueConsumer');
3const app = express();
4
5app.get('/healthz', (_, res) => res.send('OK'));
6
7app.listen(8001, () => console.log('Email Service running at 8001'));
8startConsumer();

Simulating Email Delivery

Suppose there’s a user registration service that wants to send an email to a user with this payload:

js
 1const amqp = require('amqplib');
 2
 3async function publishEmail(to, subject, body) {
 4    // Similar to the logic in the sender service
 5    const conn = await amqp.connect('amqp://localhost');
 6    const ch = await conn.createChannel();
 7    await ch.assertQueue('email_notification');
 8    ch.sendToQueue('email_notification', Buffer.from(JSON.stringify({to, subject, body})));
 9    setTimeout(() => conn.close(), 500);
10}
11
12// Send Activation Email
13publishEmail('user@site.com', 'Account Activation', '<b>Welcome!</b>');

As a result, the microservice will pull the message from the queue, send the email, and then update the status in the database.


Infrastructure: Failure & Scalability Scenarios

What happens if SMTP goes down, or traffic suddenly spikes?

  • Retry: With a queue, messages stay buffered and are automatically retried as soon as the service recovers.
  • Horizontal Scaling: Add more consumer instances (worker processes) to pull more workload from the queue (for example, with Docker/Kubernetes).

Summary and Best Practices

  • Isolating the notification microservice delivers strong decoupling and reliability.
  • Use a message broker (such as RabbitMQ or Kafka) for asynchronous delivery and failover.
  • Logging and monitoring of emails is vital, both for debugging and for delivery SLAs.
  • Avoid hardcoding SMTP credentials—always use environment variables.

Conclusion

This case study demonstrates just how powerful and maintainable an email notification microservice system can be when built in a modular, asynchronous way. With a few tweaks, this service can even be extended to SMS, Push Notifications, or other channels.

The microservice architecture is no silver bullet, but for use cases like the one above its advantages are very clear—especially in medium- to large-scale systems.

Start distributing your notifications, delegate the workload, and enjoy managing a system that becomes ever more scalable — one microservice at a time!


References:


What’s your experience implementing distributed notifications? Share it in the comments!

Related Articles

💬 Comments