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