Introduction
This article demonstrates how to send email from Azure Function using SendGrid. We will build functions that will push and pull email content from queue storage and send emails to recipient. To implement this end-to-end workflow, we are going to create two Azure functions:
- The first function is based on an HTTP trigger that will create a queue storage and push some dummy email content into it.
- The second function is based on a queue trigger that will process each queue item and read respective email content from it and send email to the recipient using SendGridMessage client.
Prerequisites
-
Basic knowledge of Azure Function.
- Azure storage account already created.
- .NET Core SDK 3.1 installed in your PC.
- Azure storage emulator installed. This is available as part of the Microsoft Azure SDK. Check here for more details.
HTTP trigger function to push messages into the queue (first function)
The purpose of this function is to push some dummy email content into a queue so that when the queue trigger will happen later by another function, that function will read the content from the queue.
NOTE
This function is optional. If you are able to create your own dummy JSON data into a queue called "email-queue", then you can skip this function to create it. But I would recommend creating this function which will help you to understand the end to end flow.
Below sequential steps will be performed by this function :
- Create a queue called “email-queue” if it doesn't exist in the storage account.
- Create a JSON object with a unique Id and HTML email content through loop. This is for just a demo email content. Based on your need, you can create static or dynamic HTML email content.
- Add that JSON object as a queue message into “email-queue”.
Let's create an empty Azure Function app project from Visual Studio.
Once a newly created solution is loaded, right-click on function project => Click on Add => Click on New Azure Function… => Give a function name as “UploadEmailMessagesHttpTriggerFunction.cs” => Click on Add => Select “Http Trigger” => Click on Create.
Now install the required NuGet packages in the project.
- Microsoft.Azure.WebJobs
- Microsoft.Azure.WebJobs.Extensions.Storage
Now we are ready to modify the default template of the function. Modify the Run method to add ExecutionContext which will be used to access storage connection string from JSON file or from function configuration settings on Azure.
UploadEmailMessagesHttpTriggerFunction.cs
- using System;
- using System.IO;
- using System.Threading.Tasks;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.Azure.WebJobs;
- using Microsoft.Azure.WebJobs.Extensions.Http;
- using Microsoft.AspNetCore.Http;
- using Microsoft.Extensions.Logging;
- using Newtonsoft.Json;
- using Microsoft.Azure.Storage.Queue;
- using Microsoft.Azure.Storage.Blob;
- using Microsoft.Azure.Storage;
- using Microsoft.Extensions.Configuration;
-
- namespace AzFunctions
- {
- public static class UploadEmailMessagesHttpTriggerFunction
- {
- [FunctionName("UploadEmailMessagesHttpTriggerFunction")]
- public static async Task<IActionResult> Run(
- [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
- ILogger log,
- ExecutionContext context)
- {
- log.LogInformation($"C# Http trigger function executed at: {DateTime.Now}");
- CreateQueueIfNotExists(log, context);
- for (int i = 1; i <= 5; i++)
- {
- string randomStr = Guid.NewGuid().ToString();
- var serializeJsonObject = JsonConvert.SerializeObject(
- new {
- ID = randomStr,
- Content = $"<html><body><h2> This is a Sample HTML content {i}! </h2></body></html>"
- });
-
- CloudStorageAccount storageAccount = GetCloudStorageAccount(context);
- CloudQueueClient cloudQueueClient = storageAccount.CreateCloudQueueClient();
- CloudQueue cloudQueue = cloudQueueClient.GetQueueReference("email-queue");
- var cloudQueueMessage = new CloudQueueMessage(serializeJsonObject, false);
-
- await cloudQueue.AddMessageAsync(cloudQueueMessage);
- }
-
- return new OkObjectResult("UploadEmailMessagesHttpTriggerFunction executed successfully!!");
- }
-
- private static void CreateQueueIfNotExists(ILogger logger, ExecutionContext executionContext)
- {
- CloudStorageAccount storageAccount = GetCloudStorageAccount(executionContext);
- CloudQueueClient cloudQueueClient = storageAccount.CreateCloudQueueClient();
- string[] queues = new string[] { "email-queue" };
- foreach (var item in queues)
- {
- CloudQueue cloudQueue = cloudQueueClient.GetQueueReference(item);
- cloudQueue.CreateIfNotExistsAsync();
- }
- }
- private static CloudStorageAccount GetCloudStorageAccount(ExecutionContext executionContext)
- {
- var config = new ConfigurationBuilder().SetBasePath(executionContext.FunctionAppDirectory)
- .AddJsonFile("local.settings.json", true, true)
- .AddEnvironmentVariables()
- .Build();
- CloudStorageAccount storageAccount = CloudStorageAccount.Parse(config["CloudStorageAccount"]);
- return storageAccount;
- }
- }
- }
Open local.settings.json and add CloudStorageAccount connection string which is required for testing from local.
This value you can find from your Azure storage account. For that, log in to Azure account => Go to storage account => Click on Access Keys under settings on left menu => You will see two keys there => Copy anyone of the connection sting => Paste that into local.settings.json file in your project.
- "Values": {
- "AzureWebJobsStorage": "UseDevelopmentStorage=true",
- "FUNCTIONS_WORKER_RUNTIME": "dotnet",
- "CloudStorageAccount": "DefaultEndpointsProtocol=https;AccountName=yourStorageAccountName;AccountKey=;EndpointSuffix=core.windows.net",
- "SendgridAPIKey": ""
- }
Let’s run the function and copy the HTTP URL from the function console window and paste it into the browser.
Once it is executed you will see the message “UploadEmailMessagesHttpTriggerFunction executed successfully!!”.
Let's verify the storage account, you will see queue created with a message ID and some sample messages are pushed into Queue.
Excellent!
Now publish the function app on right-clicking on the solution and click on Publish. In case you need any reference on how to publish the function app, please look at the section "Deploy Azure Function from Visual Studio" in
this article.
Once publish is successful from VS, you will see the newly created azure function on the Azure portal.
Now modify function configuration to add additional settings as “CloudStorageAccount” and copy the value from the storage account. Click on save.
Let's navigate to the function and click on “Click+Test” => Click on Test/Run to run it. Once it’s a success, you can cross-verify uploaded content on the container and queue from the storage account.
Awesome! We are done with our first function.
Sign up for a SendGrid account
To send email using SendGrid, we should have SendGrid API Keys. Create a free SendGrid account and create API Key from settings. This is required as part of your second function to operate.
In the Azure portal menu or the home page, select Create a resource.
- Search for and select SendGrid Accounts.
- Complete the signup form and select Create.
- Pricing tire default selected is Free, you can keep as it is.
- Enter contact details
- Once it’s created, click on manage to login to your sendgrid account
In your SendGrid dashboard, select Settings and then API Keys in the menu on the left.
Click on Create API Key => Give API Key Name => Click on create and view.
Now Copy the API Key value from SendGrid account and add that into local.settings.json under values as like below
- "Values": {
- "AzureWebJobsStorage": "UseDevelopmentStorage=true",
- "StorageConnectionString": "UseDevelopmentStorage=true",
- "FUNCTIONS_WORKER_RUNTIME": "dotnet",
- "CloudStorageAccount": "DefaultEndpointsProtocol=https;AccountName=;AccountKey=;EndpointSuffix=core.windows.net",
- "SendgridAPIKey": "Copy and paste the API Key value from SendGrid"
- }
Alright! Now we are ready to create a second function which will be a Queue trigger function.
Queue trigger function to pull messages from the queue and send an email (second function)
As I already mentioned, the purpose of this function is to process queue and read content from the queue and send emails as SendGridMessage.
To do this , Right click on project => Add new function => Name it as “SendGridEmailQueueTriggerFunction.cs” => Click on Add => Select Queue Trigger function type => Click on Create.
Install required NuGet packages into the function app project.
- Microsoft.Azure.WebJobs.Extensions.SendGrid
- Microsoft.Azure.WebJobs (Already installed as part of the first function)
- Microsoft.Azure.WebJobs.Extensions.Storage (Already installed as part of the first function)
Now delete the default content of the function and modify the function accordingly. In function parameter, specify the queue trigger name “
email-queue”, Connection as Storage account connection string name for reading the queue i.e. “
CloudStorageAccount” which we already added in local settings Json as well as azure function app configuration as part of first function configuration.
As we will use SendGrid as an email sending client, Add [SendGrid(ApiKey = "SendgridAPIKey")] out SendGridMessage sendGridMessage as additional prams - where we will set SendGrid API Key name and sendGridMessage object which we will set into the functions.
SendGridEmailQueueTriggerFunction.cs
- using System;
- using System.IO;
- using Microsoft.Azure.Storage;
- using Microsoft.Azure.Storage.Blob;
- using Microsoft.Azure.WebJobs;
- using Microsoft.Extensions.Configuration;
- using Microsoft.Extensions.Logging;
- using Newtonsoft.Json;
- using Newtonsoft.Json.Linq;
- using SendGrid.Helpers.Mail;
-
- namespace AzFunctions
- {
- public static class SendGridEmailQueueTriggerFunction
- {
- [FunctionName("SendGridEmailQueueTriggerFunction")]
- public static void Run([QueueTrigger("email-queue", Connection = "CloudStorageAccount")]string myQueueItem,
- [SendGrid(ApiKey = "SendgridAPIKey")] out SendGridMessage sendGridMessage,
- ILogger log)
- {
- log.LogInformation($"C# Queue trigger function processed: {myQueueItem}");
-
- try
- {
- var queueItem = myQueueItem.ToString();
-
- dynamic jsonData = JObject.Parse(queueItem);
- string emailBody = jsonData.Content;
-
- sendGridMessage = new SendGridMessage
- {
- From = new EmailAddress("[email protected]", "AzureFuncApps"),
- };
- sendGridMessage.AddTo("[email protected]");
- sendGridMessage.SetSubject("Awesome Azure Function app");
- sendGridMessage.AddContent("text/html", emailBody);
- }
- catch (Exception ex )
- {
- sendGridMessage = new SendGridMessage();
- log.LogError($"Error occured while processing QueueItem {myQueueItem} , Exception - {ex.InnerException}");
-
- }
- }
- }
- }
Make sure that you added “SendgridAPIKey" config value in local settings file.
Now you can test the function locally, if you want.
To publish the function app, right-clicking on the solution and click on Publish. Once the deployment is successful, you will see the newly created function in the Azure portal under the function app. Let's add a new application setting “SendgridAPIKey” into the Azure function app configuration.
Alright, now run the first function to push some data into the queue. If data are already there then the second function will automatically pick up queue messages and trigger email.
After the successful execution of the second function, you will see no queue messages are there in storage. That means all are processed.
Let’s check the mailbox where the email has been triggered!
Excellent!
We implemented email notification using the SendGrid account through the Azure queue trigger function.
Note
In case of any processing error during email sending, the queue item will retry 5 times. If it still failed, and then you will see a poison queue (called email-queue-poison) created and all unprocessed queue items were moved there.
Summary
In this article, we created an email queue though azure HTTP trigger function and pushed some HTML content as JSON object into the queue. And process the email queue through another queue trigger function that pulls email HTML content and sends an email to the recipient using the SendGrid message. Based on your need, you can read email addresses from blob content as well as like we did for email content. I hope you find this article useful!