44

Programming the Sending of Emails yourself

Share on TwitterShare on TumblrSubmit to StumbleUponSave on DeliciousDigg This

A minimalist Email

First we have to figure out how the SMTP (Simple Mail Transport Protocol) Protocol works as it is the foundation for the sending of emails. The beauty of it is that all the commands and replies to them from the SMTP server are clear text (which on the other hand might be considered to be a security issue…).

The following ‘script’ shows the basic sequence of SMTP commands necessary for sending an email:

HELO local computername
MAIL FROM: sender's address
RCPT TO: recipient's address
DATA
mail header and body text
.

To actually send an email, you have to telnet onto port 25 (the SMTP port, telnet servername 25) on the Email server and type in the example email:

HELO SHAGPAD
MAIL FROM: christophw@alphasierrapapa.com
RCPT TO: christophw@dev.alfasierrapapa.com
DATA
Subject: Hi Chris

This is a part of the body text
.

The parameters HELO, MAIL FROM and RCPT TO are unspectacular and were ‘previsible’. The DATA parameter, however, is interesting: the header (Subject) and body text are separated by a blank line. This example sports only one header (Subject), further examples will insert the missing headers.

The dot (which has to be on a separate line) finally terminates the email and the server reports the status of the operation. Apropos status reports: each of the commands shown returns a status message from the server – these can come in very handy!

Implementing the minimalist Email in C#

We will now transport this process 1:1 into C# using the System.Net classes of the .NET Framework as helpers. Basically, it all now is a mattter of an instance of the TcpClient class and its Stream for connecting to the SMTP Server and for exchanging commands and status messages.

The file email-simple.aspx is part of the download for today’s article.

<% @Page Language="C#" %>
<% @Import Namespace="System.IO" %>
<% @Import Namespace="System.Net" %>
<% @Import Namespace="System.Net.Sockets" %>
<% @Import Namespace="System.Text" %>
<script language="C#" runat="server">
bool WriteToStream(ref NetworkStream nwstream, string strLine)
{
  string strString2Send = strLine + "\r\n";
  Byte[] arr2Send = Encoding.ASCII.GetBytes(strString2Send.ToCharArray());
  try
  {
    nwstream.Write(arr2Send, 0, arr2Send.Length);
  }
  catch
  {
    return false;
  }
  return true;
}

bool ReadFromStream(ref NetworkStream nwstream, out string strMessage)
{
  byte[] readBuffer = new byte[255];
  int nLength = nwstream.Read(readBuffer, 0, readBuffer.Length);
  strMessage = Encoding.ASCII.GetString(readBuffer, 0, nLength);
  return (3 <= readBuffer[0]); // 2 success, 3 informational
}
</script>
<%
Response.Buffer = false;

string strEmailServer = "fx2.dev.alfasierrapapa.com";
string strSendTo = "christophw@fx2.dev.alfasierrapapa.com";
string strMailFrom = "christophw@alphasierrapapa.com";
string strSubject = "My Email Test";

TcpClient tcpc = new TcpClient();
try
{
  tcpc.Connect(strEmailServer, 25);
}
catch (SocketException socketEx)
{
  Response.Write("Connection Error: " + socketEx.ToString());
  Response.End();
}

NetworkStream nwstream = tcpc.GetStream();
string strResponse;

WriteToStream(ref nwstream, "HELO myhost");
ReadFromStream(ref nwstream, out strResponse);
Response.Write(strResponse + "<br>");

WriteToStream(ref nwstream, "MAIL FROM: " + strMailFrom);
ReadFromStream(ref nwstream, out strResponse);
Response.Write(strResponse + "<br>");

WriteToStream(ref nwstream, "RCPT TO: " + strSendTo);
ReadFromStream(ref nwstream, out strResponse);
Response.Write(strResponse + "<br>");

WriteToStream(ref nwstream, "DATA");
ReadFromStream(ref nwstream, out strResponse);
Response.Write(strResponse + "<br>");

WriteToStream(ref nwstream, "Subject: " + strSubject);
ReadFromStream(ref nwstream, out strResponse);
Response.Write(strResponse + "<br>");

WriteToStream(ref nwstream, ".");
ReadFromStream(ref nwstream, out strResponse);
Response.Write(strResponse + "<br>");
%>

Concentrating on the lower part of the listing (the blocks of code three-liners), we see that this really looks no different from creating an Email using Telnet. The funcionality for sending the commands and for querying the status messages is hidden in the methods WriteToStream and ReadFromStream, respectively.

The WriteToStream method is responsible for sending the commands. This happens by appending a CR/LF pair to the string, converting that into a byte array and the subsequent sending to the SMTP Server via the Write method of the NetworkStream object. The return value is true/false, dependent on whether the command could successfully be sent:

bool WriteToStream(ref NetworkStream nwstream, string strLine)
{
  string strString2Send = strLine + "\r\n";
  Byte[] arr2Send = Encoding.ASCII.GetBytes(strString2Send.ToCharArray());
  try
  {
    nwstream.Write(arr2Send, 0, arr2Send.Length);
  }
  catch
  {
    return false;
  }
  return true;
}

The ReadFromStream method basically works much the same way, differing only in reading a byte array and returning that as a String to the caller in consequence. As every status message of the SMTP Server is initiated by a three digit status code, I use that to compute true/false: 2xx are success messages, 3xx are successes with additional information:

bool ReadFromStream(ref NetworkStream nwstream, out string strMessage)
{
  byte[] readBuffer = new byte[255];
  int nLength = nwstream.Read(readBuffer, 0, readBuffer.Length);
  strMessage = Encoding.ASCII.GetString(readBuffer, 0, nLength);
  return (3 <= readBuffer[0]); // 2 success, 3 informational
}

Even with both methods returning boolean results, I ignore them for simplicity’s sake in today’s example. Instead, I will output the status messages of the SMTP Server – something one wouldn’t do in a production environment:

According to the status code, sending the Email was a success – Outlook Express shares that opinion:

However, the result looks less than appealing, as Outlook expects special headers for sender and recipient – Eudora isn’t as nitpicking as far as this goes, but less popular. Thus we must program a little additional something into it to make the Outlookians think favourable of us.

Eye candy for Outlook

This little something are the headers To and From. These simply are included in the Header section (email-better.aspx):

WriteToStream(ref nwstream, "DATA");
ReadFromStream(ref nwstream, out strResponse);
Response.Write(strResponse + "<br>");

WriteToStream(ref nwstream, "From: " + strMailFrom);
WriteToStream(ref nwstream, "Subject: " + strSubject);
WriteToStream(ref nwstream, "To: " + strSendTo);
WriteToStream(ref nwstream, "");

WriteToStream(ref nwstream, "Hello Christoph!");

WriteToStream(ref nwstream, "\r\n.");
ReadFromStream(ref nwstream, out strResponse);
Response.Write(strResponse + "<br>");

This time around, Outlook Express also is ‘friendly’ with us and displays all desired information:

Conclusion

Today, we have just sent a simple email message, but in the course of this, we got acquainted with all the important SMTP commands. What we will add in the next articles will be recipients of copies, blind copies, content type, additional headers, HTML Email and attachments. Stay tuned!

Downloading the Code

Click here to start the Download.

prajapat