Sending Email in .NET Core with Office 365 and MailKit

You may have previously used SmtpClient to send email in .NET. However, that API is now obsolete and the current recommend method is to use the MailKit library. Here’s how to use it with the Office 365 SMTP servers.

There are various ways to send email using Office 365. Connecting directly to the MX record for an organisation looks attractive but if you don’t have a static IP address to add to the SPF (in a DNS TXT record) then you will get blocked as spam and will see an error message like this.

SmtpCommandException: 5.7.1 Service unavailable, Client host [$ConnectingIP] blocked using Spamhaus. [] [] []

The best way is to authenticate directly with an Office 365 mailbox. However, there can be problems when using TLS.

Microsoft recommend the standard port of 587 and TLS enabled but this is misleading. Full TLS would use port 465.

You need to use SecureSocketOptions.StartTls. If you try to enable TLS for the whole connection (true or SecureSocketOptions.SslOnConnect) then it will fail with an error message like this.

The remote certificate is invalid according to the validation procedure

Another error message you may encounter is this.

SmtpCommandException: 5.2.0 STOREDRV.Submission.Exception:SendAsDeniedException.MapiExceptionSendAsDenied; Failed to process message due to a permanent exception with message Cannot submit message.

What this cryptic response actually means is that the from address and name specified must match the mailbox you are authenticating as.

Putting it all together, you can use the following async C# code.

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}";

message.Body = new TextPart("plain")
    Text = "{body}"

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

Clearly many of these values should come from configuration files and not be hard coded. The password (for a test account) should be kept in your secret store (outside of source control) for development and injected using a secure variable (for the production account) during a live deployment. I’ll write more about that in my next post on Azure DevOps.

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