For the second time, Microsoft has officially marked a .NET class as being replaced by an open source library. The documentation for SmtpClient now reads, “Obsolete("SmtpClient and its network of types are poorly designed, we strongly recommend you use https://github.com/jstedfast/MailKit and https://github.com/jstedfast/MimeKit instead")”.

MailKit and MimeKit were created by Jeffrey Stedfast, who we interviewed in 2014. Even back then they were considered to be the most comprehensive MIME and email libraries for .NET.

The first major open source library that Microsoft embraced was Newtonsoft’s JSON.NET. Used extensively in ASP.NET WebAPI, this is officially recommended in lieu of the JavaScriptSerializer class and is generally preferred over the DataContractJsonSerializer class as well. Unlike SmtpClient, neither of these classes are marked as obsolete.

The main problem with SmtpClient is that it has a confusing connection lifecycle. Connecting to a SMTP server can be time consuming, especially if authentication is enabled, so each SmtpClient object has an internal connection pool.

This is a rather strange design. Consider for a moment a typical database connection. When you call Dispose on a SqlClient, the underlying connection is returned to the pool. When you create a new SqlClient, the pool is checked for an active connection with the same connection string.

With SmtpClient, calling Dispose closes all of the connections and drains that object’s connection pool. This means you can’t use it with the typical ‘using’ block pattern.

You may be thinking, “So I just hold onto one shared instance like HttpClient”. Well, no. Unlike HttpClient, the Send/SendAsync methods are not thread thread-safe. So unless you want to introduce your own synchronization scheme, you can’t use it that way either. In fact, the documentation for SmtpClient warns,

There is no way to determine when an application is finished using the SmtpClient object and it should be cleaned up.

By contrast, the SMTP client in MailKit represents a simple connection to a single server. By eliminating the complexity caused by internal connection pools, it actually makes it easier to create an application-specific pool for MailKit’s connection object.

While the active bug count is surprisingly low, one area where MailKit does have problems is true async support. The normal way to add async to an existing library is to copy all of the methods, making the small changes needed for asynchronous support. This code duplication isn’t hard to deal with in simple applications, but for something as complicated as an email client it could become a maintenance nightmare. So for the time being they are simulating async support by simply calling the synchronous code paths and blocking threads.

No decision has been made about how to deal with this, but one option being considered is the AsyncRewriter tool. This Roslyn based tool was used by the PostgreSQL team to convert their synchronous code into asynchronous equivalents.