You are viewing our old blog site. For latest posts, please visit us at the new space. Follow our publication there to stay updated with tech articles, tutorials, events & more.

Email Sending Architecture Using Messaging Queue

3.67 avg. rating (77% score) - 3 votes

Emails are really playing a very important role, when it comes to keep users updated. We at send mails to update users about various activities related to them. For example, we send mails when there are new jobs on the site according to user’s custom job alerts. Job seekers are notified via email when there is any update on their job applications. Also we send mails composed by recruiters for intended job seekers and many more.

Basically there are two types of mails:

  1. Transactional Mails – which are triggered based on action of a job seeker or a recruiter.
  2. Bulk Mails – which are triggered to send updates to users based on different features to which users are subscribed to.

We send around 25 Million mails every day to our users. For such a big number, we need such an architecture to send mails in near real-time. So here is our mail sending architecture.

Email Sending Architecture

We have implemented an asynchronous system to send mails that has three separate sub processes for sending mails –

  • Mailer Data Generation Process
  • Mail Template Creation Process
  • Mail Sending Process
Asynchronous Email Sending Architecture

Email Sending Architecture

Below diagram shows the high level architecture of our central mailing system.

Whole process from start to end:

  • API:
    System has separate rest API each for bulk and transactional mails. API for bulk mails task is to implement validation on request and save it to database for further processing. API for transactional mails task is again to implement validation and push the request directly to queue.
  • Expand Worker:
    Expand workers are for bulk requests which are stored in database. Bulk request can be like, if same mail has to be sent to multiple recipients. Worker expands bulk request to single request for each recipient and push that to queue. It implements two phase commit between MySQL database and queue to ensure no mails are missed.It also applies hard bounce scrubbing. By which we mean not sending the emails to the email ids which had earlier bounced because of invalid email address.
  • Queue:
    For us each type of mail is high priority mail. So we have different queue for each type of mail. Queue is implemented on RabbitMq. Each message in queue contains the metadata required to creating mail body.
  • Mail Sending Worker:
    Mail Sending workers consume message from queue. Its task is to generate whole mail body from metadata present in message and send mail to the mail server. It then push the message to archive queue. Below is the detailed view of mail sending worker.
Detail View of Mail sending Worker

Detail View of Mail sending Worker

Whole mail sending process consists of three sub-process:

  1. Data generation process push message to next stage template creation queue.
    Template creation worker consumes message from its queue. Its task is to create the mail body using data generated in previous process and push it to final stage for sending mail.
  2. Message is pushed to Data generation queue from expand worker. These messages are consumed by data generation worker. This process is optional. We need this process if we require extra data to generate mail than currently present in message.
  3. Mail sending worker consumes message from its queue and send it to mail server and based on its response message is pushed to archive queue on success and error queue on failure.
  • Archive Worker:
    Archive worker consumes message from archive queue and stores the metadata of the mail sent to archive storage for future reference.

Here are the benefits that we were able to derive from this architecture.

  • We have been able to save un-necessary computation for sending the mails which have bounced earlier.
  • Using different queue for each type of mailer, has brought each type of mail to same level in terms of priority. There is no high or low priority.
  • Also breaking down the whole process into smaller units and executing them asynchronously had allowed us sending the same amount of mails with less number of workers instead of having a single worker for whole task.