Click here to Skip to main content
Click here to Skip to main content

SMTP: MailMessage done right

, 25 Feb 2005
Rate this:
Please Sign up or sign in to vote.
HTML email payload from a URL.

New goodies

One reader reported intermittent socket exceptions. I suspect this to be a result of his server insisting on authentication and my simplistic unauthenticated Send blindly continuing with inappropriate responses until the server drops the connection.

To address this situation, I have extended EmailMessage to support ESMTP authentication. The new method signature is EmailMessage.Send(host,port,username,password) and this method should make the class far more useful in corporate environments that typically do not permit unauthenticated relay.

The original socket based Send was naive and bereft of error checking; that said, it worked faultlessly in all the test environments available to me. Nevertheless, the source code including the test harness has been updated, and both of the socket based Send methods now implement a fair bit of error checking, as well as being thoroughly instrumented to write the proceedings of the entire socket conversation to the debug console.

The authenticated Send method even checks whether the basic authentication method is available from the server and throws a very specific exception when the required protocol is not available. This is because the SMTP server provided with WinXP purports to support ESMTP but in fact does not support basic authentication.

If you want maximum throughput, then once you have this bedded down in your environment, I suggest compiling a release build or commenting out the instrumentation.

Introduction

Automated email falls into two primary categories (apart from spam).

  • Notification of system events internal to an organisation, for which plaintext email is satisfactory.
  • Commercial communications - account statements, invoices, product information sheets, acknowledgements etc.

Commercial communications require a higher standard of presentation. There are various ways to accomplish this. The most robust in the face of recipient technical incompetence is HTML formatted messages.

Microsoft supplies SmtpServer and MailMessage classes in the DNF. For plain-text messages, these are satisfactory. If you run a quick search on these two classes, either on the web or in a newsgroup, you will find a great many people who want to know how to compose the payload from a URL (something you can do with CDO).

Why would you want to do this? Well, an ASP or ASPX page can take parameters and talk to a database, and on the whole represents a very sophisticated mail merge engine; it can even do localisation.

Unfortunately, for commercial communications, SmtpServer and MailMessage are useless. You can assign MailMessage.Body a string containing HTML, but unless this stands alone - no references to graphics or linked stylesheets - the message will be a mess when read offline. Even if it's read with a live net connection, the references had better be absolute or they aren't going to work.

CDO will composite the payload of an email from a URL, embedding stylesheets and graphics as a multipart MIME stream and doing fixups of the URLS to refer to MIME content identifiers.

This, however, involves COM interop and can get mucky. Also, the MimeOLE COM object (also used by Internet Explorer, Outlook Express and Outlook) has a few bugs. In particular, Microsoft forgot about the possibility that a stylesheet might also refer to files, for example, a background watermark image is typically specified in this way.

Moreover, link references to stylesheets embedded as independent content in a multipart mime stream do not work with OWA (Outlook Web Access). The solution here is to compose all the linked stylesheets into a single stylesheet and then embed this in a STYLE block in the HEAD block of the message. This prevents OWA from omitting it, although Hotmail manages to strip it out (Hotmail is evil).

Implementation notes

The upshot of all this is that a class is required to subsume the functionality of MailMessage and SmtpServer, but which can composite the payload of the message from a URL, consolidating stylesheets and embedding them directly into the HTML, non-redundantly mime encoding graphical content and performing fixups on the references.

Encoding

A miracle occurs as detailed in the source code. I won't go into MIME encoding or message formats. This stuff is all in RFC1521 if you care. One gotcha: boundary markers have two leading dashes that boundary declarations don't have. This is often not obvious because common practice puts a lot of dashes at the start of the boundary marker.

Transport

There are two ways to palm off the message once it's ready to send. One is to write it as a file into the pickup directory of a MTA (mail transport agent) and the other is to establish a socket to an SMTP relay host on port 25 and have a little chat about headers and payload in SMTP-ese. EmailMessage.Send() implements both methods as overloads. If you supply a string and an integer, it assumes you are passing host, port. If you pass just a string, it assumes you are passing the pickup folder path. UNC is acceptable.

Files into a pickup folder has the benefit of speed; some relay hosts throttle their connections so as not to choke the network (die spammers die we hate you) and you may find it faster to write files. Also, your mail host may be configured not to allow SMTP relay. Files from the pickup folder don't count as relay; they've originated inside the system.

Par contre, you may not have file system access to the mail host's pickup folder, making sockets a better option. If you don't have either, it's time for a chat with your friendly local sysadmin. If your process lives physically on the mail server, you could point out that loopback is actually defined for a whole class A subnet (127.x.x.x) so he can allow relay for say 127.53.103.113 (he'd probably rather do that than grant filesystem write permission for a web app).

Using EmailMessage

I've supplied it as source for an assembly, although I haven't bothered to strong-name it. This is because it's the sort of thing you'll want to add to various projects. If you were to involve my code in an abomination like cut-and-paste inheritance, you might traumatise it, and then I'd be forced to hunt you down.

Here's some code from the test harness.

using System;
using System.Collections.Specialized;
using System.Configuration;
using pdconsec.EmailSupport;

namespace ConsoleApplication1 {
    class Class1 {
        [STAThread]
        static void Main(string[] args) {
            NameValueCollection AS = 
                ConfigurationSettings.AppSettings;
            EmailMessage em = new EmailMessage();
            em.RecipientAddress = AS["RecipientAddress"];
            em.RecipientName = AS["RecipientDisplayName"];
            em.SenderAddress = AS["SenderAddress"];
            em.SenderName = AS["SenderDisplayName"];
            em.Subject = AS["EmailSubject"];
            em.Url = AS["URL"];
            em.Send(AS["SmtpServer"],25);
            //em.Send(AS["SmtpServer"],25,"ausername","apassword");
            //em.Send(@"C:\InetPub\MailRoot\Pickup");
        }
    }
}

You are expected to rewrite the app.config to point at your local SMTP relay host and supply valid email addresses. XP comes with one. Install it, it's handy for testing stuff like this. You don't have to keep it running all the time.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Peter Wone
Web Developer
Australia Australia
On a personal level (this is a bio, after all) I love gourmet coffee and my wine collection is pretty good. My passion is alpine skiing. On a more technical level, I've been a Delphi/MSSQL hack for donkey's years. VB is a distant memory. It resurfaced as VB.NET but I managed not to get any on me. Java was nice. C# is nicer. Pet peeve: kids today don't appear to know what an RFC is. Or how to spell. My one consolation is that no matter what they get away with at school and on message boards, with a compiler they have to stop their bullsh*t and spell properly.

Comments and Discussions

 
GeneralPickup Folder Pinmemberdazology9-Apr-09 6:19 
GeneralRe: Pickup Folder PinmemberPeter Wone12-Apr-09 22:49 
GeneralExcellent, only a recommendation Pinmemberfreedeveloper4-Jan-09 6:49 
GeneralSecurity consideration PinmemberSaurweinAndreas12-Oct-06 16:58 
JokeRe: Security consideration PinmemberJorge Varas8-Dec-06 5:03 
GeneralRe: Security consideration PinmemberPeter Wone16-Dec-07 18:14 
GeneralSmtpClient Pinmemberdimitris.dpant19-Sep-05 1:05 
GeneralRe: SmtpClient PinmemberPeter Wone16-Dec-07 18:12 
Generalmalformed sender address Pinmemberwsweetman4-May-05 8:50 
GeneralRe: malformed sender address PinmemberPeter Wone4-May-05 12:09 
GeneralRe: malformed sender address Pinmemberwsweetman4-May-05 12:16 
GeneralRe: malformed sender address Pinmemberwsweetman4-May-05 12:19 
GeneralRe: malformed sender address PinmemberPeter Wone4-May-05 12:32 
GeneralRe: malformed sender address Pinmemberwsweetman4-May-05 12:47 
GeneralRe: malformed sender address PinmemberPeter Wone4-May-05 13:04 
GeneralRe: malformed sender address Pinmemberwsweetman4-May-05 13:13 
GeneralRe: malformed sender address PinmemberPeter Wone4-May-05 13:16 
GeneralRe: malformed sender address Pinmemberwsweetman5-May-05 5:46 
GeneralUpdates PinmemberPeter Wone5-May-05 13:44 
GeneralRFC PinsussAnonymous6-Apr-05 6:26 
GeneralRe: RFC PinmemberPeter Wone3-May-05 16:31 
QuestionWhat's a payload?? PinmemberEl-Trucha1526-Mar-05 5:46 
AnswerRe: What's a payload?? PinsussJan Limpens1-May-05 8:28 
AnswerRe: What's a payload?? Pinmemberjan.limpens1-May-05 15:33 
GeneralRe: What's a payload?? PinmemberEl-Trucha152-May-05 7:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140827.1 | Last Updated 25 Feb 2005
Article Copyright 2005 by Peter Wone
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid