Advanced Email Sending with .NET Core and MailKit

I wrote previously about sending email in .NET Core with Office 365 and MailKit but, as that was just a getting started guide, I glossed over some of the complexities with email. There are many pitfalls and I’ll cover a few of them here.

End of the Line

.NET Core is great and it’s awesome that it runs on Windows, macOS and Linux. However, cross-platform code comes with some subtleties, not least of which is the different line ending conventions on each system. A Carriage Return, Line Feed or both may be used to represent a newline in text.

In a static managed language like .NET it is a little more complex. Certain things are set at compile time and others at runtime, so there can be some odd quirks. Maybe you build your app on Linux and it sometimes runs on Windows (or vice versa). Or perhaps you develop and package an app on macOS, then deploy it to a Docker container running on a Linux server (possibly using Kubernetes a.k.a. K8s). These platform differences can be difficult to debug, particularly when you can’t see whitespace and many applications are quite lenient in what they accept.

If you know the platforms that all of your clients will use ahead-of-time then you can simply pick the correct Azure DevOps build agent and if it’s a web app then the same server Operating System too. However, if you want to run your web app on a Linux server (due to lower cost) and it then sends mail (maybe via Office 365 SMTP) to users running native email clients on Windows and macOS then this is not an option.

If you are generating the body for a plaintext email with multi-line verbatim string interpolation then you may not get quite what you expect. Certain email clients (notably MS Outlook) might not render it correctly, similar to how notepad doesn’t handle alternative newline conventions well.

Consider the following trivial example code:

var bodyPlaintext = $@"Hi {name},

Thanks for signing up!

Please verify your email at {link}.

";

You can set the newlines to use for your code file in the edit / advanced menu in Visual Studio but your interpolated verbatim string will contain the line endings of the platform it was built on rather than from the file. Environment.NewLine can sometimes help and will get the newline string for the current environment (CRLF, LF or CR). However, if you try to match this at runtime with a verbatim interpolated string literal built on another platform then it may not match.

There are also potential encoding issues with this such as UTF-16 .NET strings in UTF-8 files and the associated Byte Order Mark. You can save with a specific encoding in the save prompt in Visual Studio (save as, then dropdown on the save button) and it’s best to use Unicode.

This is all just to illustrate that plaintext is not as straightforward as it may seem and there are many edge cases to test. Fortunately, a simple way to avoid some of these issues for email is to use HTML and represent newlines with the <br> tag (but still have a plaintext version as a backup). You can do this in MailKit with the BodyBuilder class. For example, the following code sends an email with both HTML and plaintext parts:

var message = new MimeMessage();
message.From.Add(new MailboxAddress("{from name}", "{from email address}"));
message.To.Add(new MailboxAddress("{to name}", "{to email address}"));
message.Subject = "{subject}";

var builder = new BodyBuilder
{
    TextBody = bodyPlaintext,
    HtmlBody = bodyHtml,
};
message.Body = builder.ToMessageBody();

using var client = new SmtpClient();
await client.ConnectAsync("smtp.office365.com", 587, SecureSocketOptions.StartTls);
await client.AuthenticateAsync("{from email address}", "{from password}");
await client.SendAsync(message);
await client.DisconnectAsync(true);

As this is C# 8.0 the using statement has not been explicitly scoped here.

Background Reading

You shouldn’t send email on your main thread and block returning on it. There is no QueueBackgroundWorkItem (QBWI) in .NET Core but you can use the excellent Hangfire project. The documentation is great and it just works, although it does require storage such as a SQL Server database.

If you are sending lots of emails or want a high performance reliable system then you should look into Message Queuing architectures. I’ll write more on this another time, or you could buy my last book if you can’t wait.


This blog is treeware! If you found it useful then please plant a tree.
Donate a treeDonate a tree🌳🌳