Using Amazon Simple Email Service (SES) to Send Bulk Emails
by Kurt Feeley

Photo by Paula Hayes on Unsplash
This article is part of a series to introduce developers to Amazon Simple Email Service. Check out all the articles.
Introduction to Amazon Simple Email Service – SES
Using the CLI to Setu Up Amazon SES Email Identities and Contact Lists.
Using Amazon Simple Email Service (SES) to Send Single Emails
Using Amazon Simple Email Service (SES) to Send Bulk, Templated Emails
The Solution
In this tutorial, we’ll show you how to use Amazon SES to send large groups of emails like a mass marketing campaign or an e-newsletter.
Remember, for any example solution from AWS with .NET, we focus on the code that demonstrates the problem we are trying to solve. We don’t include logging, input validation, exception handling, etc., and we sometimes embed the configuration data within classes instead of using environment variables, configuration files, key/value stores and the like. These items should not be skipped for proper solutions.
Prerequisites
To complete this solution, you will need the .NET CLI which is included in the .NET SDK. In addition, you will need to create an AWS IAM user with programmatic access and the appropriate permissions to interact with Amazon Simple Mail Service. You will also need to download the AWS CLI and configure your environment.
If you have not already, complete the previous steps, set up Amazon SES email identities and contact lists and using Amazon Simple Email Service (SES) to send single emails before you complete this tutorial.
Warning: Some AWS services may have fees associated with them.
Our Dev Environment
This tutorial was developed using Ubuntu 24.04.3, .NET 8 SDK and Visual Studio Code 1.108.1. Some commands/constructs may vary across systems.
Extending the EmailService class.
In this article we will extend the Email Service class that we built in Using Amazon Simple Email Service (SES) to Send Single Emails. If you are just starting out, it is best to begin with that article. For those that want to get started in this article, here’s the EmailService class in its entirety.
using Amazon.SimpleEmailV2;
using Amazon.SimpleEmailV2.Model;
namespace Services;
public class EmailService
{
private readonly IAmazonSimpleEmailServiceV2 _sesClient;
private readonly string _fromEmailAddress;
public EmailService(string fromEmailAddress)
{
_fromEmailAddress = fromEmailAddress;
_sesClient = new AmazonSimpleEmailServiceV2Client();
}
public async Task<string> SendSingleEmailAsync(
string recipientEmail,
string subject,
string htmlContent,
string textContent)
{
try
{
var sendEmailRequest = new SendEmailRequest
{
FromEmailAddress = _fromEmailAddress,
Destination = new Destination
{
ToAddresses = new List<string> { recipientEmail }
},
Content = new EmailContent
{
Simple = new Message
{
Subject = new Content
{
Data = subject,
Charset = "UTF-8"
},
Body = new Body
{
Html = new Content
{
Charset = "UTF-8",
Data = htmlContent
},
Text = new Content
{
Charset = "UTF-8",
Data = textContent
}
}
}
}
};
SendEmailResponse response = await _sesClient.SendEmailAsync(sendEmailRequest);
return response.MessageId;
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
return "Error";
}
}
}
Developing an Amazon SES Email Template
Before we get started extending the EmailService class, let’s create a template to store our email content. Creating a template will allow us to store raw text and HTML in Amazon SES for later use instead of including both in our AWS SES SDK calls, like we did in the SendSingleEmailAsync method. Having the ability to use a template is important, as it could allow a marketing team to work on the text and HTML of the email and then have both imported into Amazon SES for later use. This would then allow IT to focus on sending the emails using Amazon SES. As you’ll see later on, using the template is much more concise and efficient for our purposes.
To create the template, let’s first create a JSON file named, “email-template.json”, that will include the name of the template as well as the content, which will include the email subject as well as the text and HTML content.
{
"TemplateName": "March-2025-Newsletter",
"TemplateContent": {
"Subject": "Greetings!",
"Text": "Hello subscriber,\r\nEnjoy this month's newsletter.\r\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nulla ipsum, tempus non ipsum id, elementum faucibus leo. Curabitur eu bibendum nulla.",
"Html": "<p>Hello subscriber,</p><p>Enjoy this month's newsletter</p>.<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum nulla ipsum, tempus non ipsum id, elementum faucibus leo. Curabitur eu bibendum nulla.</p>"
}
}
We can then create the template using the following AWS SESv2 CLI command:
$ aws sesv2 create-email-template --cli-input-json file://email-template.json
With the template created, let’s extend the EmailService class.
Developing the GetContactsAsync Method
Before we develop the SendTemplatedBulkEmailAsync method which will send emails to many recipients, we will need to develop a private method to support the the functionality in SendTemplatedBulkEmailAsync.
We’ll need a contact list in order to send a bulk email. We’ll develop the GetContactsAsync method to help us build that contact list.
The GetContactsAsync method takes only one parameter, contactListName, which is the name of the contact list, and returns a list of strings.
private async Task<List<String>> GetContactsAsync(string contactListName)
{
}
In building out this method, we’ll create a ListContactsRequest object, providing the name of the contact list and a page size of 10.
We’ll then send the ListContactsRequest using the AWS .NET SES SDK and wait for the ListContactsResponse.
Once we have received that ListContactsResponse, we’ll parse out the email from each contact.
And, ultimately, the method will return a list of email addresses.
A note on the page size: To keep things simple, we are setting a limit of 10 contacts that will be returned. A more robust solution would be to utilize the NextToken property of the ListContactsRequest/ListContactsResponse to allow full paging functionality.
Below is the completed GetContactsAsync method.
private async Task<List<String>> GetContactsAsync(string contactListName)
{
var request = new ListContactsRequest()
{
ContactListName = contactListName,
PageSize = 10
};
ListContactsResponse response = await _sesClient.ListContactsAsync(request);
return response.Contacts.Select(x => x.EmailAddress).ToList();
}
Developing the SendTemplatedBulkEmailAsync Method
With the GetContactsAsync method complete, let’s build out the SendTemplatedBulkEmailAsync method.
The SendTemplatedBulkEmailAsync method, returns a string and accepts two string parameters, contactListName and templateName.
public async Task<string> SendTemplatedBulkEmailAsync(string contactListName, string templateName)
{
}
The first thing we’ll do is get our contact list by calling the GetContactsAsync method that we just completed, passing in the contactListName parameter.
var contacts = await GetContactsAsync(contactListName);
Now that we have fetched our list of contacts, we turn our attention to creating a SendEmailRequest. While creating the SendEmailRequest, we will reference the contacts variable in the Desination as a value for ToAddresses. We will also set the EmailContent property referencing the templateName parameter that was passed in.
Lastly, we will send the SendEmailRequest using the AWS SES SDK and wait for the response to parse and ultimately return the Amazon SES messageId.
One thing to note here is this template does not require template data and as such we provide the following TemplateData value:
{"null":"null"}
Here’s the completed SendEmailRequest/SendEmailResponse code. Notice how much less code is used when dealing with an Amazon SES email template.
var sendEmailRequest = new SendEmailRequest
{
Destination = new Destination
{
ToAddresses = contacts
},
FromEmailAddress = _fromEmailAddress,
Content = new EmailContent()
{
Template = new Template
{
TemplateName = templateName,
TemplateData = "{\"null\":\"null\"}"
}
}
};
SendEmailResponse response = await _sesClient.SendEmailAsync(sendEmailRequest);
return response.MessageId;
Below is the completed SendTemplatedBulkEmailAsync method.
public async Task<string> SendTemplatedBulkEmailAsync(string contactListName, string templateName)
{
try
{
var contacts = await GetContacts(contactListName);
var sendEmailRequest = new SendEmailRequest
{
Destination = new Destination
{
ToAddresses = contacts
},
FromEmailAddress = _fromEmailAddress,
Content = new EmailContent()
{
Template = new Template
{
TemplateName = templateName,
TemplateData = "{\"null\":\"null\"}"
}
}
};
SendEmailResponse response = await _sesClient.SendEmailAsync(sendEmailRequest);
return response.MessageId;
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
return "Error";
}
}
Here is the updated EmailService class after the updates.
using Amazon.SimpleEmailV2;
using Amazon.SimpleEmailV2.Model;
namespace Services;
public class EmailService
{
private readonly IAmazonSimpleEmailServiceV2 _sesClient;
private readonly string _fromEmailAddress;
public EmailService(string fromEmailAddress)
{
_fromEmailAddress = fromEmailAddress;
_sesClient = new AmazonSimpleEmailServiceV2Client();
}
public async Task<string> SendSingleEmailAsync(
string recipientEmail,
string subject,
string htmlContent,
string textContent)
{
try
{
var sendEmailRequest = new SendEmailRequest
{
FromEmailAddress = _fromEmailAddress,
Destination = new Destination
{
ToAddresses = new List<string> { recipientEmail }
},
Content = new EmailContent
{
Simple = new Message
{
Subject = new Content
{
Data = subject,
Charset = "UTF-8"
},
Body = new Body
{
Html = new Content
{
Charset = "UTF-8",
Data = htmlContent
},
Text = new Content
{
Charset = "UTF-8",
Data = textContent
}
}
}
}
};
SendEmailResponse response = await _sesClient.SendEmailAsync(sendEmailRequest);
return response.MessageId;
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
return "Error";
}
}
public async Task<string> SendTemplatedBulkEmailAsync(string contactListName, string templateName)
{
try
{
var contacts = await GetContactsAsync(contactListName);
var sendEmailRequest = new SendEmailRequest
{
Destination = new Destination
{
ToAddresses = contacts
},
FromEmailAddress = _fromEmailAddress,
Content = new EmailContent()
{
Template = new Template
{
TemplateName = templateName,
TemplateData = "{\"null\":\"null\"}"
}
}
};
SendEmailResponse response = await _sesClient.SendEmailAsync(sendEmailRequest);
return response.MessageId;
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
return "Error";
}
}
private async Task<List<String>> GetContactsAsync(string contactListName)
{
var request = new ListContactsRequest()
{
ContactListName = contactListName,
PageSize = 10
};
ListContactsResponse response = await _sesClient.ListContactsAsync(request);
return response.Contacts.Select(x => x.EmailAddress).ToList();
}
}
Testing the SendTemplatedBulkEmailAsync Method
Testing the new SendTemplatedBulkEmailAsync method in the EmailService is pretty straightforward.
We’ll again set the sender email address, passing the value through the class constructor. And, once the EmailService object is instantiated, we’ll call SendTemplatedBulkEmailAsync, passing in the template name along with the contact list name.
There’s no need to pass the raw text or HTML content as the subject, text, and HTML are included in the template. All we need to do is pass in the template name along with the contact list name to the SendTemplatedBulkEmailAsync method, and we are good to go.
var fromEmailAddress = "some.name@emaildomain.example";
string contactListName = "newsletter-subscribers";
string templateName = "March-2025-Newsletter";
EmailService _emailService = new EmailService(fromEmailAddress);
String messageId = await _emailService.SendTemplatedBulkEmailAsync(contactListName, templateName);
return messageId;
Summary
We have concluded this article where you have learned how to:
- Get an Amazon SES contact list using the AWS .NET SES SDK.
- Send an email to many recipients using an Amazon SES contact list and the AWS .NET SES SDK.
- Create an Amazon SES email template using the AWS CLI.
Want to know more about the tech in this article? Check out these resources: