Antuan Kinnard

in

January 2006 - Posts

Sending Mail in .NET 2.0

I'm going to start off by admitting that it's been a while since I had to write any code that supported sending email. Back when I was a corporate Java/JSP developer, support for sending email was built into almost every application's framework that we wrote. We chose email as our 'notification service' because it was easier than writing Java code which allowed users and developers to receive notifications any other way. They needed a non-intrusive way to receive these notifications which meant we couldn't install any applications. Email did just that. Now as a (mostly) .NET developer, I haven't worked on any applications requiring such an architecture. Regardless, I found myself playing around with .NET's mail classes. While they are no JavaMail classes, they've been given some attention by the Microsoft developers in the .NET Framework version 2.0. For starters, the classes in the System.Net.Mail namespace - formerly System.Web.Mail - are no longer wrappers for CDOSYS. They've been re-worked from the ground up and given more functionality.

Sending email is just as simple as it's always been in the .NET Framework. It only takes about 5 to 6 lines of code to construct a message and send it off to the SMTP server. The object that sends the message has been renamed from SmtpMail to SmtpClient - a more appropriate name if you asked me. To send the message, you can use the SmtpClient.Send method which has the same signature as the previous version's. In addition to sending the message using the Send method, we also have the option of sending mail asynchrounously. Using the SmtpClient.SendAsync method, the application will not be blocked as it is with the Send method. I'm sure some developers will see this as a bell and/or whistle since using a separate thread would be just as simple. However, I think it's cool to have this functionality right out of the box. After all, aren't the bells and whistles the reasons we're all writing .NET code? Let's look at a simple example of sending email using SendAsync.

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Net.Mail;
using System.Net.Mime;
using System.ComponentModel;

namespace SimpleMail {
    class SimpleMail {
        private SmtpClient _smtpClient = null;

        public SimpleMail() {
            _smtpClient = new SmtpClient("MyRelayServer.com", 25);
        }

        public void SendAsynchronousMail(
            string toAddress,
            string fromAddress,
            string messageBody,
            string subjectText,
            IDictionary<string, Stream> attachmentStream,
            string token /* UserState */
            ) {

            //create the message
            MailMessage msg = new MailMessage(fromAddress, toAddress);
            msg.Body = messageBody;
            msg.Subject = subjectText;


            //add the attachment(s)
            if (attachmentStream != null) {
                foreach (string s in attachmentStream.Keys) {
                    Attachment
attachment = new Attachment(attachmentStream[s], MediaTypeNames.Application.Octet);
                    attachment.Name = s.ToString();
                    msg.Attachments.Add(attachment);
                }
            }

            //send an email to the reply address of any failures after all retries
            msg.DeliveryNotificationOptions =
DeliveryNotificationOptions.OnFailure;

            //attach the delegate to handle the callback

            _smtpClient.SendCompleted +=
new SendCompletedEventHandler(this.SendCompleted);

            //send the message and return immediately

            _smtpClient.SendAsync(msg, token);


            //TODO: Do not close the stream

        }

        private void SendCompleted(object sender, AsyncCompletedEventArgs e) {
            if (e.Cancelled)
                Console
.WriteLine("The message [{0}] was canceled.", e.UserState.ToString());

            if
(e.Error != null) {
                StringBuilder errorMsg = new StringBuilder();
                errorMsg.Append(
string.Format("An error occurred sending message [{0}]. {1}", e.UserState.ToString(), e.Error.Message));

                if
(e.Error.InnerException != null)
                    errorMsg.Append(
Environment.NewLine + "Inner exception message: " + e.Error.InnerException.Message);
                   
                Console
.WriteLine(errorMsg);
            }
else {
                    Console
.WriteLine("Message [{0}] sent.", e.UserState.ToString());
            }
        }

        public void CancelMail() {
            _smtpClient.SendAsyncCancel();
        }
    }
}

As you'd expect, an event handler has been added for the callback as well as an identifier, or token, for the message sent. SendCompletedEventhandler sends the AsyncCompletedEventArgs object to the SendCompleted callback method. As shown in the example code, you can use AsyncCompletedEventArgs to determine if an error occurred while sending and if the message was canceled. The UserState property will be populated with the token object that was passed to the SendAsync method.

Along with the SendAsync method comes the SendAsyncCancel method. Can anyone guess what this does? That's right, cancels the message. However, don't expect it to cancel a message that the server has already queued. Once it's there, you won't be able to use the SendAsyncCancel method to remove it from the SMTP queue.

Another change worth mentioning is more options for sending attachments. With the 2.0 version of the framework, developers now have the option of sending attachments directly from a stream. So you no longer need a file to send from because anything from a MemoryStream to a CryptoStream can be sent directly from the stream itself. Be sure if you're using a stream with the SendAsync method that you don't close the stream before the send operation is done sending the message to the server. This will cause an SmtpException exception since the method is not actually done with the stream. This leads me to my next point: When checking exceptions thrown by these classes, be sure to check the inner exception. I'm sure I don't need to tell anyone this because we all check our inner exceptions. *cough* This is an invaluable tip for anyone using this namespace. In the case of prematurely closing memory streams that is being sent as an attachment, the first exception in the chain is an SmtpException with the message, "Failure sending mail." Not very informative to those of us without psychic abilities, eh? Checking the inner exception will yield a NotSupportedException exception and the message, "Stream does not support reading."  So check and log those inner exceptions!

Now for the all-important question, "How do I receive email?" Short answer is that you can't receive email using the System.Net.Mail namespace. This is one area I think JavaMail has got .NET beat. That said, if you're up for the challenge you can write your own communication with your POP and IMAP store using sockets. If you're not up to this challenge, there are a number of components written that can save you some time here and here.

There's a lot more functionality in the System.Net.Mailnamespace that should be explored. Sending email through SSL, sending alternate ways to view the message, and delivery notifications are a few more areas worth taking a look at.  To help get you started, you can check out Dave Wanta's site at http://systemnetmail.com.  This is a pretty good FAQ about the namespace.  If you'd like a comparison with the .NET 1.1, check out http://systemwebmail.com.