Way 2 Web

Web development tips


 
Email

Message in a bottle

Sending emails is fairly straightforward in ASP .NET, unlike old ASP (or "Classic" ASP as the Microsoft marketing team has quipped) where third party components are necessary.

You'll need to use primarilly SmtpClient and MailMessage objects:

using System.Net.Mail;

...

MailMessage email = new MailMessage();

email.Subject = "My first test email";
email.SubjectEncoding = System.Text.Encoding.UTF8;
email.Body = body;
email.BodyEncoding = System.Text.Encoding.UTF8;
email.To.Add(new MailAddress("recipient@domain.com"));
email.Sender = new MailAddress("sender@domain.com");
email.From = new MailAddress("sender@domain.com");
email.Attachments.Add(new Attachment("c:\\my-attachment.pdf")); 

try {
    SmtpClient client = new SmtpClient("mail.my-domain.com", 25);   
    //  25 is the default SMTP port
    client.Send(email);
} catch (SmtpException ex) {
    ...         
}

Email Address Validation

Another common problem - validating email address input - can be handled easilly in ASP .NET

Validation is important. It reduces the amount of incorrect or fabricated email addresses clogging up your database.

At a very minimum, the format of the address should be tested, and this is typically accomplished using regular expressions.

using System.Text.RegularExpressions;

...
    
private bool EmailAddressIsValid(string email) {
    string regExPattern = @"^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}" +
                          @"\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\" +
                          @".)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$";
    Regex regEx = new Regex(regExPattern);
    return regEx.IsMatch(email);
}

Note that this can be tested client-side using Javascript as well.

Socket to 'em

But let's say you want to be extra sure that bogus addresses will not violate your precious database space.

There are two other aspects you can test, and here ASP .NET comes into its own, using its socket functionality.

(SMTP emails are sent using socket connections. In fact you can write your own version of the SmtpClient class using sockets. But why re-invent the wheel? Unless you're really bored. Or drunk. Or both.)

Domain validation

First, test whether the email domain (the part after the @) exists.

All you have to do is open a socket connection with the domain, and see what happens.

using System.Net;
using System.Net.Sockets;

...

string email = "recipient@some-domain.com";
string[] host = email.Split('@');
string hostName = host[1];
Socket socket;
try {
    IPHostEntry entry = Dns.GetHostEntry(hostName);
    IPEndPoint endPoint = new IPEndPoint(entry.AddressList[0], 25);
    socket = new Socket(endPoint.AddressFamily, SocketType.Stream, 
                            ProtocolType.Tcp);
    socket.Connect(endPoint);
    //Yippee - the email domain exists!
} catch (SocketException se) {
    //Oops - the email domain is not valid!
}

Account validation

Even if the domain exists and is willing to receive mail, we still don't know whether the mail account exists for the domain. The only way to be sure of this is to try to connect to the account.

We continue with the socket communication process that we started in the previous stage (domain validation). This is the very process used to send an email, and we stop only very short of sending email content. This requires a series of interactions with the email server. We use utility functions to help us: SendData and CheckSmtpResponse

if (!CheckSmtpResponse(SmtpResponse.CONNECT_SUCCESS)) { //account is invalid! }

//test HELO server
SendData(string.Format("HELO {0}\r\n", Dns.GetHostName()));
if (!CheckSmtpResponse(SmtpResponse.GENERIC_SUCCESS)) { //account is invalid! }

//test for sender domain on blacklist
SendData(string.Format("MAIL From: {0}\r\n", _SenderEmail));
if (!CheckSmtpResponse(SmtpResponse.GENERIC_SUCCESS)) { //account is invalid! }

//test send
SendData(string.Format("RCPT TO: {0}\r\n", email));
if (!CheckSmtpResponse(SmtpResponse.GENERIC_SUCCESS)) { //account is invalid! }

//account is valid!

//utility funtions:

void SendData(string message) {
    byte[] bytes = System.Text.Encoding.ASCII.GetBytes(message);
    socket.Send(bytes, 0, bytes.Length, SocketFlags.None);
}

enum SmtpResponse : int {
    CONNECT_SUCCESS = 220,
    GENERIC_SUCCESS = 250,
    DATA_SUCCESS = 354,
    QUIT_SUCCESS = 221
}

bool CheckSmtpResponse(SmtpResponse code) {
    string responseString;
    int responseCode;
    byte[] bytes = new byte[1024];
    while (socket.Available == 0) {
        System.Threading.Thread.Sleep(100);
    }
    socket.Receive(bytes, socket.Available, SocketFlags.None);
    responseString = System.Text.Encoding.ASCII.GetString(bytes);
    responseCode = Convert.ToInt32(responseString.Substring(0, 3));
    return responseCode.Equals(Convert.ToInt32(code));
}

Issues

  • Use of sockets requires a trust level above the default "Medium". (See our article on logging for more details on this issue.)

    In other words, if you're using a web host, you may be limited to sending email using SmtpClient and validating only the format of the email, without validating the domain or account.

  • Rumor has it that some email services (such as hotmail, yahoo, aol, etc.) accept incoming mail for any account name. Thus, account validation may not be an airtight solution.

Updates

  • 05-Oct-2006 Added omitted enum SmtpResponse to code sample. Thanks to Michael Poznecki for the tip off.