How to Bulk Email with C# and .NET: Zero to Hero
Tags: dotnet, csharp, email, sendgrid
Published: 2022-08-04 02:37:05

---
title: How to Bulk Email with C# and .NET: Zero to Hero
published: true
date: 2022-08-02 00:00:00 UTC
tags: dotnet, csharp, email, sendgrid
canonical_url: https://www.twilio.com/blog/bulk-email-with-csharp-and-dotnet
cover_image: https://swimburger.net/media/ssqhflp3/how-to-bulk-email-with-c-and-net.png?anchor=center&mode=crop&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/bulk-email-with-csharp-and-dotnet).**

In a [previous tutorial](https://www.twilio.com/blog/send-emails-using-the-sendgrid-api-with-dotnetnet-6-and-csharp), I shared how you can send individual emails using the SendGrid API. This use case is perfect for [transactional emails](https://sendgrid.com/resource/what-is-transactional-email/) where you send an email to a single recipient or a small number of recipients. But what if you need to send emails to a very large audience? Well, in this tutorial you'll learn how to send bulk emails using the SendGrid API and C# .NET. 

If you're not familiar with sending transactional emails using .NET, I advise you go through [the previous post about sending emails with C# and SendGrid first](https://www.twilio.com/blog/send-emails-using-the-sendgrid-api-with-dotnetnet-6-and-csharp).

## Prerequisites

Here’s what you will need to follow along:

- [Git CLI](https://git-scm.com/downloads)
- [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
- A code editor or IDE (I recommend [VS Code](https://code.visualstudio.com/Download) 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/))
- A Twilio SendGrid account. [Sign up for a SendGrid account here](https://signup.sendgrid.com/) to send up to 100 emails per day completely free of charge.
- You'll need an **Email Sender** configured in SendGrid to send emails from, and you'll need an **API key** with permission to send emails. Both of these steps can be found in the [Configuring your SendGrid account to send emails section in the previous tutorial](https://www.twilio.com/blog/send-emails-using-the-sendgrid-api-with-dotnetnet-6-and-csharp#configure-sendgrid).
- An email address for testing, preferably from an email service that supports [Subaddressing](https://en.wikipedia.org/wiki/Email_address#Subaddressing). 

Many email services like Gmail and Outlook allow you to put a `+` sign and any string before the `@` symbol like this `youremail+test123@gmail.com`. Emails sent to this type of address will still be delivered to the original email address. This feature is called [Subaddressing](https://en.wikipedia.org/wiki/Email_address#Subaddressing). By using subaddressing, you will be able to test sending multiple emails to "different" addresses, however, email services will still prevent large amount of emails from coming through even when using subaddressing.

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

## Send bulk email with C# .NET

As is common in software development, there are many solutions to a given problem, and the best solution depends on your use case; the same goes for sending email in bulk. This tutorial will walk you through all the different ways of sending bulk email, and you'll learn which to use for your specific use case.

### Setup

For this tutorial, I have provided a starter project. Open a shell and run the following commands to clone the project and navigate to the project folder:

```bash
git clone https://github.com/Swimburger/BulkEmail.git --branch start
cd BulkEmail
```

The project is a console application which currently only sends one email.  
The console app has the following C# files:

- _Program.cs_: This is the starting point of the application, it will configure everything and then send the emails.
- _SenderOptions.cs_: This file has the `SenderOptions` class which is used to store some configuration about the SendGrid Email Sender.
- _SubscriberRepository.cs_: This file has the `SubscriberRepository` class which is responsible for returning your imaginary subscribers, represented as `Person` objects. The `Person` class is provided by [the Bogus library](https://github.com/bchavez/Bogus) and the subscribers are fake data generated using the same library, for testing purposes. Let's pretend this class retrieves real data from a SQL database as it would look exactly the same from the outside.

Feel free to take a look at these C# files, but don't worry about them, because the only important C# file is _EmailSender.cs_. This file has the `EmailSender` class which you will update so that it sends bulk email.

This project does need some configuration which you will store as user secrets. Run the following commands:

```bash
dotnet user-secrets set SendGridApiKey [SENDGRID_API_KEY]
dotnet user-secrets set Sender:Email [SENDER_EMAIL]
dotnet user-secrets set Sender:Name '[SENDER_NAME]'
dotnet user-secrets set ToEmailTemplate '[YOUR_EMAIL_TEMPLATE]'
```

Replace:

- `[SENDGRID_API_KEY]` with the API key you created in SendGrid,
- `[SENDER_EMAIL]` with your Sender email address that you verified in SendGrid,
- `[SENDER_NAME]` with the name you want the recipients to see,
- `[YOUR_EMAIL_TEMPLATE]` with the email address you use to test. If you're using subaddressing, use this format: `youremail+{0}@gmail.com`. `{0}` will be replaced by the program with unique numbers. If you're not using subaddressing, use your email address without any special modifications.

Now your project should be ready. Try it out by running the project using this command:

```bash
dotnet run --SubscriberCount 5
```

Even though you specified `5` subscribers, you should only receive one email in your inbox. That's because the `EmailSender` class only sends one email at the moment.

Let's take a look at the _EmailSender.cs_ file:

```csharp
using System.Text.Encodings.Web;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;

namespace BulkEmail;

public class EmailSender
{
    private readonly SubscriberRepository subscriberRepository;
    private readonly ISendGridClient sendGridClient;
    private readonly ILogger<EmailSender> logger;
    private readonly HtmlEncoder htmlEncoder;
    private readonly SenderOptions sender;

    public EmailSender(
        SubscriberRepository subscriberRepository,
        ISendGridClient sendGridClient,
        IOptions<SenderOptions> senderOptions,
        ILogger<EmailSender> logger,
        HtmlEncoder htmlEncoder
    )
    {
        this.subscriberRepository = subscriberRepository;
        this.sendGridClient = sendGridClient;
        this.logger = logger;
        this.htmlEncoder = htmlEncoder;
        this.sender = senderOptions.Value;
    }

    public async Task SendToSubscribers()
    {
        var subscribers = subscriberRepository.GetAll();
        var subscriber = subscribers.First();

        var message = new SendGridMessage
        {
            From = new EmailAddress(sender.Email, sender.Name),
            Subject = "Ahoy matey!",
            HtmlContent = "Welcome aboard <b>friend</b> ⚓️"️,
        };
        message.AddTo(new EmailAddress(subscriber.Email, subscriber.FullName));
        
        var response = await sendGridClient.SendEmailAsync(message);
        if (response.IsSuccessStatusCode) logger.LogInformation("Email queued");
        else logger.LogError("Email not queued");
    }
}
```

The `EmailSender` receives a bunch of parameters via its constructor and stores them into fields. Some of these are unused right now but will be used later. The `SendToSubscribers` method is responsible for sending an email to all subscribers coming from the `SubscriberRepository`, but currently it only grabs the first subscriber and stores it in the `subscriber` variable.

The `SendToSubscribers` method then creates a `SendGridMessage` from your configured sender and adds the `subscriber` as the recipient. Next, the `SendGridMessage` is passed into the `SendEmailAsync` method which sends the email to the SendGrid API. The SendGrid API will queue the email and return an HTTP success status code in the response if successful. Lastly, the method logs whether the email was queued or not.

Currently, the program only emails one subscriber, however, the goal is to send an email to all subscribers. Let's fix that!

### Bulk email using loops

The first solution is to wrap the email code in a loop. In this example, you can use a `foreach` -loop like this: 

```csharp
var subscribers = subscriberRepository.GetAll();
foreach (var subscriber in subscribers)
{
    var message = new SendGridMessage
    {
        From = new EmailAddress(sender.Email, sender.Name),
        Subject = "Ahoy matey!",
        HtmlContent = "Welcome aboard <b>friend</b> ⚓️"️,
    };

    message.AddTo(new EmailAddress(subscriber.Email, subscriber.FullName));

    var response = await sendGridClient.SendEmailAsync(message);
    if (response.IsSuccessStatusCode) logger.LogInformation("Email queued");
    else logger.LogError("Email not queued");
}
```

Replace lines 34 to 47 in the _EmailSender.cs_ file with the above code and save the file. Use the following command to try it out with 5 subscribers, or however many you'd like to spam your own inbox with:

```bash
dotnet run --SubscriberCount 5
```

This is the easiest and most flexible solution, however, it is also the least performant solution. 

You could further optimize this solution using parallelization or using `Task.WhenAll`, however, keep in mind that you can send a maximum of 10,000 API requests per second as noted in [the v3 Mail Send API FAQ](https://docs.sendgrid.com/for-developers/sending-email/v3-mail-send-faq#are-there-limits-on-how-often-i-can-send-email-and-how-many-recipients-i-can-send-to). 

#### Customize the email per recipient

When you send emails in bulk by submitting a `SendGridMessage` for every recipient, you have complete control over every email. To customize the subject and email body, you can use any means necessary.

Here's an example that uses string interpolation to customize the subject and email body: 

```csharp
var message = new SendGridMessage
{
    From = new EmailAddress(sender.Email, sender.Name),
    Subject = $"Ahoy {subscriber.FirstName}!",
    HtmlContent = $"Welcome aboard <b>{htmlEncoder.Encode(subscriber.FullName)}</b> ⚓️"️
};
```

When you build HTML templates and embed user input, your application may become vulnerable to HTML injection attacks. To prevent HTML injection attacks, always encode user input. In the example above the subscriber's full name is HTML encoded using the `HtmlEncoder.Encode` method. For more details, read [_How to prevent email HTML injection in C# and .NET_](https://www.twilio.com/blog/prevent-email-html-injection-in-csharp-and-dotnet).

This works fine for a proof of concept, but in a real application you could use a template engine to generate your HTML. Check out this [article about rendering emails using Razor templates](https://www.twilio.com/blog/render-emails-using-razor-templating) to learn more.

### Bulk email using Personalizations

The loop solution sends a new API request for every subscriber, but there is actually a way to send many emails using a single API request.

The `SendGridMessage` class has a property called `Personalizations` which is of type `List<Personalization>`. Using these [personalizations](https://docs.sendgrid.com/for-developers/sending-email/personalizations), you can customize the message for specific recipients using properties like `From`, `Tos`, `Ccs`, `Bccs`, `Subject`, etc. However, you cannot override the `TextContent` or `HtmlContent` property from the `SendGridMessage`.

Take a look at this example:

```csharp
new SendGridMessage
{
    From = new EmailAddress(sender.Email, sender.Name),
    Subject = "Ahoy matey!",
    HtmlContent = "Welcome aboard <b>friend</b> ⚓️"️,
    Personalizations = new List<Personalization>
    {
        new Personalization
        {
            Tos = new List<EmailAddress>
            {
                new EmailAddress("jon@localhost", "Jon")
            }
        },
        new Personalization
        {
            Tos = new List<EmailAddress>
            {
                new EmailAddress("jill@localhost", "Jill"),
                new EmailAddress("jane@localhost", "Jane")
            },
            Subject = "Hello world!"
        }
    }
};
```

The `SendGridMessage` in the snippet above will send 2 emails because there are 2 personalizations. One email with the subject "Ahoy matey!" to jon@localhost, and 1 email with the subject "Hello world!" to jill@localhost and jane@localhost.

When you add multiple email addresses to the `Tos` property, each recipient can see and reply to all the other recipients. So Jill and Jane in the example above will be able to see and reply to each other. To avoid this you can create separate `Personalization` objects for each recipient. In fact, when you previously used the `SendGridMessage.AddTo` method, the method created a personalization for you.

You can have up to 1,000 recipients per `SendGridMessage`. `Tos`, `Ccs`, and `Bccs` across all personalizations all add up to the 1,000 limit. The maximum number of `Personalization` objects per `SendGridMessage` is also 1,000.

Since you can only have 1,000 recipients per message, you'll need to paginate over the subscribers. There is a `GetByPage(int pageSize, int pageIndex)` method in the `SubscriberRepository` to help with pagination. The following code will paginate over all subscribers by 1,000 subscribers at a time.

```csharp
var pageSize = 1_000;
var subscriberCount = subscriberRepository.Count();
var amountOfPages = (int) Math.Ceiling((double) subscriberCount / pageSize);

for (var pageIndex = 0; pageIndex < amountOfPages; pageIndex++)
{
    var subscribers = subscriberRepository.GetByPage(pageSize, pageIndex);
    ...
}
```

Even if you weren't limited by 1,000 recipients per message, it would still be a good idea to paginate over the subscribers to reduce the memory usage of your application. As the number of subscribers increases, your application would run out of memory when loading all subscribers into memory at once.

Now that you have your subscribers, you can create a `Personalization` object for every one of them. Here's how you can do this using a [LINQ query](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/query-syntax-and-method-syntax-in-linq): 

```csharp
var message = new SendGridMessage
{
    From = new EmailAddress(sender.Email, sender.Name),
    Subject = "Ahoy matey!",
    HtmlContent = "Welcome aboard <b>friend</b> ⚓️"️,
    
    // max 1000 Personalizations
    Personalizations = subscribers.Select(s => new Personalization
    {
        Tos = new List<EmailAddress> {new EmailAddress(s.Email, s.FullName)},
    }).ToList(),
};
```

After putting all the previous code snippets together, your `SendToSubscribers` method should look like this:

```csharp
public async Task SendToSubscribers()
{
    var pageSize = 1_000;
    var subscriberCount = subscriberRepository.Count();
    var amountOfPages = (int) Math.Ceiling((double) subscriberCount / pageSize);

    for (var pageIndex = 0; pageIndex < amountOfPages; pageIndex++)
    {
        var subscribers = subscriberRepository.GetByPage(pageSize, pageIndex);

        var message = new SendGridMessage
        {
            From = new EmailAddress(sender.Email, sender.Name),
            Subject = "Ahoy matey!",
            HtmlContent = "Welcome aboard <b>friend</b> ⚓️"️,
            
            // max 1000 Personalizations
            Personalizations = subscribers.Select(s => new Personalization
            {
                Tos = new List<EmailAddress> {new EmailAddress(s.Email, s.FullName)},
            }).ToList(),
        };

        var response = await sendGridClient.SendEmailAsync(message);
        if (response.IsSuccessStatusCode) logger.LogInformation("Email queued");
        else logger.LogError("Email not queued");
    }
}
```

You can lower the `pageSize` number to test pagination without sending 1,000 emails to your inbox.  
For example, for testing purposes, you could set the `pageSize` to 5 and the `SubscriberCount` to 20:

```bash
dotnet run --SubscriberCount 20
```

#### Customize the email per recipient

As I mentioned before, you can override the email subject in a `Personalization` object, but not the `HtmlContent` or `TextContent`. However, you can still customize the email body per recipient using [Substitution Tags](https://docs.sendgrid.com/for-developers/sending-email/substitution-tags).

First, add substitution tags to your content like this:

```csharp
HtmlContent = "Welcome aboard <b>-FullName-</b> ⚓️"️
```

Then, add substitutions to your `Personalization` objects like this:

```csharp
new Personalization
{
    ...
    // Substitutions data is max 10,000 bytes per Personalization object
    Substitutions = new Dictionary<string, string>
    {
        {"-FullName-", htmlEncoder.Encode(s.FullName)}
    }
}
```

The substitution tag is decorated with dashes (-) in this example, however, you can use any type of decoration characters, as long as the substitution tags in the `HtmlContent` and `TextContent` match with the keys of the `Substitutions` dictionary.

You can also use these substitution tags inside of your email subject. Putting this together, you can send personalized bulk emails like this:

```cshar
var message = new SendGridMessage
{
    From = new EmailAddress(sender.Email, sender.Name),
    Subject = "Ahoy -FirstName_Raw-!",
    HtmlContent = "Welcome aboard <b>-FullName-</b> ⚓️"️,
    
    // max 1000 Personalizations
    Personalizations = subscribers.Select(s => new Personalization
    {
        Tos = new List<EmailAddress> {new EmailAddress(s.Email, s.FullName)},
        // Substitutions data is max 10,000 bytes per Personalization object
        Substitutions = new Dictionary<string, string>
        {
            {"-FirstName_Raw-", s.FirstName},
            {"-FullName-", htmlEncoder.Encode(s.FullName)}
        }
    }).ToList(),
};
```

I chose to suffix the first name substitution tag with `_Raw` to indicate to myself and others that this variable is not HTML encoded and should not be used in HTML content, to avoid HTML injection.

### Send bulk email using Dynamic Email Templates

Thus far, you provided the email subject and body from code, however, you can also use [Dynamic Email Templates](https://sendgrid.com/solutions/email-api/dynamic-email-templates/). You can create these templates using the SendGrid UI or API, and then instead of specifying the subject and content in your `SendGridMessage`, you set the ID of your template to the `SendGridMessage.TemplateId` property. Dynamic Email Templates use [the Handlebars templating language](https://docs.sendgrid.com/for-developers/sending-email/using-handlebars), which allows you to do variable substitution, conditional rendering, looping, and more.

Implementing Dynamic Email Templates is out of the scope of this tutorial, however, here is what your `SendGridMessage` would look like to achieve the same result as before:

```csharp
var message = new SendGridMessage
{
    From = new EmailAddress(sender.Email, sender.Name),
    TemplateId = "d-0a664e681ed14d76bd452637a15b20ab",
    Personalizations = subscribers.Select(s => new Personalization
    {
        Tos = new List<EmailAddress> {new EmailAddress(s.Email, s.FullName)},
        TemplateData = new
        {
            FirstName = s.FirstName,
            FullName = s.FullName
        }
    }).ToList(),
};

```

Instead of using the `Substitutions` property on your `Personalization`, you set the `TemplateData` property with an object. The Dynamic Email Template will be able to access all the data from the `TemplateData` object.

Want an in depth guide? Read this article on how to [_Send Emails with C#, Handlebars templating, and Dynamic Email Templates_](https://www.twilio.com/blog/send-emails-with-csharp-handlebars-templating-and-dynamic-email-templates).

Since Dynamic Email Templates use the same `SendGridMessage` and `Personalization` classes, everything you learned about bulk email so far also applies here.

### Send bulk email using Single Sends in SendGrid Marketing Campaigns

SendGrid Marketing Campaigns are also out of scope for this tutorial, but for the sake of completeness, I will lightly touch on it. With SendGrid Marketing Campaigns you can create [signup forms](https://sendgrid.com/solutions/email-marketing/signup-forms/), manage contacts, organize contacts into lists and segments, create [email designs](https://sendgrid.com/solutions/email-marketing/email-design/), and more. 

And of course, you can send emails to those contacts stored in SendGrid.

You can manage all of the previous features using the SendGrid UI or API. For example, you can [manage contacts using the SendGrid UI](https://docs.sendgrid.com/ui/managing-contacts/create-and-manage-contacts) or [programmatically via the API](https://docs.sendgrid.com/api-reference/contacts/add-or-update-a-contact), and you can also send emails to your contacts, lists, and segments using the [Single Send API](https://docs.sendgrid.com/api-reference/single-sends/create-single-send). Since the contacts are all stored in SendGrid, you don't have to worry about any looping and paginating.

With the [Single Send API](https://docs.sendgrid.com/api-reference/single-sends/create-single-send) you can either provide the email body via the API or you can specify the ID of the email design stored in SendGrid. For either option, you cannot pass on data through the API to use in the email template. However, the templates have access to the data stored on the contacts. Once you created the Single Send, you can [schedule the Single Send](https://docs.sendgrid.com/api-reference/single-sends/schedule-single-send) to be sent "now" or up to 72 hours from now.

## Schedule your bulk email

When you're sending a lot of emails, SendGrid recommends scheduling them to be sent in the near future instead of sending them immediately. Quoting from the [SendGrid docs](https://docs.sendgrid.com/for-developers/sending-email/scheduling-parameters):

This technique allows for a more efficient way to distribute large email requests and can improve overall mail delivery time performance. This functionality:

- Improves efficiency of processing and distributing large volumes of email.
- Reduces email pre-processing time.
- Enables you to time email arrival to increase open rates.
- Is available for free to all SendGrid customers.

To schedule emails you can set the `SendAt` property on the `SendGridMessage` or `Personalization` object. The `SendAt` property is a `long` representing a Unix timestamp.

To calculate this Unix timestamp, you can use the `DateTimeOffset.ToUnixTimeSeconds()` method.

For example, this will generate the `long` timestamp for 5 minutes from now:

```csharp
DateTimeOffset.Now.AddMinutes(5).ToUnixTimeSeconds()
```

If you're using `DateTime` instead of `DateTimeOffset`, you can implicitly and explicitly cast a `DateTime` to a `DateTimeOffset`, like this:

```csharp
DateTimeOffset fiveMinutesFromNow = DateTime.Now.AddMinutes(5); // implicit cast
fiveMinutesFromNow = (DateTimeOffset) DateTime.Now.AddMinutes(5); // explicit cast
```

Check out [this Microsoft doc on converting between DateTime and DateTimeOffset](https://docs.microsoft.com/en-us/dotnet/standard/datetime/converting-between-datetime-and-offset) to learn more.

Here's the `SendToSubscribers` method for sending the Bulk Email 5 minutes from now:

```chsarp
public async Task SendToSubscribers()
{
    var sendAt = DateTimeOffset.Now.AddMinutes(5).ToUnixTimeSeconds();
    
    var pageSize = 1_000;
    var subscriberCount = subscriberRepository.Count();
    var amountOfPages = (int) Math.Ceiling((double) subscriberCount / pageSize);

    for (var pageIndex = 0; pageIndex < amountOfPages; pageIndex++)
    {
        var subscribers = subscriberRepository.GetByPage(pageSize, pageIndex);
        
        var message = new SendGridMessage
        {
            From = new EmailAddress(sender.Email, sender.Name),
            Subject = "Ahoy -FirstName_Raw-!",
            HtmlContent = "Welcome aboard <b>-FullName-</b> ⚓️"️,

            // max 1000 Personalizations
            Personalizations = subscribers.Select(s => new Personalization
            {
                Tos = new List<EmailAddress> {new EmailAddress(s.Email, s.FullName)},
                // Substitutions data is max 10,000 bytes per Personalization object
                Substitutions = new Dictionary<string, string>
                {
                    {"-FirstName_Raw-", s.FirstName},
                    {"-FullName-", htmlEncoder.Encode(s.FullName)}
                }
            }).ToList(),
            
            // max 72 hours from now
            SendAt = sendAt
        };

        var response = await sendGridClient.SendEmailAsync(message);
        if (response.IsSuccessStatusCode) logger.LogInformation("Email queued");
        else logger.LogError("Email not queued");
    }
}
```

You can schedule emails to be sent up to 72 hours from now. If you need to schedule beyond that time, I recommend [scheduling emails using a job scheduler like Quartz.NET](https://www.twilio.com/blog/how-to-send-recurring-emails-in-csharp-dotnet-using-sendgrid-and-quartz-net) or Hangfire.

## Unsubscribe recipients

When recipients mark your emails as spam, it hurts your reputation. As an email sender, you want to maintain a good reputation to maximize your email deliverability. You must also comply with anti-spam laws. Make sure to use a double opt-in process for things like newsletters, and always provide an unsubscribe link in your emails. You can add your own unsubscribe logic, or you can use [Global Unsubscribes](https://docs.sendgrid.com/ui/sending-email/global-unsubscribes) or [Group Unsubscribes](https://docs.sendgrid.com/ui/sending-email/group-unsubscribes) from SendGrid.

## MailHelper

The SendGrid SDK contains a helper class called `MailHelper`. `MailHelper` has convenient static methods for the most common email scenarios, including bulk email. 

The `CreateSingleEmailToMultipleRecipients` method lets you conveniently create a `SendGridMessage` to send emails to many recipients. By default it will create a `Personalization` for every recipient, but there's an overload where you can set the `showAllRecipients` parameter to true. When this parameter is set to true, the method will create one `Personalization` with all the recipients in it.  

The `​​CreateSingleTemplateEmailToMultipleRecipients` method will create a `SendGridMessage` configured with a Dynamic Email Template and a `Personalization` for every recipient.

The `CreateMultipleEmailsToMultipleRecipients` method will create a `SendGridMessage` configured with a `Personalization` for every recipient and Substitution Tokens.

## Which bulk email technique should you use?

You just learned many different ways to send email in bulk, but which should you use?  
Not to be cliche, but _it depends_… Let's compare each technique:

When you're using loops, you have full control over every email you send and there are no limitations.However, this is the least performant solution because you have to send an HTTP request for each email.   
Use loops when you need to do advanced customization of the subject and email body that is not possible using Substitution Tags, or with the Handlebars templating language. This solution is ideal if you want to [generate the email body using the Razor templating language](https://www.twilio.com/blog/render-emails-using-razor-templating).

Using Personalizations will greatly improve the performance of your application because you can send 1,000 emails per HTTP request. If you don't need to customize the email body for each recipient, you can set the content once for all emails, without the need for Substitution Tokens or Dynamic Email Templates.

Personalizations with Substitution Tokens works great for doing simple email customizations where you need to substitute predefined variables for every recipient. You cannot do more complex templating like looping and conditional rendering.

When you're doing Personalizations with Dynamic Email Templates, you have to manage the email templates in SendGrid which could be a benefit or a drawback depending on your needs. Dynamic Email Templates use a fully featured templating language, Handlebars, which you can use to render the simplest to the most advanced templates.

If you don't want to store your contacts in your own database, you can store contacts in SendGrid and send emails to them using Single Sends. Single Sends uses the same Handlebar templating language, but can only access the data that is stored on the contact. This means you have to upload the data to your SendGrid contacts before sending the email that requires it. You can send emails to all your contacts, specific contact lists, or segments of your contacts and you can generate unsubscribe links. The unsubscribe links allow the contact to remove themselves from the contacts list. You also don't have to worry about looping or paginating over your contacts as SendGrid will do this for you. This is ideal for marketing emails and newsletters.

If you're still not sure, you could go down this decision tree: 

![How to bulk email programmatically? Store Contacts in SendGrid? If yes, use Single Sends. If no, Customize Email per Recipient? If no, Use Personalizations with static body. If yes, do you need advanced email customization? If no, use Personalizations with Substitution Tags or Dynamic Email Templates. If yes, do you want to store templates in SendGrid? If yes, use Personalizations with Dynamic Email Templates. If no, send emails in a loop.](https://twilio-cms-prod.s3.amazonaws.com/documents/Bulk_Mail_Decision_Tree_2.optimized.svg)

## Next steps

Big congratulations if you made it this far! 🎉

You learned how to send emails in bulk using a bunch of methods, each with their own advantages. However, this tutorial only covers the code side of things, but you should check out [this guide on bulk emails to maximize the deliverability, engagement, and minimize the unsubscription rates](https://sendgrid.com/resource/bulk-email-guide/).

If you want to see a practical example of bulk email in action, check out this [tutorial on how to build an Email Newsletter application using ASP.NET Core and SendGrid](https://www.twilio.com/blog/how-to-build-an-email-newsletter-application-using-asp-net-core-and-sendgrid). This tutorial manages the newsletter subscribers using Entity Framework Core and uses a form to upload the HTML newsletter which is then sent using Personalizations and Substitution Tokens.