A Solution for SES Throttling
I have written many services that make use of external APIs. One of the biggest challenges that
engineers face when working with API’s is throttling backend resources to avoid hitting service limits.
With Amazon SQS triggers and AWS Lambda concurrency control, we can now offload those throttling
strategies. You manage the concurrency provisioning, and throttling of Lambda functions through the
Reserved Concurrency parameter.
According to AWS’s documentation regarding using SQS as an event source for Lambda functions, when AWS
throttles a Lambda function, messages return to the queue and become visible again. SQS and Lambda’s
concurrency setting work together, resulting in messages in the queue remaining in the queue until Lambda
instances are available to process them. This feature is useful. For example, a properly configured Lambda
function can control the flow of requests to a upstream API with service limits.
On a recent project, I implemented this solution for a client. For this project, the upstream API was
Amazon Simple Email Service (SES). Every SES account has its own
set of sending limits. These limits are:
- Sending quota - the maximum number of recipients that you can send email to in a 24-hour period.
- Maximum send rate - the maximum number of recipients that you can send email per second.
The solution I implemented was a serverless application that allows the client to send emails at a rate
much higher than the email per second-rated allowed for their SES account. The emails remain in a queue
until there are Lambda instances available to process them. I have released this application as an
open-source project. The source code for the project is available at https://gitlab.com/jzj/serverless-email-queue
Introduction
“Serverless Email Queue” is an application build using the AWS Serverless Application Model (SAM) framework. SAM is an
open-source framework for building serverless applications. “Serverless Email Queue” provides two
interfaces for sending email messages: an SQS message queue and an optional REST API. SQS sends messages to
a Lambda function that forwards the messages as emails to SES. SES sends the email to its final
destination.
Application Architecture
“Serverless Email Queue” makes use of both the “Scalable
Webhook” and “Frugal Consumer”
microservice architectures.
Sequence Diagrams


AWS Infrastructure Diagram

Build and Deployment Instructions
These instructions are for a Linux operating system. You can deploy this application from a Windows
system; however, you will need to use the equivalent Windows commands:
- Install the SAM CLI.
- Install Node.JS. Release 12.20.0 was used to build this project. However, the
latest recommended release should work.
- Clone the repository:
git clone https://gitlab.com/jzj/serverless-email-queue
- In a terminal/console window,
cd into the serverless-email-queue folder
that you extracted.`
- In the base of the repo, run the following commands:
1
2
3
4
5
6
7
|
export AWS_SDK_LOAD_CONFIG=1
export SAM_CLI_TELEMETRY=0
npm install
npm run test
npm run build
sam build --base-dir ./dist
sam deploy --guided
|
Usage
To send messages via the REST API, you need the following:
- The URL of the REST API.
- A valid API Gateway API key.
AWS SQS API
To send messages directly to the message queue, you need the following:
- The SQS queue URL.
- Installation and configuration of the AWS SDK.
- The AWS IAM permissions to publish to the SQS queue URL.
REST API
Sending Email Messages
The following are the possible fields of an email message:
-
from - The email address of the sender. All email addresses can be plain ‘sender@server.com’ or formatted ‘“Sender Name” sender@server.com’.
- to - Comma separated list or an array of recipients email addresses that will appear
on the To: field.
- subject - The subject of the email.
- text - The plaintext version of the message as a Unicode string.
- html - The HTML version of the message as a Unicode string.
The format of the SQS message must be a JSON encoded string. Here is an example:
1
2
3
4
|
{\"from\":\"foo@example.com\",
\"to\":[\"success@simulator.amazonses.com\"],
\"subject\":\"test #1\",
\"text\":\"just a test\"}
|
A PHP Code Example for using the REST API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
$senderEmail = 'foo@example.com';
$recipientEmails = ['success@simulator.amazonses.com'];
$subject = 'Test Message from Serverless Email Gateway API';
$textBody = 'This email was sent with Serverless Email Gateway API.' ;
$htmlBody = '<h1>Just a test</h1><p>This email was sent with Serverless Email Gateway API.</p>';
$apiUrl = 'https://0000000000.execute-api.us-east-1.amazonaws.com/Prod/send';
$apiKey = '0000000000000000000000000000000000000000';
foreach ($recipientEmails as $recipientEmail) {
try {
$message = array(
'from' => $senderEmail,
'to' => [$recipientEmail],
'text' => $textBody,
'html' => $htmlBody,
'subject' => $subject
);
$messageBody = json_encode($message);
$opts = array('http' =>
array(
'method' => 'POST',
'header' => array('Content-Type: application/json', 'x-api-key: '.$apiKey),
'content' => $messageBody
)
);
$context = stream_context_create($opts);
$result = file_get_contents($apiUrl, false, $context);
if (!$result) {
echo("Email queue via the API failed!\n".$result."\n");
throw new ErrorException('unknown error');
}
echo("Email queued via the API!\n".$result."\n");
} catch (Exception $e) {
echo("The email was not sent via the API.\nError message: ".$e->getMessage()."\n");
echo "\n";
}
}
|
A PHP Code Example for using the SQS Queue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
require 'vendor/autoload.php';
use Aws\Sqs\SqsClient;
use Aws\Exception\AwsException;
$client = SqsClient::factory(array(
'profile' => 'default',
'region' => 'us-east-1',
'version' => '2012-11-05'
));
$queueUrl = 'https://sqs.us-east-1.amazonaws.com/00000000000/EmailMessageQueue';
$senderEmail = 'foo@example.com';
$recipientEmails = ['success@simulator.amazonses.com'];
$subject = 'Test Message from Serverless Email Gateway';
$textBody = 'This email was sent with Serverless Email Gateway SQS.' ;
$htmlBody = '<h1>Just a test</h1><p>This email was sent with Serverless Email Gateway SQS</p>';
foreach ($recipientEmails as $recipientEmail) {
try {
$message = array(
'from' => $senderEmail,
'to' => [$recipientEmail],
'text' => $textBody,
'html' => $htmlBody,
'subject' => $subject
);
$messageBody = json_encode($message);
$params = [
'MessageBody' => $messageBody,
'QueueUrl' => $queueUrl
];
$result = $client->sendMessage($params);
echo("Email queued!\n".$result."\n");
} catch (AwsException $e) {
echo $e->getMessage();
echo("The email was not sent to the queue.\nError message: ".$e->getAwsErrorMessage()."\n");
echo "\n";
}
}
|
Summary
SES throttles API requests and will reject requests that exceed quota limits. “Serverless Email Queue”
is a serverless application that automatically throttles requests to SES. Therefore, the rate for sending
email messages to “Serverless Email Queue” can exceed the SES quota limits. “Serverless Email Queue” stores
backlogged messages in a message queue until the sending capacity is available. This capability is useful
for sending large amounts of email in a short time.
Additional Resources
- Posted on:
- January 5, 2021
- Length:
- 6 minute read, 1101 words
- Categories:
-
Serverless