Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#
Article

SMTP: MailMessage done right

Rate me:
Please Sign up or sign in to vote.
4.82/5 (7 votes)
25 Feb 20055 min read 125.8K   803   47   33
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.

C#
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


Written By
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 bullshit and spell properly.

Comments and Discussions

 
GeneralPickup Folder Pin
filsdufaso9-Apr-09 6:19
professionalfilsdufaso9-Apr-09 6:19 
I am writting an application and don t have a clue on how to use the
pickup folder using an MTA.
Is this the string for pickup folder?
//em.Send(@"C:\InetPub\MailRoot\Pickup");
GeneralRe: Pickup Folder Pin
Peter Wone12-Apr-09 22:49
Peter Wone12-Apr-09 22:49 
GeneralExcellent, only a recommendation Pin
freedeveloper4-Jan-09 6:49
professionalfreedeveloper4-Jan-09 6:49 
GeneralSecurity consideration Pin
Andreas Saurwein12-Oct-06 16:58
Andreas Saurwein12-Oct-06 16:58 
JokeRe: Security consideration Pin
Jorge Varas8-Dec-06 5:03
Jorge Varas8-Dec-06 5:03 
GeneralRe: Security consideration Pin
Peter Wone16-Dec-07 18:14
Peter Wone16-Dec-07 18:14 
GeneralSmtpClient Pin
dimpant19-Sep-05 1:05
dimpant19-Sep-05 1:05 
GeneralRe: SmtpClient Pin
Peter Wone16-Dec-07 18:12
Peter Wone16-Dec-07 18:12 
Generalmalformed sender address Pin
Member 10585724-May-05 8:50
Member 10585724-May-05 8:50 
GeneralRe: malformed sender address Pin
Peter Wone4-May-05 12:09
Peter Wone4-May-05 12:09 
GeneralRe: malformed sender address Pin
Member 10585724-May-05 12:16
Member 10585724-May-05 12:16 
GeneralRe: malformed sender address Pin
Member 10585724-May-05 12:19
Member 10585724-May-05 12:19 
GeneralRe: malformed sender address Pin
Peter Wone4-May-05 12:32
Peter Wone4-May-05 12:32 
GeneralRe: malformed sender address Pin
Member 10585724-May-05 12:47
Member 10585724-May-05 12:47 
GeneralRe: malformed sender address Pin
Peter Wone4-May-05 13:04
Peter Wone4-May-05 13:04 
GeneralRe: malformed sender address Pin
Member 10585724-May-05 13:13
Member 10585724-May-05 13:13 
GeneralRe: malformed sender address Pin
Peter Wone4-May-05 13:16
Peter Wone4-May-05 13:16 
GeneralRe: malformed sender address Pin
Member 10585725-May-05 5:46
Member 10585725-May-05 5:46 
GeneralUpdates Pin
Peter Wone5-May-05 13:44
Peter Wone5-May-05 13:44 
GeneralRFC Pin
Anonymous6-Apr-05 6:26
Anonymous6-Apr-05 6:26 
GeneralRe: RFC Pin
Peter Wone3-May-05 16:31
Peter Wone3-May-05 16:31 
QuestionWhat's a payload?? Pin
Lord Kixdemp26-Mar-05 5:46
Lord Kixdemp26-Mar-05 5:46 
AnswerRe: What's a payload?? Pin
Jan Limpens1-May-05 8:28
Jan Limpens1-May-05 8:28 
AnswerRe: What's a payload?? Pin
Jan Limpens1-May-05 15:33
Jan Limpens1-May-05 15:33 
GeneralRe: What's a payload?? Pin
Lord Kixdemp2-May-05 7:15
Lord Kixdemp2-May-05 7:15 

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

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