A simple, reliable and resilient architecture for sending emails with Queue-Based Load leveling pattern.
A web app needs to send out the emails via an SMTP Server.

There are 2 issues:
-
Un-scalable: If a large number of instances of the web app run concurrently, the data store might be unable to respond to requests quickly enough, causing requests to time out, be throttled, or otherwise fail.
-
Not resilient: If the SMTP Server was down, the web app cannot send out the emails. It does not have the ability to re-send the emails when the SMTP Server recovered and up again, so we will lose the emails in the downtime period of SMTP Server.
Refactor the solution and introduce a queue and a worker (a service which works as a subscriber to the queue) between the web app and the SMTP Server.
- The web app (email-service) posts a message containing the data required for sending email to a queue.
- The queue acts as a buffer, storing the message until it's retrieved by the worker.
- The worker (email-worker) retrieves the messages from the queue and processes them by calling SMTP Server for
sending out the email.
- If worker processes the message successfully (the email was sent successfully), the worker sends acknowledgement (ack) to the queue, then the queue will delete that message.
- If worker dies (or SMTP Server was timeout...) without sending an ack, the queue will understand that a message wasn't processed fully and will re-queue it. We could configure the maximum number of re-queue times.
- If the message exceeds the maximum of re-queue times, it will be sent to dead queue.
- We could check the message in the dead queue to find out why the message could not be processed successfully.
- In the case we would like to re-queue these messages again, we could simply move these message from the dead queue to the normal queue.
This solution provides the following benefits:
- It can help to maximize availability because delays arising in email-worker or SMTP Server won't have an immediate and direct impact on the web app (email-service), which can continue to post messages to the queue even when the worker isn't available or isn't currently processing messages.
- It can help to maximize scalability because both the number of queues and the number of workers can be varied to meet demand.
- It can help to improve the resilience because now the system have the ability to recover and send out the email
after SMTP Server failure and back to normal.
- In the case connection to SMTP Server is intermittent (server down in the very short period, connection timeout), the worker could easily retry.
- In the case the SMTP Server is down for a long time and then it backs to normal, all the messages in the downtime period will be stored in the dead queue, the user could easily move the messages from the dead queue to the normal queue, then the worker could process as normal.
In this repository, we provide 2 sample implementation for 2 queue services:
- RabbitMQ: main branch

- Azure Service Bus: azure-service-bus branch

Those above components are provided in this repository as following:
- Email-Service is built with Spring Boot, it exposes the API endpoint for sending out the email. When the user called this API, it will send the message to the queue.
- Email-Worker is built with Spring Boot, it subscribes to the queue to get the message, extract the info from the message to create the email to send to SMTP Server.
- Queue:
- RabbitMQ: you could use the local RabbitMQ which is provided as Docker container in docker-compose in this repository. Or you could use a cloud service of RabbitMQ - CloudAMQP (there is free plan for development also).
- Azure Service Bus: you need to input your correct connection string to your Azure Service Bus in the file application.properties. Follow steps in Azure Documentation to create a Service Bus queue and get the connection string.
- SMTP Server: we use fake SMTP Server as Docker container.
Note: RabbitMQ and SMTP Server are provided as docker-compose in this repository. Both RabbitMQ and fake SMTP Server have the Web Management UI so that you could easily see what happened in RabbitMQ and SMTP Server.
The setup development workspace process is simpler than ever with following steps:
- Install JDK 11.
- Install Docker for Desktop.
- Install Maven.
- Clone this project to your local machine.
- Open terminal and make sure you're at the root directory of this project, run the command
docker-compose up(this will automatically setup RabbitMQ and a fake SMTP Server for you).
That's all.
-
Email-service:
- Default port: 8081
- API Endpoint for sending out email: http://localhost:8081/emails (POST)
- Swagger UI: http://localhost:8081/swagger-ui/index.html
-
Email-worker:
- Default port: 8082
-
RabbitMQ:
- Web Management UI: http://localhost:15672/ (login with guest:guest)
-
Fake SMTP Server:
- Web Management UI: http://localhost:5080/
- Cloud design patterns - Azure Architecture Center | Microsoft Docs


