One of the background parts of most systems I write involves messaging of some kind ... more often than not, the default is email notification of one thing or another. When I need to carryout large scale mail-outs I always use one fo the major Azure services, but when I only need adhoc mail operations or low volume, I use a combination of an SMTP Client and a DNS MX Lookup. MX what? ..,. Lookup! he said, Lookup! :)
Quick explanation: Normally, our message gets sent from the sender, to the senders mail-server, and it takes care of everything ... this is from A -> B -> C -> D above. Our alternative is to dispose of the intermediate server at 'B', and go straight to 'C' ourselves.
side-note 1: if you are short on time and don't want the background to understand whats happening, just download the attached code, its reasonably self explanatory
side-note 2: while I use this code on Azure, it should also work on other cloud providers and also standard windows applications as well.
When we send an email to say our friend 'email@example.com', we generally hand it over to an SMTP server to send onward for us. But how does it actually get sent on and what happens to it along the way? ... Many years ago I had the serious fun writing a custom MailServer - this involved creating both a POP3 and an SMTP server. In the process of building them I learned about the protocols used in mail exchange, and learned about the MX or 'Mail Exchange' record that is stored in a DNS server. When you open your browser and enter 'CodeProject.com' as the address to go to, one of the things your system does is go to its local DNS (domain name server) and asks 'where is the actual IP address of this server/resource that the website CodeProject.com resides? ..... Depending on what the system asks for, it will return simply the IP address of the web-server, or other information as well. The DNS stores not only information about web-server services, but also about all kinds of other things for dns based network communication. A DNS record can have many entries, here are some examples of different types and their general use:
- 'A' record .. returns a 32-bit IPv4 address
- 'CNAME' .. returns an alias to another record
- 'MX' .. returns a list of 1:n servers that specifically handle mail
(if you want to poke around, here is a list of different DNS record types)
The reason we are interested in the MX or Mail Exchange record, is that it will allow us to bypass the need for an SMTP server to send our message and go directly to the receiving target email server. To understand how we can do this, lets first quickly look at the protocol used for sending email and then how this is used by an SMTP server to manage mail transport.
MX Record lookup
As a mail-server, when we have a message we want to send, we need to find out where to send it - just because someone has an 'outlook.com' address does not mean you send it directly to the server that hosts the main domain. To lookup the MX record, we need to send the root domain in as a query to a DNS server, and extract the MX record from the answer. In the case of most large mail hosts there will be multiple mail servers returned. The list you receive may not be consistent, and the order of the servers listed may also change - this assists with load balancing among other things.
For a visual look at how this works, I will use the DNS query tool at the 'Network Tools' website, in particular the NSLOOKUP service: http://network-tools.com/nslook/
Enter a query for CodeProject.com as follows:
Now lets execute and see the first result we get, take note in particular the first entry, the other alternatives given, and the order in which they are listed...
Now lets do exactly the same thing again and notice the changes... I have marked the *previous* order/positions in red.
The general process that happens is a mail server carries out an MX lookup, gets a list of results back (like above), then it iterates through the list, connecting to each listed mail-server in turn, until one successfully accepts the message.
The important part we are interested in here is the 'exchange' part of the MX record. This tells us where the server is that will accept a message for the root domain we are trying to connect to (in this case, CodeProject.com).
Once we know the mail-server we need to connect to, we then need to have a consistent method to connect to the server and send our message - we do this using an SMTP message protocol.
SMTP stands for 'Simple mail transfer protocol'. It was first developed back in the prehistoric ages of 1982 - you can read it in its glory on the IEFT Webpage for RFC 821. Mail servers use SMTP to transmit and receive messages, whereas email clients typically use SMTP to forward mail to server, and then use either POP3 or IMAP to receive messages from a server. The SMTP protocol is a 'chat' between two endpoints, it goes kind of like this....
Client: 'Hey, I have a message for someone on your server....'
Server: 'Great, let me have it!'
Client: 'Here you go....<sends message>'
Server: 'Got it thanks, I will deliver it asap, good-bye and thanks for calling!'
Client: 'Cool, Cya!'
The protocol has an agreed standard for connecting the two endpoints, for passing over the message, and closing the connection again. You can actually send an email message form the command-line using the inbuilt windows application 'telnet' if you wish! ... lets take a little side-trip and look at that, just because its interesting!
Telnet is a command-line utility that allows you to connect to a remote computer. Its generally turned off by default in Windows - to turn it on, open your control panel, then add/remove programs and 'turn windows features on or off' - in here look for the 'telnet client' - confirm the checkbox is ticked and close the modal window - it should install and you are good to go.
To use telnet and connect to a mail-server, open a command window. At the prompt, enter telnet, followed by the 'exchange' name you are trying to contact, followed by the port number of the mail server. This is usually port 25.
telnet aspmx3.googlemail.com 25
When connected, you will see something like the following:
We are connected to the mail-server and it is awaiting our first command. We tell it who we are by saying 'Helo' (no, its not a typo!), and announcing who we are...
The mail server responds by greeting us with a code '250', and saying its ready for the next command, so now we can have the entire conversation, send our message and sign off:
On the other side, when I check my email, I can see the message coming in:
So under the hood, that's how an email is sent, but its not the entire story, and that's where the full functionality of the SMTP mail-server comes into play.
An SMTP server is usually incorporated into a full mail-server and is responsible for both receiving mail from other systems, and sending messages it has received, if for external addresses, to the domains associated with those addresses. So if a server is handling the domain 'codeproject.com' and we send a mail to 'firstname.lastname@example.org', it will be stored internally until Bob collects the message. If on the other hand we are bob, and send a message to this server for 'email@example.com', then the SMTP server will take Bobs mail, and take care of connecting to the SMTP server for Microsoft, and delivering Anjits mail to that Microsoft server. In receiving and sending mails, the SMTP server implements the SMTP protocol (above). But that's not the full story of the SMTP server ... its not simply just about sending mail from one place to another.
In general, an SMTP server will be responsible for the correct routing of an email message from one place to another. This means that if it accepts a message from Bob@codeproject for Anjit@microsoft, but for whatever reason is *unable* to connect and deliver the message to the Microsoft mail-server, then it will take care of the process of storing the message for a while, then retrying X number of times, and if all retries fail, it will send a message back to the sender letting them know. There more as well, such as store and forward, checking for spam, viruses etc. Now, of course it will be said that these functions are not *strictly* the domain of the SMTP server, but rather an associated support/management service. I agree, however, the point of saying it is to show that there's more to sending mail than simply connecting and expecting everything to go right on first connect.
Which brings us on to the point of all this - how we can use information we get from the MX record of a DNS record, and combine this with an SMTP Client, and use this to make our own simple mail transport mechanism.
The process of sending an email message from our C# application without needing an intermediate server is very straightforward and involves a number of simple steps:
- Extract the domain name from the email address you wish to send the message to
- Connect to a DNS server and query the domain name to extract the 'mx record'
- Use a simple SMTP class to connect to the SMTP mail-server given in the 'ms record', and send the email
Thanks to the wonderful worlf of OpenSource and package managers, everything we need to glue our solution together and get things operational is available on NuGet. Sstart a new Visual Studio project, and add the following packages to your project:
- DNS Client
Using the code
First things first, import some of the libraries in your USING section:
My code example demonstrates how things work in a simple MVC project, but it can be used anywhere.
In this example I am going to send a test email to my gmail account...
our intial code sets up a new MX 'Lookup' client...
var client = new LookupClient();
we then query the destination domain 'gmail.com', using the 'MX' query parameter.
var result = client.Query("gmail.com", QueryType.MX);
The client query returns a collection of 'Answer records' which should list out the details of one or more 'exchange mail' servers. Our task is to iterate through these and attempt to send our message. If we succeed, we can then break out iteration loop as the job is done, else we can handle the failure.
So, having queried the DNS server for MX records, lets enter the send attempt loop....
foreach (var x in result.Answers)
var X = x as DnsClient.Protocol.MxRecord;
var y = X.Exchange;
The key parts to note here and
- we loop through the ANSWERs received from the DNS server
- we type-cast as an MX record
- we extract the EXCHANGE part of the record - this is the *actual destination mail server* we will connect with
Now that we have the mail-server information, we need to construct our message and try to send it. We do this using the MailKit library we loaded earlier.
var message = new MimeMessage();
message.From.Add(new MailboxAddress("Mr Test", "firstname.lastname@example.org"));
message.To.Add(new MailboxAddress("Mrs Recipient Name", "email@example.com"));
message.Subject = "Weekly report from system";
message.Body = new TextPart("plain")
Text = @"Hi there, your weekly test message is available here"
To send the message, we create an SMTP client, and attach our message to it for sending:
using (var smclient = new SmtpClient())
smclient.ServerCertificateValidationCallback = (s, c, h, e) => true;
smclient.Connect(y, 25, false);
The main code we are interested in here is the 'connect' and 'send'. Note when we connect, we tell the smtpClient the server-host/mail-exchange we received from the MX lookup ('y') and tell it that the SMTP host is on port 25.
Once we connect, we send our message and then disconnect. Assuming everything went well, we then break out of the loop, otherwise we fall into an exception and start at the top fo the loop again.
And, thats it! .... I have tested this on a console app, desktop app, web-app in Azure and in function code on Azure. As usual, this is example code, you need to make it robost and production ready yourself.
The solution present here is generally for low volume messaging. If you need something more reliable, for faster messages, to get through anti-spam filters etc, you should consider a commercial cloud mail provider.
Example code is attached to the article - please login and download and use as you need. As usual, if you found the article useful, pleae consider giving it a star rating at the top of the page!
Version 1 - 04/Feb/2018