Building a scalable serverless mail redirection solution

Preamble

We use Facebook Login massively at PoweredLocal. In result, our customers get email addresses of thousands of WiFi users which, nowadays, may lead to some undesired results like massive, repetitive email campaigns.

So we decided to protect end-users from excessive correspondence and mask their email addresses so that all emails would pass through our system first. Then, we would be able to control volumes, as well as quality of email campaigns.

We came up with a serverless solution entirely based on AWS services.

The task

The task is simple — a WiFi visitor John has an email address of john@smith.com. John often goes to Proud Mary Coffee in North Melbourne and we want the coffee shop to be able to notify John of specials from time to time. What we don’t want however is the coffee shop to send 10 emails every day–telling about a new PoS system installed, a new haircut one of the baristas got or the latest Hario dripper.

So instead of passing john@smith.com to the coffee shop owners, we want to give them something like very-well-masked-email@poweredlocal.com, and we will act as a email proxy. If the coffee shop starts behaving bad (eg. sending way too many emails or too many Hario close up photos), we would like to be able to turn the email redirection off, temporarily or permanently.

The problems

We chose Amazon SES as our email solution but no matter who you choose — one of the issues, the bottlenecks will be your throttle rate (maximum send-out rate). Therefore, the solution has to be able to adjust the pace according to the current allowed rate.

Since you are going to send out the emails yourself, you must take care and take note of complaints and bounces. You don’t want your email provider to shut the access one morning because you ignored all the customer complaints.

The solution

Our solution almost looks like an AWS lego:

We assign “proxied” emails in the form of uuid@mailtumble.com (we called our solution MailTumble and we even made it open-source). MX is set to AWS SES service so all the emails will arrive to Amazon.

Every incoming email is saved in an S3 bucket, as well as processed by a Lambda function. This Lambda function verifies that the destination email exists and the email is ok to pass through (there haven’t been too many complaints from the final recipient, content doesn’t have viruses, etc).

If all checks pass, emails unique Id is passed to a SQS queue. We chose a queue here because we want to be able to send-out the messages synchronously, at any rate of our choice. We don’t want to send emails asynchronously using Lambda (or SNS) because we may hit the throttle rate very, very quickly.

Another Lambda function is triggered every minute by CloudWatch, it polls the queue to see if there are any emails to send out. It adjusts the pace (concurrency) according to the queue size and the current send-out rate limit. If the queue is empty, it will keep the number of workers at minimum. If the queue is swarming, this Lambda function will fork as many workers (God bless JavaScript promises) as possible.

After email is sent, SES will send us a message through SNS informing of the result — was it delivered, or did it bounce, or did the recipient mark it as spam? We will use another Lambda function to process these notifications.

Results

We ended up with:

  • 5 Lambda functions written in JavaScript (some people also call it Node.js) — 2 for receiving and sending out, 2 for API endpoints (user existence checks) and 1 for email notification processing;
  • 3 SNS topics and one 1 SQS queue;
  • 1 S3 bucket and 1 API gateway;
  • Zero servers of our own engaged. Zero DevOps efforts, yay!

The solution is scalable (theoretically to very high throughputs) and cost-efficient. If there are no emails coming at all — we will only pay for Lambda invocations of the sending out function and SQS polls associated with it (at the minimal rate).

Feel free to browse our solution on GitHub and ask any questions. If you want to contribute — even better!


About us

PoweredLocal is a Melbourne-based wi-fi innovation startup. We host PHP, Ruby and Node.js microservices behind our newly baked API gateway.


About author

Written by Denis Mysenko, Chief Technical Officer at PoweredLocal

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.