C# SMTP Mail without SMTP Service or CDO
By Peter A. Bromberg, Ph.D.
Printer - Friendly Version
Peter Bromberg

Recently one of our readers and I went back and forth via email about putting together a little app that he wanted his mother to be able to use that would retrieve her current IP address and email it to him, so that he could remote to her desktop to help her out with whatever computer problem she may have been having.



One of the problems we had was either finding or writing code that would enable the app to send this email without relying on her having the SMTP service present and running on her machine, and without relying on the presence of CDO either. The .NET System.Web.Mail.SmtpMail class uses Collaboration Data Objects for Windows 2000 (CDOSYS) message component under the hood, so if Mom is running Windows ME, for example, you are plum out of luck with your app. Since then, we've had several additional requests for this type of code, so I thought it would be worthwhile to investigate this further.

Sending email via TCP using the native SMTP RFC commands "HELO", "MAIL From", RCPT TO", etc. is no big deal. That's one of the first tricks we learn with Telnet. Finding or writing managed code that will do so reliably is another story. The code in the class that follows is not my original code - I've cobbled it together from three different sample sources, fixing namespaces, error handling, and other minor items, changing console code to class library code, and providing a complete Winforms - based test harness front end that illustrates its correct usage. I've also included sample code to correctly process and add a mail attachment via an OpenFileDialog here. This code MIME encodes and transmits the attachment(s) according to the specification.

Since the class code speaks for itself, I'll simply post it below. It should be pretty much self-explanatory. You can download the complete solution at the link below and you should be able to plug this class into your applications with no changes necessary - right out of the box.

using System;
using System.Text;
using System.IO;
using System.Net.Sockets;
using System.Net;
using System.Web.Mail;
namespace SMTP
{
/// <summary>
/// provides methods to send email via smtp direct to mail server
/// </summary>
public class SmtpDirect
{
/// <summary>
/// Get / Set the name of the SMTP mail server
/// </summary>
public static string SmtpServer;
private enum SMTPResponse: int
{
CONNECT_SUCCESS = 220,
GENERIC_SUCCESS = 250,
DATA_SUCCESS = 354,
QUIT_SUCCESS = 221
}
public static bool Send(MailMessage message)
{
IPHostEntry IPhst = Dns.Resolve(SmtpServer);
IPEndPoint endPt = new IPEndPoint(IPhst.AddressList[0], 25);
Socket s= new Socket(endPt.AddressFamily, SocketType.Stream,ProtocolType.Tcp);
s.Connect(endPt);

if(!Check_Response(s, SMTPResponse.CONNECT_SUCCESS))
{
s.Close();
return false;
}

Senddata(s, string.Format("HELO {0}\r\n", Dns.GetHostName() ));
if(!Check_Response(s, SMTPResponse.GENERIC_SUCCESS))
{
s.Close();
return false;
}

Senddata(s, string.Format("MAIL From: {0}\r\n", message.From ));
if(!Check_Response(s, SMTPResponse.GENERIC_SUCCESS))
{

s.Close();
return false;
}

string _To = message.To;
string[] Tos= _To.Split(new char[] {';'});
foreach (string To in Tos)
{
Senddata(s, string.Format("RCPT TO: {0}\r\n", To));
if(!Check_Response(s, SMTPResponse.GENERIC_SUCCESS))
{
s.Close();
return false;
}
}

if(message.Cc!=null)
{
Tos= message.Cc.Split(new char[] {';'});
foreach (string To in Tos)
{
Senddata(s, string.Format("RCPT TO: {0}\r\n", To));
if(!Check_Response(s, SMTPResponse.GENERIC_SUCCESS))
{
s.Close();
return false;
}
}
}

StringBuilder Header=new StringBuilder();
Header.Append("From: " + message.From + "\r\n");
Tos= message.To.Split(new char[] {';'});
Header.Append("To: ");
for( int i=0; i< Tos.Length; i++)
{
Header.Append( i > 0 ? "," : "" );
Header.Append(Tos[i]);
}
Header.Append("\r\n");
if(message.Cc!=null)
{
Tos= message.Cc.Split(new char[] {';'});
Header.Append("Cc: ");
for( int i=0; i< Tos.Length; i++)
{
Header.Append( i > 0 ? "," : "" );
Header.Append(Tos[i]);
}
Header.Append("\r\n");
}
Header.Append( "Date: " );
Header.Append(DateTime.Now.ToString("ddd, d M y H:m:s z" ));
Header.Append("\r\n");
Header.Append("Subject: " + message.Subject+ "\r\n");
Header.Append( "X-Mailer: SMTPDirect v1\r\n" );
string MsgBody = message.Body;
if(!MsgBody.EndsWith("\r\n"))
MsgBody+="\r\n";
if(message.Attachments.Count>0)
{
Header.Append( "MIME-Version: 1.0\r\n" );
Header.Append( "Content-Type: multipart/mixed; boundary=unique-boundary-1\r\n" );
Header.Append("\r\n");
Header.Append( "This is a multi-part message in MIME format.\r\n" );
StringBuilder sb = new StringBuilder();
sb.Append("--unique-boundary-1\r\n");
sb.Append("Content-Type: text/plain\r\n");
sb.Append("Content-Transfer-Encoding: 7Bit\r\n");
sb.Append("\r\n");
sb.Append(MsgBody + "\r\n");
sb.Append("\r\n");

foreach(object o in message.Attachments)
{
MailAttachment a = o as MailAttachment;
byte[] binaryData;
if(a!=null)
{
FileInfo f = new FileInfo(a.Filename);
sb.Append("--unique-boundary-1\r\n");
sb.Append("Content-Type: application/octet-stream; file=" + f.Name + "\r\n");
sb.Append("Content-Transfer-Encoding: base64\r\n");
sb.Append("Content-Disposition: attachment; filename=" + f.Name + "\r\n");
sb.Append("\r\n");
FileStream fs = f.OpenRead();
binaryData = new Byte[fs.Length];
long bytesRead = fs.Read(binaryData, 0, (int)fs.Length);
fs.Close();
string base64String = System.Convert.ToBase64String(binaryData, 0,binaryData.Length);

for(int i=0; i< base64String.Length ; )
{
int nextchunk=100;
if(base64String.Length - (i + nextchunk ) <0)
nextchunk = base64String.Length -i;
sb.Append(base64String.Substring(i, nextchunk));
sb.Append("\r\n");
i+=nextchunk;
}
sb.Append("\r\n");
}
}
MsgBody=sb.ToString();
}

Senddata(s, ("DATA\r\n"));
if(!Check_Response(s, SMTPResponse.DATA_SUCCESS))
{
s.Close();
return false;
}
Header.Append( "\r\n" );
Header.Append( MsgBody );
Header.Append( ".\r\n" );
Header.Append( "\r\n" );
Header.Append( "\r\n" );
Senddata(s, Header.ToString());
if(!Check_Response(s, SMTPResponse.GENERIC_SUCCESS ))
{
s.Close();
return false;
}

Senddata(s, "QUIT\r\n");
Check_Response(s, SMTPResponse.QUIT_SUCCESS );
s.Close();
return true;
}
private static void Senddata(Socket s, string msg)
{
byte[] _msg = Encoding.ASCII.GetBytes(msg);
s.Send(_msg , 0, _msg .Length, SocketFlags.None);
}
private static bool Check_Response(Socket s, SMTPResponse response_expected )
{
string sResponse;
int response;
byte[] bytes = new byte[1024];
while (s.Available==0)
{
System.Threading.Thread.Sleep(100);
}

s.Receive(bytes, 0, s.Available, SocketFlags.None);
sResponse = Encoding.ASCII.GetString(bytes);
response = Convert.ToInt32(sResponse.Substring(0,3));
if(response != (int)response_expected)
return false;
return true;
}
}
}

Download the code that accompanies this article


 


Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.