A better direct SMTP mailer
Wayne posted an interesting little SMTP snippet recently and I had been poking around at that, so I thought I'd share this... It is an adaptation of several articles scattered around through the years, his code, and several other things I wrote a while ago. Enjoy...
  namespace SMTP     /// <summary>             if (string.IsNullOrEmpty(inputEmail))             // WLF: RegEx check             const string strRegex = @"^(([a-zA-Z0-9_\-\+/\^]+)([\.]?)([a-zA-Z0-9_\-\+/\^]+))+@((\[[0-9]{1,3}" +             if (!re.IsMatch(inputEmail))             // WWB: Check To Make Sure The Email Address Parses             MailAddress mailAddress;             if (tryLookup)                 if (DnsMx.GetMXRecords(mailAddress.Host).Length == 0)             if (tryConnect)                 try                     var ipHostEntry = Dns.GetHostEntry(mxRecords[0]);                     // WLF: Attempting to connect                     if (!CheckResponse(s, SmtpResponse.ConnectSuccess))                     // WLF: HELO server                     Senddata(s, string.Format("HELO {0}\r\n", Dns.GetHostName()));                     // WLF: Say who it is from                      Senddata(s, string.Format("MAIL From: {0}\r\n", fromEmail));                     // WLF: Try to send 
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.IO;
    using System.Net;
    using System.Net.Mail;
    using System.Net.Sockets;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading;
    /// provides methods to send email via smtp direct to mail server
    /// </summary>
    public class SmtpDirect
    {
        /// <summary>
        /// Determines whether the specified input email is email.
        /// </summary>
        /// <param name="inputEmail">The input email.</param>
        /// <param name="fromEmail">From email.</param>
        /// <param name="tryLookup">if set to <c>true</c> [try lookup].</param>
        /// <param name="tryConnect">if set to <c>true</c> [try connect].</param>
        /// <returns>
        ///     <c>true</c> if the specified input email is email; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsEmail(string inputEmail, string fromEmail, bool tryLookup, bool tryConnect)
        {
            // WLF: Check for null or empty input
            {
                return false;
            }
                                    @"\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\" +
                                    @".)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$";
            var re = new Regex(strRegex);
            {
                return false;
            }
            try
            {
                mailAddress = new MailAddress(inputEmail);
            }
            catch
            {
                return false;
            }
            {
                // WWB: Check To Make Sure There Is a SMTP Server To Recieve The Email Address
                {
                    return false;
                }
            }
            {
                // WLF: Try to connect to the SMTP server
                {
                    var mxRecords = DnsMx.GetMXRecords(mailAddress.Host);
                    if (mxRecords.Length == 0)
                    {
                        return false;
                    }
                    var endPoint = new IPEndPoint(ipHostEntry.AddressList[0], 25);
                    var s = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                    s.Connect(endPoint);
                    {
                        s.Close();
                        return false;
                    }
                    if (!CheckResponse(s, SmtpResponse.GenericSuccess))
                    {
                        s.Close();
                        return false;
                    }
                    if (!CheckResponse(s, SmtpResponse.GenericSuccess))
                    {
                        s.Close();
                        return false;
                    }
                    Senddata(s, string.Format("RCPT TO: {0}\r\n", inputEmail));
                    if (!CheckResponse(s, SmtpResponse.GenericSuccess))
                    {
                        s.Close();
                        return false;
                    }
                }
                catch
                {
                    return false;
                }
            }
// WWB: Success
            return true;
        }
        /// <summary>
        /// Sends the mail directly to the receiver's server.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="errors">The errors.</param>
        /// <returns></returns>
        public static bool SendDirect(MailMessage message, out List<string> errors)
        {
            errors = new List<string>();
            var deliveryAddresses = new List<MailAddress>();
            // WLF: Add To's
            foreach (var address in message.To)
            {
                deliveryAddresses.Add(address);
            }
            // WLF: Add CC's
            foreach (var address in message.CC)
            {
                deliveryAddresses.Add(address);
            }
            foreach (var to in deliveryAddresses)
            {
                var mxRecords = DnsMx.GetMXRecords(to.Host);
                if (mxRecords.Length == 0)
                {
                    errors.Add(to.Host);
                }
                else
                {
                    var ipHostEntry = Dns.GetHostEntry(mxRecords[0]);
                    var endPoint = new IPEndPoint(ipHostEntry.AddressList[0], 25);
                    var s = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                    s.Connect(endPoint);
                    if (!CheckResponse(s, SmtpResponse.ConnectSuccess))
                    {
                        s.Close();
                        return false;
                    }
                    Senddata(s, string.Format("HELO {0}\r\n", Dns.GetHostName()));
                    if (!CheckResponse(s, SmtpResponse.GenericSuccess))
                    {
                        s.Close();
                        return false;
                    }
                    Senddata(s, string.Format("MAIL From: {0}\r\n", message.From));
                    if (!CheckResponse(s, SmtpResponse.GenericSuccess))
                    {
                        s.Close();
                        return false;
                    }
                    Senddata(s, string.Format("RCPT TO: {0}\r\n", to.Address));
                    if (!CheckResponse(s, SmtpResponse.GenericSuccess))
                    {
                        s.Close();
                        return false;
                    }
#region Build message body
var header = GetHeader(message);
                    var msgBody = message.Body;
                    if (!msgBody.EndsWith("\r\n"))
                        msgBody += "\r\n";
#region Handle attachments
                    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");
                        var 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 (var a in message.Attachments)
                        {
                            if (a == null) continue;
                            var f = new FileInfo(a.Name);
                            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");
                            var fs = f.OpenRead();
                            var binaryData = new Byte[fs.Length];
                            long bytesRead = fs.Read(binaryData, 0, (int) fs.Length);
                            Trace.WriteLine(string.Format("Bytes read: {0}", bytesRead));
                            fs.Close();
                            var base64String = Convert.ToBase64String(binaryData, 0, binaryData.Length);
                            for (var i = 0; i < base64String.Length;)
                            {
                                var 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();
                    }
#endregion
#endregion
#region Send message body
                    Senddata(s, ("DATA\r\n"));
                    if (!CheckResponse(s, SmtpResponse.DataSuccess))
                    {
                        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 (!CheckResponse(s, SmtpResponse.GenericSuccess))
                    {
                        s.Close();
                        return false;
                    }
                    Senddata(s, "QUIT\r\n");
                    CheckResponse(s, SmtpResponse.QuitSuccess);
                    s.Close();
                    #endregion
                }
            }
            return errors.Count == 0;
        }
        /// <summary>
        /// Sends the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="outboundSmtpServer">The outbound SMTP server.</param>
        /// <returns></returns>
        public static bool Send(MailMessage message, string outboundSmtpServer)
        {
            var ipHostEntry = Dns.GetHostEntry(outboundSmtpServer);
            var endPoint = new IPEndPoint(ipHostEntry.AddressList[0], 25);
            var s = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            s.Connect(endPoint);
            if (!CheckResponse(s, SmtpResponse.ConnectSuccess))
            {
                s.Close();
                return false;
            }
            Senddata(s, string.Format("HELO {0}\r\n", Dns.GetHostName()));
            if (!CheckResponse(s, SmtpResponse.GenericSuccess))
            {
                s.Close();
                return false;
            }
            Senddata(s, string.Format("MAIL From: {0}\r\n", message.From));
            if (!CheckResponse(s, SmtpResponse.GenericSuccess))
            {
                s.Close();
                return false;
            }
            foreach (var to in message.To)
            {
                Senddata(s, string.Format("RCPT TO: {0}\r\n", to.Address));
                if (!CheckResponse(s, SmtpResponse.GenericSuccess))
                {
                    s.Close();
                    return false;
                }
            }
            foreach (var cc in message.CC)
            {
                Senddata(s, string.Format("RCPT TO: {0}\r\n", cc.Address));
                if (!CheckResponse(s, SmtpResponse.GenericSuccess))
                {
                    s.Close();
                    return false;
                }
            }
var header = GetHeader(message);
            var msgBody = message.Body;
            if (!msgBody.EndsWith("\r\n"))
                msgBody += "\r\n";
#region Handle attachments
            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");
                var 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 (var a in message.Attachments)
                {
                    if (a == null) continue;
                    var f = new FileInfo(a.Name);
                    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");
                    var fs = f.OpenRead();
                    var binaryData = new Byte[fs.Length];
                    long bytesRead = fs.Read(binaryData, 0, (int) fs.Length);
                    Trace.WriteLine(string.Format("Bytes read: {0}", bytesRead));
                    fs.Close();
                    var base64String = Convert.ToBase64String(binaryData, 0, binaryData.Length);
                    for (var i = 0; i < base64String.Length;)
                    {
                        var 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();
            }
#endregion
            Senddata(s, ("DATA\r\n"));
            if (!CheckResponse(s, SmtpResponse.DataSuccess))
            {
                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 (!CheckResponse(s, SmtpResponse.GenericSuccess))
            {
                s.Close();
                return false;
            }
            Senddata(s, "QUIT\r\n");
            CheckResponse(s, SmtpResponse.QuitSuccess);
            s.Close();
            return true;
        }
        /// <summary>
        /// Gets the header.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <returns></returns>
        private static StringBuilder GetHeader(MailMessage message)
        {
            var header = new StringBuilder();
header.Append("From: " + message.From + "\r\n");
            var tos = string.Empty;
            foreach (var to in message.To)
            {
                tos = to.Address + ",";
            }
            header.AppendFormat("To: {0}\r\n", tos.TrimEnd(','));
            if (message.CC.Count > 0)
            {
                var ccs = string.Empty;
                foreach (var cc in message.CC)
                {
                    ccs = cc.Address + ",";
                }
                header.AppendFormat("Cc: {0}\r\n", ccs.TrimEnd(','));
            }
            header.AppendFormat("Date: {0}\r\n", DateTime.Now.ToString("ddd, d M y H:m:s z"));
            header.AppendFormat("Subject: {0}\r\n", message.Subject);
            header.Append("X-Mailer: SMTPDirect v1\r\n");
            return header;
        }
#region Socket Operations
        /// <summary>
        /// Sends data to the specified socket.
        /// </summary>
        /// <param name="socket">The socket.</param>
        /// <param name="message">The message.</param>
        private static void Senddata(Socket socket, string message)
        {
            var messageBytes = Encoding.ASCII.GetBytes(message);
            socket.Send(messageBytes, 0, messageBytes.Length, SocketFlags.None);
        }
        /// <summary>
        /// Checks the response.
        /// </summary>
        /// <param name="socket">The socket.</param>
        /// <param name="responseExpected">The response expected.</param>
        /// <returns></returns>
        private static bool CheckResponse(Socket socket, SmtpResponse responseExpected)
        {
            var bytes = new byte[1024];
            while (socket.Available == 0)
            {
                Thread.Sleep(100);
            }
            socket.Receive(bytes, 0, socket.Available, SocketFlags.None);
            var sResponse = Encoding.ASCII.GetString(bytes);
            var response = Convert.ToInt32(sResponse.Substring(0, 3));
            return response == (int) responseExpected;
        }
#endregion
#region DNS
        /// <summary>
        /// DNS MX Lookup
        /// </summary>
        public class DnsMx
        {
            /// <summary>
            /// Executes a DNS Query
            /// </summary>
            /// <param name="pszName">Name of the PSZ.</param>
            /// <param name="wType">Type of the query.</param>
            /// <param name="options">The query options.</param>
            /// <param name="aipServers">The aip servers.</param>
            /// <param name="ppQueryResults">The pp query results.</param>
            /// <param name="pReserved">The p reserved.</param>
            /// <returns></returns>
            [DllImport("dnsapi", EntryPoint = "DnsQuery_W", CharSet = CharSet.Unicode, SetLastError = true,
                ExactSpelling = true)]
            private static extern int DnsQuery([MarshalAs(UnmanagedType.VBByRefStr)] ref string pszName,
                                               QueryTypes wType, QueryOptions options, int aipServers,
                                               ref IntPtr ppQueryResults, int pReserved);
            /// <summary>
            /// Grabs the DNS Record List
            /// </summary>
            /// <param name="pRecordList">The p record list.</param>
            /// <param name="FreeType">Type of the free.</param>
            [DllImport("dnsapi", CharSet = CharSet.Auto, SetLastError = true)]
            private static extern void DnsRecordListFree(IntPtr pRecordList, int FreeType);
            /// <summary>
            /// Gets the MX records.
            /// </summary>
            /// <param name="domain">The domain.</param>
            /// <returns></returns>
            public static string[] GetMXRecords(string domain)
            {
                var ptr1 = IntPtr.Zero;
                IntPtr ptr2;
                MXRecord recMx;
                if (Environment.OSVersion.Platform != PlatformID.Win32NT)
                {
                    throw new NotSupportedException();
                }
                var list1 = new List<string>();
                var num1 = DnsQuery(ref domain, QueryTypes.DnsTypeMX, QueryOptions.DnsQueryBypassCache, 0, ref ptr1, 0);
                if (num1 != 0)
                {
                    throw new Win32Exception(num1);
                }
                for (ptr2 = ptr1; !ptr2.Equals(IntPtr.Zero); ptr2 = recMx.PNext)
                {
                    recMx = (MXRecord) Marshal.PtrToStructure(ptr2, typeof (MXRecord));
                    if (recMx.WType != 15) continue;
                    var text1 = Marshal.PtrToStringAuto(recMx.PNameExchange);
                    list1.Add(text1);
                }
                DnsRecordListFree(ptr1, 0);
                return list1.ToArray();
            }
#region Nested type: MXRecord
            [StructLayout(LayoutKind.Sequential)]
            private struct MXRecord
            {
                public IntPtr PNext;
                public string PName;
                public short WType;
                public short WDataLength;
                public int Flags;
                public int DwTtl;
                public int DwReserved;
                public IntPtr PNameExchange;
                public short WPreference;
                public short Pad;
            }
#endregion
#region Nested type: QueryOptions
            /// <summary>
            /// Query Options
            /// </summary>
            private enum QueryOptions
            {
                //DnsQueryAcceptTruncatedResponse = 1,
                /// <summary>
                /// DNS Query Bypass Cache
                /// </summary>
                DnsQueryBypassCache = 8,
                //DnsQueryDontResetTtlValues = 0x100000,
                //DnsQueryNoHostsFile = 0x40,
                //DnsQueryNoLocalName = 0x20,
                //DnsQueryNoNetbt = 0x80,
                //DnsQueryNoRecursion = 4,
                //DnsQueryNoWireQuery = 0x10,
                //DnsQueryReserved = -16777216,
                //DnsQueryReturnMessage = 0x200,
                //DnsQueryStandard = 0,
                //DnsQueryTreatAsFqdn = 0x1000,
                //DnsQueryUseTcpOnly = 2,
                //DnsQueryWireOnly = 0x100
            }
#endregion
#region Nested type: QueryTypes
            /// <summary>
            /// Query Types
            /// </summary>
            private enum QueryTypes
            {
                //DnsTypeA = 1,
                //DnsTypeNs = 2,
                //DnsTypeCname = 5,
                //DnsTypeSoa = 6,
                //DnsTypePtr = 12,
                //DnsTypeHinfo = 13,
                /// <summary>
                /// DNS Type MX
                /// </summary>
                DnsTypeMX = 15,
                //DnsTypeTxt = 16,
                //DnsTypeAaaa = 28
            }
            #endregion
        }
#endregion
#region Nested type: SmtpResponse
        /// <summary>
        /// Smtp Response
        /// </summary>
        private enum SmtpResponse
        {
            /// <summary>
            /// Connect Success
            /// </summary>
            ConnectSuccess = 220,
            /// <summary>
            /// Generic Success
            /// </summary>
            GenericSuccess = 250,
            /// <summary>
            /// Data Success
            /// </summary>
            DataSuccess = 354,
            /// <summary>
            /// Quit Success
            /// </summary>
            QuitSuccess = 221
        }
        #endregion
    }
}
Comments
Comments are closed