In this article, we are going to learn how to send an email in ASP.NET Core. We are going to start with simple project creation and creating a basic email configuration. Once we are finished with that, we are going to install the required MailKit library and create the logic to send email messages from ASP.NET Core.
We are going to send email messages in ASP.NET Core synchronously and asynchronously and change the email body from plain text to pure HTML.
Finally, we are going to learn how to add attachments to the email body.
VIDEO: How to Send an Email with Attachments in ASP.NET Core.
Let’s start.
Configure an SMTP Server
We are going to start with a new ASP.NET Core Web API project. After the project creation, we are going to add the .NET Class Library project with the name EmailService. Of course, we have to add a reference to the main project:
Let’s add the EmailConfiguration
class inside that project:
public class EmailConfiguration { public string From { get; set; } public string SmtpServer { get; set; } public int Port { get; set; } public string UserName { get; set; } public string Password { get; set; } }
This class contains properties needed to configure sending email messages from our application. We are going to use the appsettings.json
file to populate these properties, and in order to do that, we have to modify that file:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "EmailConfiguration": { "From": "[email protected]", "SmtpServer": "smtp.gmail.com", "Port": 465, "Username": "[email protected]", "Password": "our test password" }, "AllowedHosts": "*" }
To finish with the configuration process, we are going to modify the ConfigureServices
method in the Startup.cs
class:
public void ConfigureServices(IServiceCollection services) { var emailConfig = Configuration .GetSection("EmailConfiguration") .Get<EmailConfiguration>(); services.AddSingleton(emailConfig); services.AddControllers(); }
In .NET 6, we have to modify the Program class:
var emailConfig = builder.Configuration .GetSection("EmailConfiguration") .Get<EmailConfiguration>(); builder.Services.AddSingleton(emailConfig); builder.Services.AddControllers();
So, we extract configuration values from the appsettings file and register EmailConfiguration
as a singleton. And that’s all we need to configure our email service.
How to Enable Less Secure Apps with Gmail
Starting on May 30, 2022, Google no longer supports the use of third-party apps or devices which ask you to sign in to your Google Account using only your username and password. So, we have to use a different solution for our application. To do this, we need to enable 2-step verification for our Gmail account first and then we can use the App Password feature to overcome this issue.
So, to enable 2-step verification, we have to:
- Navigate to our Google Account – the account you will send the emails from (https://myaccount.google.com/)
- In the menu on the left, we should select Security
- Then under the “Signing in to Google” section, we can see that 2-Step Verification is off – so we have to click on it
- Click Get Started, provide your password, and confirm the code by providing a mobile number
- If everything goes well, you should see the Turn On option, so just click on it
At this point, we have enabled our 2-Step verification and we can return to the Security page. There, under the same “Signing in to Google” section, we can find the App passwords option set to None.
So, we have to:
- Click on it
- Provide a password
- Click the Select app menu and choose the Other (Custom Name) option
- Now, all we have to do is to provide any name we like for our app and click the Generate button
This will generate a new password for us, which we should use in our appsettings.json
file instead of our personal password.
With this in place, we are enabled again to send emails with our third-party apps.
Add the MailKit Library
Before starting any other operation in our project, we have to add the NETCore.MailKit
library to the EmailService
project:
This library is going to help us send the email.
The imports we’ll be using are:
using MailKit.Net.Smtp; using MimeKit;
Send a Test Email
Next, in the same project, we are going to create a Message
class:
public class Message { public List<MailboxAddress> To { get; set; } public string Subject { get; set; } public string Content { get; set; } public Message(IEnumerable<string> to, string subject, string content) { To = new List<MailboxAddress>(); To.AddRange(to.Select(x => new MailboxAddress(x))); Subject = subject; Content = content; } }
We are going to use this class to set the data related to our email recipients, subject, and content.
Then, let’s create a new interface:
public interface IEmailSender { void SendEmail(Message message); }
And a class that implements this interface:
public class EmailSender : IEmailSender { private readonly EmailConfiguration _emailConfig; public EmailSender(EmailConfiguration emailConfig) { _emailConfig = emailConfig; } public void SendEmail(Message message) { var emailMessage = CreateEmailMessage(message); Send(emailMessage); } }
As you can see, we inject email configuration into EmailSender
class and then we call two different methods to create an email message and to send the email respectively. Now, let’s implement those two missing methods:
private MimeMessage CreateEmailMessage(Message message) { var emailMessage = new MimeMessage(); emailMessage.From.Add(new MailboxAddress(_emailConfig.From)); emailMessage.To.AddRange(message.To); emailMessage.Subject = message.Subject; emailMessage.Body = new TextPart(MimeKit.Text.TextFormat.Text) { Text = message.Content }; return emailMessage; } private void Send(MimeMessage mailMessage) { using (var client = new SmtpClient()) { try { client.Connect(_emailConfig.SmtpServer, _emailConfig.Port, true); client.AuthenticationMechanisms.Remove("XOAUTH2"); client.Authenticate(_emailConfig.UserName, _emailConfig.Password); client.Send(mailMessage); } catch { //log an error message or throw an exception or both. throw; } finally { client.Disconnect(true); client.Dispose(); } } }
Note: If you are using the newest Mailkit package, you may want to modify the .From.Add
line to:
emailMessage.From.Add(new MailboxAddress("email", _emailConfig.From));
We use the first method to create an object of type MimeMessage
and to configure the required properties. Then, we pass that object to the second method and use the SmtpClient
class to connect to the email server, authenticate and send the email.
Just pay attention that the SmtpClient
class comes from the MailKit.Net.Smtp
namespace. So, you should use that one instead of System.Net.Mail
.
Now, we have to register this service in the Startup.cs
class if you are using .NET 5 or some of the previous versions:
services.AddScoped<IEmailSender, EmailSender>();
Or for .NET 6, we have to modify the Program class:
builder.Services.AddScoped<IEmailSender, EmailSender>();
We have one more step to do. Let’s modify the Get action in the WeatherForecastController
:
[HttpGet] public IEnumerable<WeatherForecast> Get() { var rng = new Random(); var message = new Message(new string[] { "[email protected]" }, "Test email", "This is the content from our email."); _emailSender.SendEmail(message); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); }
That’s it. Let’s test this now:
We get a 200 OK response. So, let’s check the source email server:
And let’s check the destination server:
Excellent. Everything works as expected.
Using HTML in the Email Body
In the previous example, we have been using Plain Text as a body format. But we can use HTML as well with a few modifications to our code.
So, all we have to do is to modify the CreateEmailMessage
method:
private MimeMessage CreateEmailMessage(Message message) { var emailMessage = new MimeMessage(); emailMessage.From.Add(new MailboxAddress(_emailConfig.From)); emailMessage.To.AddRange(message.To); emailMessage.Subject = message.Subject; emailMessage.Body = new TextPart(MimeKit.Text.TextFormat.Html) { Text = string.Format("<h2 style='color:red;'>{0}</h2>", message.Content) }; return emailMessage; }
And that’s all it takes. A simple change of the body text format and the content itself.
We can try sending the same request one more time from Postman:
It doesn’t get any easier than that.
Sending an Email in ASP.NET Core Asynchronously
If we want to send email messages asynchronously, we have to make some changes to our project. If you are not familiar with the asynchronous programming in ASP.NET Core Web API, we strongly recommend reading our article about Asynchronous Repository Pattern in ASP.NET Core Web API.
Let’s start with the interface modification:
public interface IEmailSender { void SendEmail(Message message); Task SendEmailAsync(Message message); }
Next, let’s modify the EmalSender
class:
public async Task SendEmailAsync(Message message) { var mailMessage = CreateEmailMessage(message); await SendAsync(mailMessage); }
As you can see, we use the same method to create an email message but a different method to send that email. So, we have to implement that one:
private async Task SendAsync(MimeMessage mailMessage) { using (var client = new SmtpClient()) { try { await client.ConnectAsync(_emailConfig.SmtpServer, _emailConfig.Port, true); client.AuthenticationMechanisms.Remove("XOAUTH2"); await client.AuthenticateAsync(_emailConfig.UserName, _emailConfig.Password); await client.SendAsync(mailMessage); } catch { //log an error message or throw an exception, or both. throw; } finally { await client.DisconnectAsync(true); client.Dispose(); } } }
Finally, we have to modify the Get
action:
[HttpGet] public async Task<IEnumerable<WeatherForecast>> Get() { var rng = new Random(); var message = new Message(new string[] { "[email protected]" }, "Test email async", "This is the content from our async email."); await _emailSender.SendEmailAsync(message); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); }
Awesome. Now let’s try this out with the same request from Postman:
Adding File Attachments
In order to include attachments to our email messages, we have to provide a way for our app to process the attached files.
So, let’s start by adding a new POST action to our controller:
[HttpPost] public async Task<IEnumerable<WeatherForecast>> Post() { var rng = new Random(); var files = Request.Form.Files.Any() ? Request.Form.Files : new FormFileCollection(); var message = new Message(new string[] { "[email protected]" }, "Test mail with Attachments", "This is the content from our mail with attachments.", files); await _emailSender.SendEmailAsync(message); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); }
This action is almost the same as the previous one, but it has some important modifications. The first thing to notice is that this is a POST action. The second change is the part where we extract files from the request, if any, or return an empty file collection. The third change is related to the Message
constructor, it now accepts an additional parameter.
Of course, we have to modify the Message
class:
public class Message { public List<MailboxAddress> To { get; set; } public string Subject { get; set; } public string Content { get; set; } public IFormFileCollection Attachments { get; set; } public Message(IEnumerable<string> to, string subject, string content, IFormFileCollection attachments) { To = new List<MailboxAddress>(); To.AddRange(to.Select(x => new MailboxAddress(x))); Subject = subject; Content = content; Attachments = attachments; } }
As you can see, we’ve added the additional IFormCollection
parameter. Now, let’s move on to the EmailSender
class:
private MimeMessage CreateEmailMessage(Message message) { var emailMessage = new MimeMessage(); emailMessage.From.Add(new MailboxAddress(_emailConfig.From)); emailMessage.To.AddRange(message.To); emailMessage.Subject = message.Subject; var bodyBuilder = new BodyBuilder { HtmlBody = string.Format("<h2 style='color:red;'>{0}</h2>", message.Content) }; if (message.Attachments != null && message.Attachments.Any()) { byte[] fileBytes; foreach (var attachment in message.Attachments) { using (var ms = new MemoryStream()) { attachment.CopyTo(ms); fileBytes = ms.ToArray(); } bodyBuilder.Attachments.Add(attachment.FileName, fileBytes, ContentType.Parse(attachment.ContentType)); } } emailMessage.Body = bodyBuilder.ToMessageBody(); return emailMessage; }
We now use the BodyBuilder
class to create a body for the email message. Additionally, we check for the attachment files and if they exist, we convert each of them to the byte array and add it to the Attachments
part from the bodyBuilder
object. Finally, we convert the bodyBuilder
object to the message body and return that message.
Now, before we use Postman to send a request, we are going to add the FormOptions
configuration to the ConfigureServices
method:
public void ConfigureServices(IServiceCollection services) { var emailConfig = Configuration .GetSection("EmailConfiguration") .Get<EmailConfiguration>(); services.AddSingleton(emailConfig); services.AddScoped<IEmailSender, EmailSender>(); services.Configure<FormOptions>(o => { o.ValueLengthLimit = int.MaxValue; o.MultipartBodyLengthLimit = int.MaxValue; o.MemoryBufferThreshold = int.MaxValue; }); services.AddControllers(); }
In .NET 6, we have to use the builder variable in the Program class:
builder.Services.Configure<FormOptions>(o => { o.ValueLengthLimit = int.MaxValue; o.MultipartBodyLengthLimit = int.MaxValue; o.MemoryBufferThreshold = int.MaxValue; });
With the FormOptions
configuration, we set the limit for its different properties to the maximum value.
One more thing. We have to modify the call to the Message constructor in the Get action because now it accepts four parameters.
var message = new Message(new string[] { "[email protected]" }, "Test email async", "This is the content from our async email.", null);
To prepare a request in Postman, we have to choose the POST method option and use the form-data options to send files with the request:
And let’s inspect our email:
Excellent work.
Conclusion
We did a great job here. Now we have a fully functional Web API service that we can use in our projects to send email messages using C#.
So to sum up, we’ve learned:
- How to create a basic SMTP configuration
- The way to send email messages with plain text or an HTML body
- How to send email messages asynchronously
- How to include attachments in an email message