Universal SMTP Code To Send Emails In .NET Apps

Introduction

 
As already mentioned quite clearly in the tagline, the only reason to write this article is to cover the basic requirements of every SMTP vendor's code in .NET applications. If your vendor is Google's Mail Service — Gmail — then, you can most likely leave this article and go back to the article which I wrote long ago to share how to send emails through a C# program - Sending Emails Over .NET framework, and General Problems – using C# code. That article will cover the most important parts of understanding the libraries, and how you should communicate with all the settings, etc., to be able to send emails from your own account. 
 
Thus, the benefit I would conclude from this article would be that I can easily share this article with anyone who might want to ask questions such as, 
 
"How to send an email using {your SMTP provider}?"
 
Or to the questions, where the OP is asking us to review their code for any mistakes. They need to understand that problems with sending emails are not due to bad coding only, they involve several other reasons as well. In this post, all those points would be covered so that there can be a standard "working" SMTP code, which you can try out in your own application to determine whether the problem is in the code or in the account which is being used to send the email. 
 
The content and the code sample of this article are intended to be provided as a reference only and might contain bugs. It is up to you to decide whether to copy/paste shamelessly and try it in your code or just get the useful stuff out of this post and consider it for your applications. 
 
Design of the code provided here
 
Before I go down and start working out the stuff that I have implemented and where I found that, I want to tell you about the design of this code, how I did it, and how it might be useful for you. I will be trying to solve a few questions, which I why the design of the code required me to write a factory class in the background to provide me with the objects of different SMTP vendors. In this code sample, I have implemented different objects for:
  1. Google Mail
  2. Yahoo Mail
  3. Hotmail — the Outlook as we know it
  4. Office 365
  5. SendGrid — I found quite a lot of questions, circulating around SendGrid API so I wanted to share the code, which does indeed sends the email from their servers. 
  6. Custom objects, to implement anything in a different manner. 
These are the major SMTP service providers available out there and most questions come from their users. The code sample, as provided on GitHub, would help you to either understand how to write the code for sending emails or would help you in understanding how to write the SmtpClient objects, for each one of them. Sometimes they require a different configuration and sometimes they are all just the same robots, with different names. 
 
That said, there were a few things contradicting the present situation of SMTP providers and the way accounts are managed, which would require me to add a different section at the bottom of this article to support and talk about all those aspects of the user accounts to enable SMTP programming against an account, through a .NET application. 
 
The ISmtpClient interface
 
I started with creating an interface that would contain the information required to create a concrete implementation of an SMTP client. The purpose of this interface was to make sure that I can implement this interface in multiple classes, and provide the implementation for them. There were, however, a few problems which I would discuss at the end of this article. A simple introduction to the interface would seem a bit helpful, right, so...
  1. Interface was a definition of an SMTP client, which would hold.
     
    1. SMTP service host address.
    2. Connection port information.
    3. SSL enabled or not.
    4. Message to send.
       
  2. SMTP host could be changed later.
  3. Port address, which can be the default (25) or configured (587, or anything else, which a server expects).
  4. SSL support enabled, though it was always required.
  5. The credentials to use while communicating with the server. 
Thus, I ended up with the following interface.
 
Universal SMTP code
 
Figure 1ISmtpClient interface diagram.
 
I am going to skip a few things from here so that I can explain them in a later section below (such as the IDisposable implementation in ISmtpClient), so please bear with me. But, so far we had the basic implementation which can be utilized in the further processes, to create the SMTP clients — just asking, am not I repeating this sentence just too many times? :D 
 
Alright, now the next section talks about how I implemented this ISmtpClient interface. Keep reading.
 
Creating the factory class for SmtpClient instances — SmtpClientFactory
 
Once this interface was developed, I went on to create a factory class, which would help me in creating different SMTP clients for different services. I am not a fan of design patterns, as I never understand any of them. But somehow, I always happen to implement them in my programs and my coauthor told me, that what-ever I had come up within the program was actually the Factory Patternoh right beggars can't be choosy I guess. To design the core creator of the clients, I created an enum to specify the type of clients I would be creating and using further down the stream, the enum was as simple as:
  1. public enum ClientType { Gmail, Outlook, Yahoo, Office365, SendGrid, Custom }  
As you can see, this enumeration has a value for each possible client that I am interested in. Plus, a special kind of client does not share anything to the eye of the reader, but just an idea that this is a custom client and must be provided with everything on the runtime, as the factory pattern would be expecting a lot of information for this fellow. 
 
The implementation of ISmtpClient required me to pass a few of the most important elements to the factory class's main method, which I did, and (although skipping now), the class instances of the respective types were generated. Following the factory pattern, the creation of objects was returned as the instance of an interface, by creating an instance of the implementing class, not the interface itself. 
 
Universal SMTP code
 
Figure 2SmtpClientFactory class diagram, with the supported client types enumeration.
 
The enum part of this class has already been discussed, which leaves me one function to talk about. The GetClient function is the core function, which takes care of the abstraction and plays the role of creating the instances and provided us with them. Now, the function itself was implemented like the following:
  1. // Method returns the ISmtpClient as an instance of the required object  
  2. public static ISmtpClient GetClient(ClientType type, NetworkCredential credentials, bool withSsl, string host = ""int port = 25)  
  3. {  
  4.     ISmtpClient client;  
  5.   
  6.     if (type == ClientType.Gmail)  
  7.     {  
  8.         client = new GmailClient(credentials, withSsl);  
  9.     }  
  10.     else if (type == ClientType.Office365)  
  11.     {  
  12.         client = new Office365Client(credentials, withSsl);  
  13.     }  
  14.     else if (type == ClientType.Outlook)  
  15.     {  
  16.         client = new OutlookClient(credentials, withSsl);  
  17.     }  
  18.     else if (type == ClientType.Yahoo)  
  19.     {  
  20.         client = new YahooClient(credentials, withSsl);  
  21.     }  
  22.     else if (type == ClientType.SendGrid)  
  23.     {  
  24.         client = new SendGridClient(credentials, withSsl);  
  25.     }  
  26.     else  
  27.     {  
  28.         // Custom  
  29.         if (host == "")  
  30.         {  
  31.             throw new Exception("Host name is required for a custom SMTP client.");  
  32.         }  
  33.         client = new CustomClient(host, port, withSsl, credentials);  
  34.     }  
  35.     return client;  
  36. }  
For the time being, ignore the classes which I will discuss in the next section. Until then, just understand the fact that this function returns the client which we require. It expects the users, to input: 
  1. ClientTypeto decides what sort of client is being expected here. Remember, that the rest of the stuff depends on the type of the client, credentials, SSL, and no SSL, port and other settings all depend on this first parameter. 
  2. NetworkCredentialobject, to store the credential for the user, such as his/her username and password combination. 
  3. SSL allowance, however in my own experience I have figured that you are always required to use SSL in the major SMTP vendors such as Google Mail, Outlook, etc. 
  4. Host and port settings are required to create the CustomClientobject, which requires a bit more control over how the communication is done, such as setting SSL off or using a different port. However, a default port of 25 is used. 
There is nothing special in this code to talk about, other than the fact that it checks if the hostname is empty, and the client is of type CustomClient, then it prompts the users to enter something and throws that exception. 
 
Only a few of the implementations
 
I do not want to share a lot of code of the same redundant implementations, instead, I just want to share a few of them here for you so that you can get the gist of what I am trying to convey here. Rest assured, you can always read the code sample on GitHub, which is made available to you — see at the top in the downloads section. 
  1. // Implementing the interface, to capture the fields and functions.  
  2. public class GmailClient : ISmtpClient  
  3. {  
  4.     // Host acts as a getter-only property here, implemented via lambda.  
  5.     public string Host { get => "smtp.gmail.com"; }  
  6.     public int Port { get; set; }  
  7.     public bool EnforceSsl { get; set; }  
  8.     public NetworkCredential Credentials { get; set; }  
  9.     public SmtpClient Client { get; set; }  
  10.   
  11.     public GmailClient(NetworkCredential credentials, bool ssl)  
  12.     {  
  13.         Credentials = credentials;  
  14.         Port = 25;  
  15.         EnforceSsl = ssl;  
  16.         if (ssl)  
  17.         {  
  18.             Port = 587;  
  19.         }  
  20.   
  21.         Client = new SmtpClient(Host, Port);  
  22.     }  
  23.   
  24.     // Async function to send the email, whereas the event handler can still be applied.   
  25.     async Task ISmtpClient.Send(MailMessage message)  
  26.     {  
  27.         Client.EnableSsl = EnforceSsl;  
  28.         Client.Credentials = Credentials;  
  29.   
  30.         // Send  
  31.         await Client.SendMailAsync(message);  
  32.     }  
  33.   
  34.     // The Dispose() function, to properly send the QUIT command to SMTP server  
  35.     public void Dispose()  
  36.     {  
  37.         Client.Dispose();  
  38.     }  
  39. }  
Rest of the clients, which are provided with a named type all implement the service in a similar manner, whereas the custom client type is a bit different. So, that is why the other type of client which I am going to show you how to implement, is the custom client type. 
  1. // Implementing the interface, to capture the fields and functions.  
  2. public class CustomClient : ISmtpClient  
  3. {  
  4.     // Needs a setter, so that object can set Host information later on  
  5.     public string Host { get; set; }  
  6.     public int Port { get; set; }  
  7.     public bool EnforceSsl { get; set; }  
  8.     public NetworkCredential Credentials { get; set; }  
  9.     public SmtpClient Client { get; set; }  
  10.   
  11.     // Constructor requires somewhat more information.  
  12.     public CustomClient(string host, int port, bool ssl, NetworkCredential credentials)  
  13.     {  
  14.         // In the custom mode, we can set the host ourselves.  
  15.         Host = host;  
  16.         Port = port;  
  17.         Credentials = credentials;  
  18.         EnforceSsl = ssl;  
  19.   
  20.         Client = new SmtpClient(Host, Port);  
  21.     }  
  22.   
  23.     // Async function to send the email, whereas the event handler can still be applied.   
  24.     async Task ISmtpClient.Send(MailMessage message)  
  25.     {  
  26.         Client.EnableSsl = EnforceSsl;  
  27.         Client.Credentials = Credentials;  
  28.   
  29.         // Send  
  30.         await Client.SendMailAsync(message);  
  31.     }  
  32.   
  33.     // The Dispose() function, to properly send the QUIT command to SMTP server  
  34.     public void Dispose()  
  35.     {  
  36.         Client.Dispose();  
  37.     }  
  38. }  
The only major difference in both implementations is their constructor because one of them requires somewhat more information as compared to the other one, which already contains information about the host and a few other kinds of stuff. 
 
Secondly, as we saw previously in the SmtpClientFactory class, we were using the extra parameters to fill in the details of the custom client:
  1. GetClient(ClientType type, NetworkCredential credentials, bool withSsl, string host = ""int port = 25)  
Corresponds, to the following constructor: 
  1. public CustomClient(string host, int port, bool ssl, NetworkCredential credentials)  
Whereas the other ones don't get the values and our program does not require to pass those values at all. In such cases, it would be useful to always follow the type, and ignore passing hardcoded values. However, there might be cases where we need to pass in some of the information directly, such as the cases where we do not know of the hostnames, port to connect at, and whether SSL is required or not. Of course, there are services out there, some insecure services, which provide free access to their servers for sending emails and in turn, they track your emails, content, and personal information to sell and get their bargain. 
 
Implementing the IDisposable interface
 
As mentioned above, it is the time to talk about the implementation of the IDisposable interface in my own ISmtpClient interface, where it was already implemented by the SmtpClient of the .NET framework. The thing is, when I was writing the application code, I was not implementing the interface in my own interface, instead I was using something like this:
  1. // I know the signature says, "async Task ISmtpClient.Send(params) {}" but, read the paragraphs below  
  2. void ISmtpClient.Send(MailMessage message, NetworkCredential credentials, bool withSsl) {  
  3.     // Create the object here  
  4.     using (var client = new SmtpClient(Host, Port)) {  
  5.         /* Other stuff,  
  6.          * Send the email here, do a proper cleanout, etc. 
  7.          */   
  8.     }  
  9. }  
Then, later on, I was to figure out that there is another API in SmtpClient as well, which allows you to send the emails asynchronously and it is always recommended to consider the async approaches, as compared to the synchronous ones unless you really know what you are doing and it is required to grab the synchronous code. Now, with this code, I would require to actually wait out inside the function or at least have the object wait until the email is sent out or so, before cleaning out the object. 
 
The reason to bring the IDisposable out here, was so that I can bring the control of disposing of the objects properly out to the scope of my program itself, and control when the object would be disposed of. Thus, to make the required changes I did the following: 
  1. Changed the function signature to mark is as an asynchronous context
  2. Changed the return type from the void, to Task— which in turn is a void in the asynchronous context. 
  3. Applied the awaitkeyword and used the asynchronous function.
The "using" context is helpful where you have to clean up the resources, which a program has been consuming for a while. You can read more about this in one of my previous articles, and you can also look for it on the MSDN or Microsoft Docs website as well. Pretty much, it is a language construct, which allows you to ensure that an object releases all the resources it is holding up, before going out of scope. I did not want to miss this important factor out of the code before writing it down, that is why it was important to make sure somewhere a using block is being used by the code. As you will see onwards, the Dispose function of SmtpClient is called to send a QUIT command to the server and properly terminate the connection before leaving the context and being deleted by the GC. 
 
The explanation of the async context is in the next section, please continue reading. 
 
Asynchronous functionality in the clients
 
Now that we have bits and bytes merged and summed up, let us apply a few final polishing marks to the clients and make it asynchronous for the graphical applications. The previous function was changed to the following code sample now:
  1. // Async keyword, and the Task return type  
  2. async Task ISmtpClient.Send(MailMessage message)  
  3. {  
  4.     Client.EnableSsl = EnforceSsl;  
  5.     Client.Credentials = Credentials;  
  6.   
  7.     // Sending of the mail message asynchronously, and waiting till this finishes.   
  8.     await Client.SendMailAsync(message);  
  9. }  
The benefit of the two changes, the first being the IDisposable, and the second one being the addition of async/await keywords to the content, allow me to write an asynchronous code as well as maintaining the disposing of the SmtpClient object properly. 
 
Better heads up, because there is another function SendAsync(). That function does not work with async/await design, because the function returns a void and thus cannot be awaited. That is why, if you are considering using async/await then you must avoid that function and instead consider the one with "Mail" in it. Now the majority of our code has been taken care of, let us move down a step and try sending the emails from our code. 
 
Note
 
I will not send emails by every client, as I already have done that, instead I will only send email through the default Google Mail client and show you what it does, and a few suggestions by the end of this article. 
 
Getting started with System.Net.Mail
 
The first thing you would want to do is to understand what sort of support does the .NET framework provides for email delivery. There are several things that you can handle in the SmtpClient object, and that is what we want to see in this section now. The SmtpClient object, in the .NET framework, supports the basic level of SMTP communication, as it is defined in the RFC 2821. You need to look over that resource to learn more about SMTP. What I am going to talk about here, in this post is a few of the best practices to ensure that your application keeps running, and doesn't crash obviously. 
 
Optimistic try — a successful hit
 
Now, first I will try the working code and show you what I have, there is no need to share the code again, or say which client I used. The thing to notice here is, that the clients work if you provide them the accurate details, and I will discuss those accurate details in a minute. Here is the result of my trial: 
 
Universal SMTP code
 
Figure 3: "Email sent successfully." message being shown in a terminal.
 
This proves the fact that this code work. Secondly, I tested the code with the 4est practices provided by each individual service provider, to ensure the general code is the exact skeleton of what is required here. Now, let us try to break the code once. The email message was, 
 
Universal SMTP code
 
Figure 4: The email message is shown in the webmail client.
 
Moving onwards, now.
 
Breaking the code in several ways
 
This is the part where the .NET framework's SmtpClient annoys you. The API doesn't tell you whether the connection is set up, working, ready to accept the email messages or not. The only point where it would tell you is when you will call the Send function! This is just not at all useful, but if we have to stick to everything-.NET, then there is nothing we can do. In this client, there are several things that can go wrong. Let me list a few out to you: 
  1. Your credentials are faulty
  2. Your port was incorrect, perhaps you wanted to use the default port (25) instead of the secure one.
  3. You didn't enable the SSL-based communication and wanted to communicate over an SSL channel.
In all these cases, you just need to ensure that the SSL channel is enabled before you send the email. 
  1. // somewhere inside the client  
  2. client.EnableSsl = true;  
  3. // rest of the stuff  
Almost, in every possible way, the client would break and give you this error — or a similar one. 
 
Universal SMTP code
 
Figure 5: Exception message being shown in the Visual Studio IDE.
 
The exception was captured in the Visual Studio's window, but you get the idea. The error is thrown in most cases, and this is the most-likely-encountered error in SMTP programming with .NET because the API does not show any interest in letting you control further states of the server-client communication here. However, the primary reason for this error was that I set the EnableSsl field to false. 
 
Bonus- Understanding the behavior of different providers
 
Finally, I would like to give a bit of talk on the way different providers are implemented and how they expect their customers to access the APIs. One thing we know is that all these vendors are service-providers, for consumers and are primarily used as consumer-based and not as enterprise-oriented (SendGrid, Office 365 and Google G Suite, etc are an exception, and other SMTP providers also, generally speaking). They all have similar behavior and provide a similar service. On my conquest for this article, Otherwise, there might still be a question like, 
 
Quote
 
I have enabled SSL, and my username/password is correct, but I am still unable to send an email. What might be wrong?
 
Such questions require a bit of provider-specific details and require to dig a bit deeper into each one of them. I noticed a few things in all of them and here in this final section, I would like to share them with you... 
 
Security implementations
 
Since all these vendors are consume-based, they disable programmability on their services. Google Mail, and Yahoo, for instance, required me to go in the settings and enable not only the SMTP services but low-security applications as well.
 
For example, have a look at the following screenshot of my own inbox, where Google Mail is telling me that I need to check my account details (e.g. password) being compromised, or I should allow those devices to access if I know what is going on, 
 
Universal SMTP code
 
Figure 6: Google Mail showing a sign-in attempt warning to a user, notifying them of unusual activity.
 
There are several other checks, that are implemented to ensure that your account is safe. Whereas on the other end, in the application service providers such as Office 365, or SendGrid, your account will be secured with strong credentials as they are enterprise-oriented service providers and they don't like to lock out their users once they have all logged in with the API keys or username/password combinations. 
 
The communication-security
 
Now let us talk about the security of the communication, what happens when the mail travels on the wire? This is one of the most confusing things in the entire API. The API says, that the communication is done on SSL, which is utter bullshit... The communication does not take place on SSL, instead, it takes place on TLS (which is not SSL). 
 
Thus, when you read the following line of code:
  1. client.EnableSsl = true;  
Please, understand the fact that this is TLS going on, and not the SSL. I would recommend that you read a bit more about TLS (Transport Layer Security) and the SSL (Secure Socket Layer), to get a better understanding of them both. 
 
Please go through the following links at RFC documents, to get a better understanding of how things go, 
  1. The TLS Protocol Version 1.0
  2. Simple Mail Transfer Protocol
  3. SMTP Service Extension for Secure SMTP over Transport Layer Security
By the end of reading those links, most of the confusion would be removed as the confusions are merely due to the naming conventions and the people telling out names of these technologies. Nothing else. 
 
Secondly, not sure, but all the clients require you to enable SSL communication, otherwise, they will always cause the error. It is also a better practice to enable SSL and then send an email, you should avoid using the servers and service providers where security is not taken seriously. Even if nothing serious happens, your email account would be left open to the public and spammers are always looking for email addresses. 
 
Misc
 
Or as I call it, the "Final Words". All of the SMTP providers are following the RFC documents which I have shared with you above, and it is always recommended to find a library which conforms to those standards, and I have found that the .NET framework's SmtpClient has a poor implementation, the reasons for which are obvious and shared in the article (e.g. the client is unable to tell whether the connection is established or not, or what is the status of the connection unless you actually try to do something). I am also trying to get a useful library and would update once that is made available until then there is very less to say. 
 
In my quest, I found that Office 365 was a bit sluggish, to send the emails and generate the responses, and the SendGrid was even worse. What SendGrid did was they responded back with an OKAY (or whatever SMTP alternative for this English command is), but they didn't send an email out until a while... Which was disturbing for me and doesn't count as a speed-send. Google Mail, Yahoo, and Outlook (not Office 365) were all quite fast enough for me. 
 
Nonetheless, for a while, my default email provider stays the Google Mail and soon I might be migrating to Office 365 or Outlook and thus would be investigating why their servers are a bit late in sending an email. 
 
I hope you have found this article useful, and would be using the content provided here as a reference, or for any purpose, you might want. The code is not a simple sample function as it was previously, but instead is a complete library-sort-of-thing, so you can take what you need and implement it in your own code.