How to send emails from C#/.NET - The definitive tutorial

TOC

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.

How to send emails from C#/.NET - The definitive tutorial

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.

Monitor errors when sending emails

➡️ Reduce errors by 90% with elmah.io centralized error logging and uptime monitoring ⬅️

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:

App passwords

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:

Less secure app access

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 recommendation

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.

Cloud logging for .NET

➡️ Reduce errors by 90% with elmah.io centralized error logging and uptime monitoring ⬅️

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":

SendGrid integration

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.

elmah.io: Error logging and Uptime Monitoring for your web apps

This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.

See how we can help you monitor your website for crashes Monitor your website