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.