How to send emails from C#/.NET - The definitive tutorial
I just answered some questions from a friend of mine about a subject that I believe would fit within my how-to-series of posts. That's where I try to come up with updated answers to common C# challenges like converting between formats or saving content to a file. For today's post, I'll show you different ways of sending emails from C#/.NET.
Sending out emails isn't what most developers consider a fun task. Emails have existed like always and every (well almost) system needs to send out one or more emails to the users. In my experience, creating a good solution for sending out emails, can be quite fun and a challenging task if every aspect is to be taken into account. Like how to handles bounces and monitoring errors. In this post, I'll show you a range of options for sending out emails and some of the worst practices to avoid.
Using SMTP
.NET and .NET Core come with built-in support for sending emails through the System.Net.Mail
namespace. While it might seem like the easy choice, you will need an SMTP server for this to work. I hosted an SMTP server back in the day and that isn't something I would recommend anyone doing today. In the sections below, I'll introduce you to a couple of different options. There are a lot of both free and paid SMTP services out there, so don't be shy on Google to find the right option for you.
Sending emails from C# using an SMTP server requires only a few lines of code:
var smtpClient = new SmtpClient("smtp.gmail.com")
{
Port = 587,
Credentials = new NetworkCredential("username", "password"),
EnableSsl = true,
};
smtpClient.Send("email", "recipient", "subject", "body");
In the example, I'm creating a new SmtpClient
which is the key class when needing to communicate with SMTP servers. The class accepts the hostname (smtp.gmail.com
used as an example but the value will depend on what service you pick) in the constructor. Most SMTP servers expect you to use port 587
through a secure connection that is set up using the Port
, Credentials
, and EnableSsl
properties. You need to replace username
with your full username (typically the email) and password
with your password. In the last line, I call the Send
-method. Replace email
with your full email address and recipient
with the full email address of the person who should receive the email.
There's an overloaded Send
-method that accepts a MailMessage
object to help build the email message:
var mailMessage = new MailMessage
{
From = new MailAddress("email"),
Subject = "subject",
Body = "<h1>Hello</h1>",
IsBodyHtml = true,
};
mailMessage.To.Add("recipient");
smtpClient.Send(mailMessage);
By using the MailMessage
class, you have access to several new properties, like the IsBodyHtml
used to include HTML inside the Body
property.
Google Workspace
If you are a Google Workspace customer, you can send emails using a username and password. I would recommend generating a new password specific to your C# code to avoid sharing your password with others.
To generate a new App password, head over to your Security page and click the App passwords option. Then select the Other app type and give it a name:
After clicking the GENERATE button you will get a new password generated for your account. For SMTP settings, use Google's SMTP server, your email, and your newly created password:
var smtpClient = new SmtpClient("smtp.gmail.com")
{
Port = 587,
Credentials = new NetworkCredential("email", "password"),
EnableSsl = true,
};
Gmail
Since writing this section, the Less secure app feature has been removed from most Google accounts.
To test sending emails through SMTP, you can use your Gmail account or sign up for a new one. To use Google's SMTP servers, there are a couple of options for authentication. You can redirect to Google's OAuth2 flow to get an authentication token. This approach will not be covered in this post, since that will require manual interaction from a user and is not suitable for a job or console application. The other solution is to enable Less secure app access on your profile's Security page:
Like the message says, allowing less secure apps aren't recommended. So, while it serves our test purpose, you should consider using something else when running in production. All major email providers offer to send out emails based on SMTP.
If the Less secure app access isn't available on your profile, it might be because two-factor authentication is enabled.
After enabling this option, you can use your Gmail address and password as username and password for the SMTP server.
Alternative SMTP servers
There are a lot of cloud-based SMTP servers available. I won't go into detail on each one, since I haven't tried any of them personally. Mailchimp, sendinblue, and Sendgrid are just a few options that I've heard people talk about. Make sure to try out each service and pick the one that fits your needs the best. Some of the services also provide their own .NET clients (more about that later in this post), why this could be a valid option instead of the built-in SMTP client.
Attachments
Attaching files to an email is easy. It requires you to use the MailMessage
class as seen in the previous example:
var mailMessage = new MailMessage
{
// ...
};
var attachment = new Attachment("profile.jpg", MediaTypeNames.Image.Jpeg);
mailMessage.Attachments.Add(attachment);
The code expects a file named profile.jpg
to exist in the program directory. A full path can be used as input as well, and even more overloads accepting Stream
exists.
Inline images
Rather than attaching images, you may want to inline the images as part of the email body. This can be done by creating what's called an alternate view and referencing attached images through a CID:
LinkedResource linkedResource = new LinkedResource("profile.jpg");
linkedResource.ContentId = Guid.NewGuid().ToString();
var html = $"<h1>Hello from</h1><img src=\"cid:" + linkedResource.ContentId + "\"/>";
AlternateView alternateView =
AlternateView.CreateAlternateViewFromString(html, null, MediaTypeNames.Text.Html);
alternateView.LinkedResources.Add(linkedResource);
var mailMessage = new MailMessage
{
IsBodyHtml = true,
// ...
};
mailMessage.AlternateViews.Add(alternateView);
SMTP settings in config
Until now, I have added configuration directly in the C# code. For obvious reasons, you don't want that in a real-life scenario. Also, you may want to switch settings, depending on which environment the code is currently running in.
In .NET, SMTP settings has its own config section (beneath the system.net
element) in the app.config
and web.config
file:
<mailSettings>
<smtp deliveryMethod="Network">
<network host="smtp.gmail.com" port="587" userName="email" password="password" enableSsl="true" />
</smtp>
</mailSettings>
Once settings is present in the config file, you simply create a new instance of the SmtpClient
using the empty constructor:
var smtpClient = new SmtpClient();
.NET Core doesn't have a similar concept and totally replaced the configuration system. Declaring SMTP settings in appsettings.json
can be easily done:
{
"Smtp": {
"Host": "smtp.gmail.com",
"Port": 587,
"Username": "email",
"Password": "password"
}
}
Then referenced from code like this:
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
var config = builder.Build();
var smtpClient = new SmtpClient(config["Smtp:Host"])
{
Port = int.Parse(config["Smtp:Port"]),
Credentials = new NetworkCredential(config["Smtp:Username"], config["Smtp:Password"]),
EnableSsl = true,
};
.NET configuration also provides strongly typed options through the IOptions
interface. There are thousands of blog posts available showing how to do this, why I don't want to repeat it here. Check out AppSettings in ASP.NET Core for details on how to make strongly typed config classes in ASP.NET Core.
MailKit
I have some bad news for you. While the SMTP client available in .NET and .NET Core works just fine, Microsoft recommends not to use it:
SmtpClient
always covered my needs, but let's look at the alternative Microsoft recommends MailKit. MailKit is an open-source .NET library supporting IMAP, POP3, SMTP, and much more. It has an interface very similar to the built-in SMTP client. Let's rewrite the simple mail example from SmtpClient
to MailKit. Start by installing the MailKit
NuGet package:
Install-Package MailKit
Sending an email looks very familiar with MailKit:
var mailMessage = new MimeMessage();
mailMessage.From.Add(new MailboxAddress("from name", "from email"));
mailMessage.To.Add(new MailboxAddress("to name", "to email"));
mailMessage.Subject = "subject";
mailMessage.Body = new TextPart("plain")
{
Text = "Hello"
};
using (var smtpClient = new SmtpClient())
{
smtpClient.Connect("smtp.gmail.com", 587, true);
smtpClient.Authenticate("user", "password");
smtpClient.Send(mailMessage);
smtpClient.Disconnect(true);
}
As already mentioned, MailKit supports a range of other scenarios like reading emails through IMAP. If you are looking for something more advanced than sending a simple message, MailKit is a good choice. In the following sections, I will present you with a range of cloud-based alternatives to SmtpClient
and MailKit.
Using AWS Simple Email Service
As already mentioned, hosting your own SMTP server isn't ideal. In addition to that, building the content of the email to look good in all email clients, can require quite a lot of code. We switched our email generation needs to an external provider years ago and switched the product on the way. We currently use Simple Email Service (SES) from Amazon Web Services (AWS).
Don't let the name fool you. SES is indeed simple to use but it offers a wide range of features like email templates with handlebars code, bounce handling, send statistics, and much more.
A quick note about SES and SMTP. When signed up, you will notice the SMTP Settings menu item inside AWS. SES can be used simply as an SMTP server, meaning that all of the code from the SMTP section in this post, apply for SES too. In this section, I will show you some of the more advanced features of SES, to avoid making it a copy of what we already discussed, only with SES config.
To test SES, let's upload a template to AWS. A template is a JSON file containing an email template. The nice thing about using templates is that you only need to tell SES which email to send and provide it with any dynamic variables. The email looks like this:
<h1>Hello {{firstname}}</h1>
<p>Welcome as a new user on our amazing service.</p>
SES email templates cannot be uploaded through the AWS Console, why you need to embed the content in a JSON file:
{
"Template": {
"TemplateName": "my-email-template",
"SubjectPart": "Welcome",
"HtmlPart": "<h1>Hello {{firstname}}</h1>
<p>Welcome as a new user on our amazing service.</p>"
}
}
The JSON contains a name (slug), a subject and the HTML from the example. To upload the template, you can use the AWS CLI:
aws ses create-template --cli-input-json fileb://my-email-template.json
Sending the email is easy using the excelent SES package for .NET:
Install-Package AWSSDK.SimpleEmail
And a bit of C# code:
var emailClient = new AmazonSimpleEmailServiceClient(
new BasicAWSCredentials("accessKey", "secretKey"),
RegionEndpoint.USWest2);
var data = @"{
""firstname"": ""Ragnar""
}";
var sendRequest = new SendTemplatedEmailRequest
{
Source = "Sender <email>",
Destination = new Destination { ToAddresses = new List<string> { recipient } },
Template = "my-email-template",
TemplateData = data,
};
await emailClient.SendTemplatedEmailAsync(sendRequest);
To send emails, we need a new instance of the AmazonSimpleEmailServiceClient
class. The class needs your access and secret key, available on the AWS Console. You also provide the region hosting your SES.
The SendTemplatedEmailRequest
corresponds to the MailMessage
class from the previous examples. It tells AWS who sends the email, who should receive it, as well as the template name and input data.
Finally, the SendTemplatedEmailAsync
-method sends the email through SES.
Using Mandrill
There are a lot of email solutions out there. The one we used before switching to AWS, was Mandrill (by Mailchimp). Mandrill offers a lot of the same features as AWS SES, but the cost is higher. With the higher cost, you also receive some additional features not available on AWS. Like an online template builder, that I won't go into details with here. It works pretty much like templates on SES and handlebars codes can be embedded in the template. We simply moved our Mandrill email templates to SES and everything worked out of the box.
There are a couple of different .NET clients available, but the best one (IMO) is Mandrill.NET:
Install-Package Mandrill.net
The usage corresponds to that of SES:
var mandrillApi = new MandrillApi("apikey");
var mandrillMessage = new MandrillMessage
{
FromEmail = "email",
};
mandrillMessage.AddTo("recipient");
message.AddGlobalMergeVars("firstname", "Floki");
await mandrillApi.Messages.SendTemplateAsync(mandrillMessage, "my-email-template");
For more information about sending emails through Mandrill, check out Sending transactional emails using Mandrill and .NET.
Using SendGrid
Yet another mail provider with good .NET support is SendGrid. SendGrid supports both SMTP and a .NET client. With SMTP, you can use either the SmtpClient
class or the MailKit package that I showed you already. All you need to do is to replace the SMTP server, port, and credentials with those obtained from SendGrid.
Since we already covered how to send mails using SMTP, let's take a look at SendGrid's .NET client. Start by installing the SendGrid
NuGet package:
Install-Package SendGrid
Sending emails requires pretty much the same pattern that we've already seen multiple times:
var sendGridClient = new SendGridClient("API_KEY");
var from = new EmailAddress("from email", "from user");
var subject = "subject";
var to = new EmailAddress("to email", "to name");
var plainContent = "Hello";
var htmlContent = "<h1>Hello</h1>";
var mailMessage = MailHelper.CreateSingleEmail(from, to, subject, plainContent, htmlContent);
await sendGridClient.SendEmailAsync(mailMessage);
What about Azure?
It comes to a chock for many (including me) that Azure doesn't provide an email solution. There is a range of integration with external services like SendGrid and Mailjet, but there's no native service available.
Using an external service like SES, Mandrill and SendGrid works great from Azure, but notice that you pay for outgoing traffic from Azure. This means that you will be charged for the traffic coming from within your Azure data center and to the external mail provider. Using templates is a great way to reduce the bytes sent back and forth.
Among the different solutions I have mentioned in this post, SendGrid is the easiest solution to get started with from Azure. Creating a new SendGrid account is available directly in the Azure Portal by clicking the Create a resource button and searching for "SendGrid":
Troubleshooting
This section will list common problems and solutions when trying to send emails from C#.
Username and Password not accepted
This error is typically shown when either the username or password is wrong. Check both values and make sure to use valid credentials. As already mentioned in the SMTP section, when using Google's SMTP servers, you will need to enable Less secure app access. If this setting isn't enabled, sending emails through their servers requires you to sign in through OAuth2.
ASP.NET Core template application doesn't send emails
When creating a new ASP.NET Core application from the template, there are a couple of built-in email features. One of them being the forgot password feature. With the default code provided, no email is actually sent when clicking this button. You will need to tell ASP.NET Core how to send emails using one of the solutions listed previously in this post.
Start by creating a new class and give it a name. For this example, I'll implement a simple SMTP based mailer:
public class SmtpEmailSender : IEmailSender
{
private readonly SmtpClient smtpClient;
public SmtpEmailSender(SmtpClient smtpClient)
{
this.smtpClient = smtpClient;
}
public async Task SendEmailAsync(string email, string subject, string htmlMessage)
{
var mailMessage = new MailMessage
{
From = new MailAddress("thomas@elmah.io"),
Subject = subject,
Body = htmlMessage,
IsBodyHtml = true,
};
mailMessage.To.Add(email);
await smtpClient.SendMailAsync(mailMessage);
}
}
The class is an implementation of the IEmailSender
interface provided by Microsoft. To tell ASP.NET Core to use this, include the following code in the Startup.cs
or Program.cs
code:
var smtpClient = new SmtpClient("smtp.gmail.com")
{
Port = 587,
Credentials = new NetworkCredential("username", "password"),
EnableSsl = true,
};
builder.Services.AddSingleton(smtpClient);
builder.Services.AddTransient<IEmailSender, SmtpEmailSender>();
Also, make sure to disable default account verification as explained here: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/accconfirm?view=aspnetcore-6.0&tabs=visual-studio#disable-default-account-verification-when-accountregisterconfirmation-has-been-scaffolded.