2
votes

The Problem:

So we are building a newsletter system for our app that must have a capacity to send 20k-40k emails up to several times a day.

Tools of Preference:

  1. Amazon SES - for pricing and scalability
  2. Azure Functions - for serverless compute to send emails

Limitations of Amazon SES:

  1. Amazon SES Throttling having Max Send Rate - Amazon SES throttles sending via their services by imposing a max send rate. Right now, being out of the sandbox SES environment, our capacity is 14 emails/sec with 50K daily emails cap. But this limit can be increased via a support ticket.

Limitations of Azure Functions:

  1. On a Consumption Plan, there's no way to limit the scale as to how many instances of your Azure Function execute. Currently the scaling is handled internally by Azure, and thus the function can execute between just a few to hundreds of instances.

  2. From reading other post on Azure Functions, there seems to be "warm-up" period for Azure Functions, meaning the function may not execute as soon as it is triggered via one of the documented triggers.

Limitations of Azure Functions with SES:

  1. The obvious problem would be Amazon SES throttling sending emails from Azure functions because the scaled execution of Azure Function that sends out an email will be much higher than allowed send rate for SES.

  2. Due to "warm-up" period of Azure Function messages may end up being piled up in a queue before Azure Function actually starts processing them at a scale and sending out the email, thus there's a very high probability of hitting that send/rate limit.

Question:

  1. How can we utilize sending emails via Azure Functions while still being under X emails/second limit of SES? Is there a way to limit how many times an Azure Function can execute per time frame? So like let's says we don't want more than 30 instances of Azure Function running per/second?

Other thoughts:

  1. Amazon SES might not like continuous throttling of SES for a customer if the customer's implementation is constantly hitting up that throttling limit. Amazon SES folks, can you please comment?

  2. Azure Functions - as per documentation, the scaling of Azure Functions on a Consumption Plan is handled internally. But isn't there a way to put a manual "cap" on scaling? This seems like such a common requirement from a customer's point of view. The problem is not that Azure Functions can't handle the load, the problem is that other components of the system that interface with Azure Functions can't handle the load at the massive scale at which Azure Functions can handle it.

Thank you for your help.

2
It's really going to be a custom system and tightly integrate with our CMS. So we don't want to rely on external components. "Billions of emails" sounds very catchy...again Amazon SES will throttle you. We are also a .NET / Azure shop, would prefer to work with other cloud providers via .NET APIs only. Thanks for a suggestion though.CloudDev

2 Answers

1
votes

If I understand your problem correctly, the easiest method is a custom queue throttling solution.

Basically your AF just retrieve the calls for all the mailing requests, and then queue them into a queue system (say ServiceBus / EventHub / IoTHub) then you can have another Azure function that runs by x minute interval, which will pull a maximum of y messages and push it to SES. Your control point becomes that clock function, and since the queue system will help you to ensure you know your message delivery status (has sent to SES yet) and you can pop the queue once done, it would allow you to ensure the job is eventually processed.

0
votes

You should be able to set the maxConcurrentCalls to 1 within the host.json file for the function; this will ensure that only 1 function execution is occurring at any given time and should throttle your processing rate to something more agreeable from AWS perspective in terms of sends per second:

host.json

{
    // The unique ID for this job host. Can be a lower case GUID
    // with dashes removed. When running in Azure Functions, the id can be omitted, and one gets generated automatically.
    "id": "9f4ea53c5136457d883d685e57164f08",
    // Configuration settings for 'serviceBus' triggers. (Optional)
    "serviceBus": {
      // The maximum number of concurrent calls to the callback the message
      // pump should initiate. The default is 16.
      "maxConcurrentCalls": 1,
      ...