From 133efb9a09190b03ca924acffa1b696e6bba6b4c Mon Sep 17 00:00:00 2001 From: dyuvaraaj <45474965+dyuvaraaj@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:19:04 -0700 Subject: [PATCH] Switch SendGrid Email Notification to AWS SnS (#191) * Switch SendGrid Email Notification to AWS SnS * Limit 14 calls max per second * Fix typo * Auto Format * Revert "Auto Format" This reverts commit 5733a59577b1d6df33d8ab4299c4160c92e29f46. * Fix Alignment Issue * Fix Dave review comments * Resolve formating issue * Update NotificationSystem/SendSubscriberEmail.cs Co-authored-by: Dave Thaler * Update NotificationSystem/SendSubscriberEmail.cs --------- Co-authored-by: Yuvaraj Co-authored-by: Dave Thaler --- NotificationSystem/NotificationSystem.csproj | 3 ++- NotificationSystem/README.md | 8 +++---- NotificationSystem/SendModeratorEmail.cs | 16 +++++++++---- NotificationSystem/SendSubscriberEmail.cs | 13 ++++++++--- NotificationSystem/Utilities/EmailHelpers.cs | 24 +++++++++++++------- 5 files changed, 43 insertions(+), 21 deletions(-) diff --git a/NotificationSystem/NotificationSystem.csproj b/NotificationSystem/NotificationSystem.csproj index f314a9f8..387e0fb4 100644 --- a/NotificationSystem/NotificationSystem.csproj +++ b/NotificationSystem/NotificationSystem.csproj @@ -4,11 +4,12 @@ v4 + - + diff --git a/NotificationSystem/README.md b/NotificationSystem/README.md index 3d0ab51a..42edc7e0 100644 --- a/NotificationSystem/README.md +++ b/NotificationSystem/README.md @@ -52,7 +52,7 @@ In the moderators flow: - A change in the Cosmos DB metadata store triggers the SendModeratorEmail function - If there is a newly detected orca call that requires a moderator to validate, the function fetches the relevant email list -- The function then calls SendGrid to send emails to moderators +- The function then calls AWS Simple Email Service to send emails to moderators In the subscribers flow: @@ -60,7 +60,7 @@ In the subscribers flow: - If there is a new orca call that the moderator has validated, the function sends a message to a queue - The SendSubscriberEmail function periodically checks the queue - If there are items in the queue, the function fetches the relevant email list -- The function then calls SendGrid to send emails to subscribers +- The function then calls AWS Simple Email Service to send emails to subscribers ## Get email list @@ -111,7 +111,6 @@ All resources are located in resource group **LiveSRKWNotificationSystem**. 1. Storage account with queues, email template images and moderator/subscriber list: orcanotificationstorage 2. Metadata store (from which some functions are triggered): aifororcasmetadatastore 3. Azure function app: orcanotification -4. SendGrid account (for sending emails): aifororcas ## Run Locally It is recommended to go to the "orcanotification" function app, then Settings > Configuration to find the app settings used. @@ -127,7 +126,8 @@ Create local.settings.json in the current directory (NotificationSystem) using t "OrcaNotificationStorageSetting": "", "aifororcasmetadatastore_DOCUMENTDB": "", - "SendGridKey": "", + "AWS_ACCESS_KEY_ID": "", + "AWS_SECRET_ACCESS_KEY": "", "SenderEmail": "" } } diff --git a/NotificationSystem/SendModeratorEmail.cs b/NotificationSystem/SendModeratorEmail.cs index d58cdee6..f176ef7d 100644 --- a/NotificationSystem/SendModeratorEmail.cs +++ b/NotificationSystem/SendModeratorEmail.cs @@ -1,4 +1,8 @@ using System; +using Amazon; +using Amazon.SimpleEmail; +using RateLimiter; +using ComposableAsync; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Table; @@ -7,13 +11,14 @@ using Microsoft.Extensions.Logging; using NotificationSystem.Template; using NotificationSystem.Utilities; -using SendGrid.Helpers.Mail; namespace NotificationSystem { [StorageAccount("OrcaNotificationStorageSetting")] public static class SendModeratorEmail { + static int SendRate = 14; + [FunctionName("SendModeratorEmail")] public static async Task Run( [CosmosDBTrigger( @@ -24,7 +29,6 @@ public static async Task Run( LeaseCollectionPrefix = "moderator", CreateLeaseCollectionIfNotExists = true)]IReadOnlyList input, [Table("EmailList")] CloudTable cloudTable, - [SendGrid(ApiKey = "SendGridKey")] IAsyncCollector messageCollector, ILogger log) { if (input == null || input.Count == 0) @@ -54,16 +58,18 @@ public static async Task Run( return; } - // TODO: make better email string body = EmailTemplate.GetModeratorEmailBody(documentTimeStamp, location); + var timeConstraint = TimeLimiter.GetFromMaxCountByInterval(SendRate, TimeSpan.FromSeconds(1)); + var aws = new AmazonSimpleEmailServiceClient(RegionEndpoint.USWest2); log.LogInformation("Retrieving email list and sending notifications"); foreach (var emailEntity in EmailHelpers.GetEmailEntities(cloudTable, "Moderator")) { - string emailSubject = string.Format("OrcaHello Candidate at location {0}", location); + await timeConstraint; + string emailSubject = string.Format("OrcaHello Candidate at location {0}", location); var email = EmailHelpers.CreateEmail(Environment.GetEnvironmentVariable("SenderEmail"), emailEntity.Email, emailSubject, body); - await messageCollector.AddAsync(email); + await aws.SendEmailAsync(email); } } } diff --git a/NotificationSystem/SendSubscriberEmail.cs b/NotificationSystem/SendSubscriberEmail.cs index eae3201b..25a97458 100644 --- a/NotificationSystem/SendSubscriberEmail.cs +++ b/NotificationSystem/SendSubscriberEmail.cs @@ -1,4 +1,8 @@ using System; +using Amazon; +using Amazon.SimpleEmail; +using RateLimiter; +using ComposableAsync; using System.Collections.Generic; using System.Text; using System.Threading.Tasks; @@ -10,20 +14,20 @@ using Newtonsoft.Json.Linq; using NotificationSystem.Template; using NotificationSystem.Utilities; -using SendGrid.Helpers.Mail; namespace NotificationSystem { [StorageAccount("OrcaNotificationStorageSetting")] public static class SendSubscriberEmail { + static int SendRate = 14; + [FunctionName("SendSubscriberEmail")] // TODO: change timer to once per hour (0 0 * * * *) public static async Task Run( [TimerTrigger("0 */1 * * * *")] TimerInfo myTimer, [Queue("srkwfound")] CloudQueue cloudQueue, [Table("EmailList")] CloudTable cloudTable, - [SendGrid(ApiKey = "SendGridKey")] IAsyncCollector messageCollector, ILogger log) { log.LogInformation("Checking if there are items in queue"); @@ -38,12 +42,15 @@ public static async Task Run( log.LogInformation("Creating email message"); var body = await CreateBody(cloudQueue); + var timeConstraint = TimeLimiter.GetFromMaxCountByInterval(SendRate, TimeSpan.FromSeconds(1)); + var aws = new AmazonSimpleEmailServiceClient(RegionEndpoint.USWest2); log.LogInformation("Retrieving email list and sending notifications"); foreach (var emailEntity in EmailHelpers.GetEmailEntities(cloudTable, "Subscriber")) { + await timeConstraint; var email = EmailHelpers.CreateEmail(Environment.GetEnvironmentVariable("SenderEmail"), emailEntity.Email, "Notification: Orca detected!", body); - await messageCollector.AddAsync(email); + await aws.SendEmailAsync(email); } } diff --git a/NotificationSystem/Utilities/EmailHelpers.cs b/NotificationSystem/Utilities/EmailHelpers.cs index 9c2473f0..dd51f5c6 100644 --- a/NotificationSystem/Utilities/EmailHelpers.cs +++ b/NotificationSystem/Utilities/EmailHelpers.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; using NotificationSystem.Models; -using SendGrid.Helpers.Mail; +using Amazon.SimpleEmail.Model; namespace NotificationSystem.Utilities { @@ -72,14 +72,22 @@ public static IEnumerable GetEmailEntities(CloudTable cloudTable, s return cloudTable.ExecuteQuery(query); } - public static SendGridMessage CreateEmail(string from, string to, string subject, string body) + public static SendEmailRequest CreateEmail(string from, string to, string subject, string body) { - var message = new SendGridMessage(); - message.AddTo(to); - message.AddContent("text/html", body); - message.SetFrom(from); - message.SetSubject(subject); - return message; + var email = new SendEmailRequest(); + email.Source = from; + email.Destination = new Destination(new List { to }); + //Create message and attach to email request. + Message message = new Message(); + message.Subject = new Content(subject); + message.Body = new Body(); + message.Body.Html = new Content + { + Charset = "UTF-8", + Data = body + }; + email.Message = message; + return email; } } } \ No newline at end of file