Send Emails using C# .NET with Azure Functions and SendGrid Bindings
Tags: dotnet, azure, sendgrid, csharp
Published: 2022-03-02 22:49:55

---
title: Send Emails using C# .NET with Azure Functions and SendGrid Bindings
published: true
date: 2022-03-02 00:00:00 UTC
tags: dotnet, azure, sendgrid, csharp
canonical_url: https://www.twilio.com/blog/send-emails-csharp-dotnet-with-azure-functions-and-sendgrid-bindings
cover_image: https://swimburger.net/media/komfbwsp/how-to-send-emails-with-c-net-azure-functions-using-sendgrid-bindings-canonical.png?crop=0.0079484559820199082,0,0.007872929920011304,0&cropmode=percentage&width=1000&height=420
---

**This blog post was written for [Twilio](https://www.twilio.com/) and [originally published at the Twilio blog](https://www.twilio.com/blog/send-emails-csharp-dotnet-with-azure-functions-and-sendgrid-bindings).**

Azure Functions has its own opinionated way of developing applications based on triggers, input bindings, and output bindings. Azure supports two Twilio products using output bindings: [Twilio Programmable Messaging](https://www.twilio.com/messaging) for sending SMS and [Twilio SendGrid](https://sendgrid.com/) for sending emails.

In this tutorial, you'll learn how to send emails with C# .NET using Azure Functions and SendGrid bindings.

If you also want to send SMS, follow this [guide on how to send SMS using Azure Functions](https://www.twilio.com/blog/send-sms-with-csharp-dotnet-and-azure-functions-using-the-twilio-output-binding)!

## Prerequisites

You will need these items to follow along:

- An OS that supports .NET (Windows/macOS/Linux)
- [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
- [Azure Functions Core Tools](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#install-the-azure-functions-core-tools)
- A code editor or IDE (Recommended: VS Code with [the C# plugin](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp), [Visual Studio](https://visualstudio.microsoft.com/), or [JetBrains Rider](https://www.jetbrains.com/rider/))
- [Node.js version 8.0 or later](https://nodejs.org/) (optional)

You can find [the source code for this tutorial on GitHub](https://github.com/Swimburger/AzureFunctionsWithSendGridBindings). Use the source code if you run into any issues, or [submit an issue](https://github.com/Swimburger/AzureFunctionsWithSendGridBindings/issues/new) on this GitHub repo, if you run into problems.

## Get Started with SendGrid

You can sign up for a SendGrid account in two different ways. Either sign up directly at [SendGrid's website](https://signup.sendgrid.com/), or create a [SendGrid account through the Azure Marketplace](https://azuremarketplace.microsoft.com/en-us/marketplace/apps/sendgrid.tsg-saas-offer?tab=PlansAndPrice).   
When you upgrade your SendGrid plan on Azure, the plan will be billed on your Azure Subscription, which is useful for consolidating your billing. 

Once you've created a SendGrid account, go to [your SendGrid dashboard](https://app.sendgrid.com/) and move on to the next section.

### Configuring your SendGrid account to send emails

There are two things you need to configure before you can send emails. First, you'll need to set up Sender Authentication. This will verify that you own the email address or domain that you will send emails from. Second, you'll need to create a **SendGrid API Key** with permission to send emails.

#### Sender Authentication

It is recommended to configure [**Domain Authentication**](https://docs.sendgrid.com/ui/account-and-settings/how-to-set-up-domain-authentication/), which requires you to add a record to your DNS host. To keep things simple, you will use **Single Sender Verification** for this tutorial instead. This will verify that you own the single email address that you want to send emails from. Single Sender Verification is great **for testing purposes** , but it is not recommended for production.

Twilio recommends Domain Authentication for production environments. An authenticated domain proves to Inbox Service Providers you own the domain, and removes the "via sendgrid.net" text that inbox providers would otherwise append to your from address.

Click on the **Settings** tab in the side menu. Once the settings tab opens, click on **Sender Authentication**.

Then, click **Get Started** under the **Single Sender Verification** section.

This will open a form on the right-side panel. Fill out the form with your information and email address.

Click **Create** after filling out the form. Another panel will appear on the right, asking you to confirm your email address. In the background, an email has been sent to the email address you entered.

Go to your personal email inbox, open the email from SendGrid, and click Verify Single Sender.

Your email address has been verified. You can now use it to send emails!

#### Create a SendGrid API key to send emails

Back on the SendGrid website, click on **API Keys** under the **Settings tab** , then click on **Create API Key** in the top right-hand corner. This will open another form in the right-side panel. Give your API Key a useful name. You can assign different permissions to the API Key. For optimal security, you should only give the minimum amount of permissions that you need. Next, click on Restricted Access.

Scroll down to the **Mail Send** accordion item and click on it to reveal the permissions underneath. Drag the slider to the right for the **Mail Send** permission.

Scroll to the bottom of the form and click on **Create & View**. The API key will now be displayed on your screen. You will not be able to retrieve the API key again once you leave this screen, so make sure you **copy the secret somewhere safe.**

With the sender verified and the API Key created, you're ready to set up your local Azure Functions environment.

## Preparing your local Azure Functions environment

You will need to prepare your development machine depending on the type of Azure Functions you will be developing. HTTP trigger based functions don't require any extra steps, but timer trigger based functions and other types of Azure Functions depend on Azure Storage. Luckily, Azure has developed a cross-platform and open-source storage emulator called [Azurite](https://github.com/Azure/Azurite). You'll need to install and run Azurite before you can develop Azure Functions locally.

There are [multiple ways to install Azurite](https://docs.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=npm), but one of the easiest ways is to install it as a global NPM package. This means you need to have [Node.js version 8.0 or later](https://nodejs.org/) installed on your machine.

To install Azurite as a global NPM package, run the following command:

```bash
npm install -g azurite
```

When you run Azurite, it will create several files and folders in the current directory. Given that, create a new directory and navigate to it to avoid accidentally polluting your current directory:

```bash
mkdir Azurite
cd Azurite
```

Run Azurite using the following command:

```bash
azurite
```

Leave Azurite running in your current shell and open a new shell for all future commands. Make sure the new shell is not opened inside the _Azurite_ folder.

## Create an Azure Function

It's time to start developing your Azure Functions. Run the following command in the new shell you opened in the previous step:

```bash
func init AzureFunctionsWithSendGridBindings --dotnet
```

This command will create a new folder _AzureFunctionsWithSendGridBindings_ and generate a .NET project for Azure Functions. Navigate to the new folder by running the command below:

```bash
cd AzureFunctionsWithSendGridBindings
```

This project does not contain any functions yet. Run the following command to generate a timer trigger based Azure Function.

```bash
func new --name SendEmailTimer --template "Timer trigger"
```

This will create a new C# file: _SendEmailTimer.cs_. Open the file and replace `0 */5 * * * *` with `*/15 * * * * *`. With that change, the _SendEmailTimer.cs_ file should now look like this:

```csharp
using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
 
namespace AzureFunctionsWithSendGridBindings
{
    public class SendEmailTimer
    {
        [FunctionName("SendEmailTimer")]
        public void Run([TimerTrigger("*/15 * * * * *")]TimerInfo myTimer, ILogger log)
        {
            log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
        }
    }
}

```

The `FunctionName` attribute marks the method as an Azure Function. This method will be triggered on a time schedule by using the `TimerTrigger` attribute in the parameter of the method. The argument passed into `TimerTrigger` is a [cron](https://en.wikipedia.org/wiki/Cron) expression that instructs the Azure Functions platform when to run the function. This particular cron expression occurs every 15 seconds.

The Azure Functions platform uses a 6-part CRON syntax:

```
* * * * * *
- - - - - -
| | | | | |
| | | | | +--- day of week (0 - 6) (Sunday=0)
| | | | +----- month (1 - 12)
| | | +------- day of month (1 - 31)
| | +--------- hour (0 - 23)
| +----------- min (0 - 59)
+------------- sec (0 - 59)
```

Azure Functions uses the [NCrontab library](https://github.com/atifaziz/NCrontab) to parse the cron expressions. You can use this handy [online CRON Expression validation tool](https://ncrontab.swimburger.net/) I made to verify when your Azure Functions will run based on your cron expression.

The details of the timer are passed into the method through the `myTimer` parameter. This parameter is an example of a very simple input binding, but input bindings can be a lot more powerful. 

The Azure Functions platform also injects a logger into the method because it is specified as the second parameter of the method. The order of the parameters doesn't really matter because the platform will use a combination of reflection and dependency injection to configure your Azure Functions and inject the necessary parameters.

The method itself only does one thing at the moment: logging a message with the current date and time.

Run the following command to run the Azure Functions project:

```bash
func start
```

Since this is a .NET project, you can use all the usual .NET CLI commands like `clean`, `restore`, `build`, etc., but you cannot run the project directly. You need to use the Azure Functions CLI to run the project as shown above.

The output of running the Azure Functions project should look like this:

```
Microsoft (R) Build Engine version 17.0.0+c9eb9dd64 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.
 
  Determining projects to restore...
  All projects are up-to-date for restore.
  AzureFunctionsWithSendGridBindings -> /Users/nswimberghe/AzureFunctionsWithSendGridBindings/bin/output/AzureFunctionsWithSendGridBindings.dll
 
Build succeeded.
    0 Warning(s)
    0 Error(s)
 
Time Elapsed 00:00:04.22
 
Azure Functions Core Tools
Core Tools Version: 4.0.3971 Commit hash: d0775d487c93ebd49e9c1166d5c3c01f3c76eaaf (64-bit)
Function Runtime Version: 4.0.1.16815
 
[2022-01-07T21:10:05.720Z] Found /Users/nswimberghe/AzureFunctionsWithSendGridBindings/AzureFunctionsWithSendGridBindings.csproj. Using for user secrets file configuration.
 
Functions:
 
	SendEmailTimer: timerTrigger
 
For detailed output, run func with --verbose flag.
[2022-01-07T21:10:12.070Z] Host lock lease acquired by instance ID '0000000000000000000000002511DF13'.
[2022-01-07T21:10:15.048Z] Executing 'SendEmailTimer' (Reason='Timer fired at 2022-01-07T16:10:15.0135530-05:00', Id=4098e2d1-5d27-4e5f-bd6f-540eeb3e9d67)
[2022-01-07T21:10:15.059Z] C# Timer trigger function executed at: 1/7/2022 4:10:15 PM
[2022-01-07T21:10:15.083Z] Executed 'SendEmailTimer' (Succeeded, Id=4098e2d1-5d27-4e5f-bd6f-540eeb3e9d67, Duration=54ms)
[2022-01-07T21:10:30.004Z] Executing 'SendEmailTimer' (Reason='Timer fired at 2022-01-07T16:10:30.0032640-05:00', Id=10e74f2c-4e6f-4117-bbba-118a6b595591)
[2022-01-07T21:10:30.004Z] C# Timer trigger function executed at: 1/7/2022 4:10:30 PM
[2022-01-07T21:10:30.005Z] Executed 'SendEmailTimer' (Succeeded, Id=10e74f2c-4e6f-4117-bbba-118a6b595591, Duration=1ms)
```

Every 15 seconds, a message looking like this should be logged: `C# Timer trigger function executed at: 1/7/2022 1:25:00 PM`.

Stop the project by pressing **control + c**.

Now that you have an Azure Function, you are ready to start integrating with SendGrid.

## Integrate Twilio SendGrid into your Azure Function

Azure Functions use the concept of triggers, input bindings, and output bindings. You've already encountered a trigger, the `TimerTrigger`, and an input binding of type `TimerInfo`. Now you're going to use your first output binding to send emails using [SendGrid bindings](https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-sendgrid?tabs=csharp).

Add the [SendGrid bindings NuGet package](https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.SendGrid) using the .NET CLI:

```bash
dotnet add package Microsoft.Azure.WebJobs.Extensions.SendGrid
```

This library is maintained by Microsoft, but the library itself depends on [SendGrid's C# .NET SDK](https://github.com/sendgrid/sendgrid-csharp) maintained by Twilio SendGrid.

Update _SendEmailTimer.cs_ with the following code:

```csharp
using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
using SendGrid.Helpers.Mail;
 
namespace AzureFunctionsWithSendGridBindings
{
    public class SendEmailTimer
    {
        [FunctionName("SendEmailTimer")]
        [return: SendGrid(ApiKey = "SendGridApiKey")]
        public SendGridMessage Run([TimerTrigger("*/15 * * * * *")]TimerInfo myTimer, ILogger log)
        { 
            log.LogInformation($"SendEmailTimer executed at: {DateTime.Now}");
 
            var msg = new SendGridMessage()
            {
                From = new EmailAddress("[REPLACE WITH YOUR EMAIL]", "[REPLACE WITH YOUR NAME]"),
                Subject = "Sending emails with Twilio SendGrid is Fun",
                PlainTextContent = "and easy to do anywhere, especially with C#",
                HtmlContent = "and easy to do anywhere, <strong>especially with C#</strong>"
            };
            msg.AddTo(new EmailAddress("[REPLACE WITH DESIRED TO EMAIL]", "[REPLACE WITH DESIRED TO NAME]"));
 
            return msg;
        }
    }
}
```

Update the placeholder strings:

- Replace `[REPLACE WITH YOUR EMAIL]` with the email address you verified with your SendGrid account.
- Replace `[REPLACE WITH YOUR NAME]` with your name.
- Replace `[REPLACE WITH DESIRED TO EMAIL]` with the email address you want to send an email towards.
- Replace `[REPLACE WITH DESIRED TO NAME]` with the recipient's name.

Save the _SendEmailTimer.cs_ file.

With these changes, the `Run` method will now create an instance of `SendGridMessage` which is then returned. `SendGridMessage` lets you specify the subject of the email, the text and HTML content of the email, and from which email address the email should be sent from. You can use the `SendGridMessage.AddTo` method to add one or more recipients to the email. However, creating this object does not actually send the email.

The Azure Functions platform will invoke the method, and when it receives the `SendGridMessage` instance, the platform will send the email. For the platform to be able to send the email, it needs to authenticate with the SendGrid API. 

This process is completed by the  `SendGrid` attribute on line 12. This attribute is applied to the **return** type, hence the `return` keyword preceding the attribute. The `ApiKey` parameter is used to specify the names of the application settings that hold the SendGrid API key.

With this information, the SendGrid binding will be able to connect to the SendGrid API and send your emails.

Now, before you can run the updated Azure Function, you need to configure the `SendGridApiKey` application setting. To do this, open the _local.settings.json_ file and add the `SendGridApiKey` setting to the `Values` object like below:

```json
{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS\_WORKER\_RUNTIME": "dotnet",
        "SendGridApiKey": "[REPLACE\_WITH\_YOUR\_SENDGRID\_API\_KEY]"
    }
}
```

Replace the `[REPLACE_WITH_YOUR_SENDGRID_API_KEY]` string with your API key that you created earlier, and save the file.

The API key is sensitive information, so be very careful not to share it or check it into git. Git should ignore the _local.settings.json_ file by default because of the _.gitignore_ file, but be careful nonetheless.

Start the Azure Functions project to test the project:

```bash
func start
```

The Azure Function should be invoked every 15 seconds, which will send an email using SendGrid.

**But what if you already have another output binding as a return value? Or what if you want to send two different emails?**

As an alternative to returning an instance of `SendGridMessage`, you can also add `out` parameters to your method. Here's an updated example:

```csharp
[FunctionName("SendEmailTimer")]
public void Run(
    [TimerTrigger("*/15 * * * * *")] TimerInfo myTimer,
    ILogger log,
    [SendGrid(ApiKey = "SendGridApiKey")] out SendGridMessage message1,
    [SendGrid(ApiKey = "SendGridApiKey")] out SendGridMessage message2
)
{
    log.LogInformation($"SendEmailTimer executed at: {DateTime.Now}");
 
    message1 = new SendGridMessage()
    {
        From = new EmailAddress("[REPLACE WITH YOUR EMAIL]", "[REPLACE WITH YOUR NAME]"),
        Subject = "Sending emails with Twilio SendGrid is Fun",
        PlainTextContent = "and easy to do anywhere, especially with C#",
        HtmlContent = "and easy to do anywhere, <strong>especially with C#</strong>"
    };
    message1.AddTo(new EmailAddress("[REPLACE WITH DESIRED TO EMAIL]", "[REPLACE WITH DESIRED TO NAME]"));
 
    message2 = new SendGridMessage()
    {
        From = new EmailAddress("[REPLACE WITH YOUR EMAIL]", "[REPLACE WITH YOUR NAME]"),
        Subject = "Sending emails with Twilio SendGrid is Fun",
        PlainTextContent = "Azure Functions and SendGrid makes sending emails easy!",
        HtmlContent = "<strong>Azure Functions and SendGrid</strong> makes sending emails easy!"
    };
    message2.AddTo(new EmailAddress("[REPLACE WITH DESIRED TO EMAIL]", "[REPLACE WITH DESIRED TO NAME]"));
}
```

Instead of applying the `SendGrid` attribute to the return type of the method, the attribute is now applied to the two `out SendGridMessage` parameters. Simply assign your `SendGridMessage` instance to your `out` parameter and the platform will receive the output and send the emails.

**How do I send multiple emails without specifying multiple `out` parameters?**

Alternatively, you can use the generic `ICollector<T>` type and `​​IAsyncCollector<T>` type as parameters of your method. The platform will inject an instance of the specified type, to which you can add any amount of instances of the generic type you specified. 

Here's an example:

```csharp
[FunctionName("SendEmailTimer")]
public void Run(
  [TimerTrigger("*/15 * * * * *")] TimerInfo myTimer,
  ILogger log,
  [SendGrid(ApiKey = "SendGridApiKey")]
  ICollector<SendGridMessage> messageCollector
)
{
  log.LogInformation($"SendEmailTimer executed at: {DateTime.Now}");
  
  for (int i = 1; i <= 2; i++)
  {
    var message = new SendGridMessage()
    {
        From = new EmailAddress("[REPLACE WITH YOUR EMAIL]", "[REPLACE WITH YOUR NAME]"),
        Subject = $"Email {i}: Sending emails with Twilio SendGrid is Fun ",
        PlainTextContent = "and easy to do anywhere, especially with C#",
        HtmlContent = "and easy to do anywhere, <strong>especially with C#</strong>"
    };
    message.AddTo(new EmailAddress("[REPLACE WITH DESIRED TO EMAIL]", "[REPLACE WITH DESIRED TO NAME]"));
 
    messageCollector.Add(message);
  }
}

```

Now the method accepts an instance of `ICollector<SendGridMessage>` to which you can add multiple `SendGridMessage` instances. The for loop will add two `SendGridMessage` instances, and the moment an instance is added using `messageCollector.Add`, the platform will send the email.

Alternatively, you can use `IAsyncCollector` instead of `ICollector`. The async version will not send emails immediately, but instead wait for `FlushAsync` to be called. If `FlushAsync` isn't called, the platform will send your emails after the method is finished.

Here's the async version:

```csharp
[FunctionName("SendEmailTimer")]
public async Task Run(
  [TimerTrigger("*/15 * * * * *")] TimerInfo myTimer,
  ILogger log,
  [SendGrid(ApiKey = "SendGridApiKey")]
  IAsyncCollector<SendGridMessage> messageCollector
)
{
  log.LogInformation($"SendEmailTimer executed at: {DateTime.Now}");
 
  for (int i = 1; i <= 2; i++)
  {
    var message = new SendGridMessage()
    {
      From = new EmailAddress("[REPLACE WITH YOUR EMAIL]", "[REPLACE WITH YOUR NAME]"),
      Subject = $"Email {i}: Sending emails with Twilio SendGrid is Fun ",
      PlainTextContent = "and easy to do anywhere, especially with C#",
      HtmlContent = "and easy to do anywhere, <strong>especially with C#</strong>"
    };
    message.AddTo(new EmailAddress("[REPLACE WITH DESIRED TO EMAIL]", "[REPLACE WITH DESIRED TO NAME]"));
 
    await messageCollector.AddAsync(message);
  }
 
  await messageCollector.FlushAsync();
}
```

Those are all the ways you can send emails using the Azure Functions SendGrid bindings. SendGrid binding is a helpful feature, but if it doesn't fulfill your requirements, you can still use the [SendGrid's C# .NET SDK](https://github.com/sendgrid/sendgrid-csharp) directly as with any other .NET application.

If you want to learn how to send email without the SendGrid bindings, read [how to Send Emails with C# and .NET 6 using the SendGrid API here](https://www.twilio.com/blog/send-emails-using-the-sendgrid-api-with-dotnetnet-6-and-csharp).

## Sending emails with SendGrid and Azure Functions

Azure Functions uses the concept of triggers, and input and output bindings. Microsoft maintains SendGrid bindings for Azure Functions, which helps you send emails using the Twilio SendGrid API. 

There are multiple ways to send emails using Azure Functions:

- Return a `SendGridMessage` instance
- Assign a `SendGridMessage` instance to an `out` parameter
- Let the platform inject an instance of `ICollector<SendGridMessage>` or `IAsyncCollector<SendGridMessage>` and add instances of `SendGridMessage` to it
- Use the [SendGrid's C# .NET SDK](https://github.com/sendgrid/sendgrid-csharp) directly

Emails aren't the only thing you can send using Azure Function, learn [how to send SMS with C# .NET using Azure Functions and the Twilio Binding](https://www.twilio.com/blog/send-sms-with-csharp-dotnet-and-azure-functions-using-the-twilio-output-binding).

## Additional resources

Check out the following resources for more information on the topics and tools presented in this tutorial:

- [SendGrid bindings for Azure Functions](https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-sendgrid?tabs=csharp) – There is additional information about the SendGrid bindings at Azure's documentation.
- [Twilio binding for Azure Functions](https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-twilio?tabs=csharp) – In addition to sending emails using the SendGrid bindings, you can send SMS using the Twilio binding. 
- [Deploy your Azure Function project to Azure](https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-csharp?tabs=azure-cli%2Cin-process#deploy-the-function-project-to-azure) – Your Azure Functions work locally, but you probably want to deploy them somewhere. Check out the Microsoft Docs to learn how to deploy your Azure Function project to Azure.
- [Source Code to this tutorial on GitHub](https://github.com/Swimburger/AzureFunctionsWithSendGridBindings) - Use this source code if you run into any issues, or submit an issue on this GitHub repo if you run into problems.

_Niels Swimberghe is a Belgian American software engineer and technical content creator at Twilio. Get in touch with Niels on_ [_Twitter @RealSwimburger_](https://twitter.com/RealSwimburger) _and follow Niels' personal blog on .NET, Azure, and web development at_ [_swimburger.net_](https://swimburger.net/)_._