Click here to Skip to main content
6,594,432 members and growing! (17,182 online)
Email Password   helpLost your password?
General Programming » Internet / Network » Email & SMTP     Beginner License: The Code Project Open License (CPOL)

MailMergeLib - A .NET Mail Client Library

By Norbert Bietsch

MailMergeLib is a SMTP mail client library. It makes use of .NET System.Net.Mail and provides comfortable mail merge capabilities. MailMergeLib corrects a number of the most annoying bugs and RFC violations that .NET 2.0 to .NET 4.0 suffer from.
C# (C# 2.0, C# 3.0), Windows (Win2K, WinXP, Win2003, Vista), .NET 2.0, ASP.NET, Visual Studio (VS2005, VS2008), Dev
Version:3 (See All)
Posted:10 Jul 2007
Updated:7 Aug 2009
Views:63,481
Bookmarked:183 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
38 votes for this article.
Popularity: 7.37 Rating: 4.66 out of 5

1
2 votes, 5.3%
2
1 vote, 2.6%
3
4 votes, 10.5%
4
31 votes, 81.6%
5
Screenshot - OutlookExpressView.png

Introduction

During the time when I worked on .NET 1.1, I built a library for doing basic mail merge in a Web Service. I started to use the mail client DotNetOpenMail by Mike Bridge which really was doing a good job.

After moving to .NET 2.0, I thought that DotNetOpenMail had become obsolete because Microsoft had introduced their System.Net.Mail library. Generally speaking, this works quite nice, but it has some annoying bugs and RFC violations - which I found out one after another, and which have not come to an end yet. At the same time, the mail merge library grew and became quite comfortable to use.

Background

Sure, with System.Net.Mail, you can send mail messages. The point is: some bugs and RFC violations will increase the spam rating of spam filters for your message, and some mail clients may even show parts as garbage. Besides providing mail merge capabilities, MailMergeLib addresses the following issues:

Bugs fixed in .NET 3.5 SP1:

  1. MailMessage.MailAddressCollection.Clear does not clear the corresponding headers. If you want to re-use an existing MailMessage object for different recipients, you will have to remove the recipients headers yourself (e.g. MailMessage.Headers.Remove("to"))
  2. Setting the transfer encoding to TransferEncoding.SevenBit turns into a header text sevenbit, instead of 7bit. sevenbit is not RFC compliant and causes problems with some mail clients.
  3. MailMessage.To.ToString() returns the encoded string only after the message was sent. According to the documentation, this should be the case no matter whether the message was already sent or not.
  4. Display names of mail addresses that are not all 7bit characters must be encoded. This works fine with every address type except for to addresses. to addresses will never be encoded, no matter which Encoding parameter is used for a new MailAddress.

Bugs not fixed up to .NET 4.0:

  1. Quoted-Printable encoding is not limited to a maximum of 76 characters in System.Net.Mail. RFC 2045 requires that Quoted-Printable encoding encodes lines be no more than 76 characters long. If longer lines are to be encoded with the Quoted-Printable encoding, "soft" line breaks must be used.
  2. Spaces in display names of a MailAddress must be encoded.
  3. With MailMessage.Headers there is a bug where headers will have white space in an encoded text. This will lead to non-RFC 2047 compliant messages.
  4. Attachments to a mail message must not have white space in the file name, which is neglected by System.Net.Mail.
  5. If the display name of an attachment contains non-ASCII characters System.Net.Mail throws an exception instead on encoding them. Anyway: The display name of an attachment must be encoded if it contains non-ASCII characters (e.g. Chinese, German Umlaut, etc.)

So although Microsoft has supplied some bug fixes, quite a few can be considered as persistent bugs without too much hope for getting them fixed. So I tried to find ways to fix them on my own. All bugfixes are included in a static class Bugfixer which hopefully won't be needed in one of the glorious future versions of .NET. Pie in the Microsoft sky?

Using the Code

For sending a mail in System.Net.Mail, you'll first create a MailMessage, and then send it with SmtpClient. In MailMergeLib this is quite similar: you'll create a MailMergeMessage and then send it with MailMergeSender.

MailMergeMessage

Create a New Message

One big advantage of MailMergeLib comes from placeholders. {Placeholders} are the field names of a DataTable embedded in any text with curly braces.

So first create the message and adjust some settings. CultureInfo is relevant for formatting placeholders that contain dates, currency or numeric data.

// create the mail message
MailMergeMessage mmm = new MailMergeMessage("My subject for {Nickname}");

// adjust mail specific settings
mmm.CharacterEncoding = Encoding.GetEncoding("iso-8859-1");
mmm.CultureInfo = new System.Globalization.CultureInfo("en-US");
mmm.TextTransferEncoding = System.Net.Mime.TransferEncoding.SevenBit;
mmm.BinaryTransferEncoding = System.Net.Mime.TransferEncoding.Base64;

Formatting Capabilities

It is possible to add standard .NET formatting attributes to your placeholders. For a date that will show the day number and the month's name, you would write: {Date:"{0:dd MMMM}"}.

In case a column of DataRow will have ExtendedProperties for null and/or format, these properties will be used. Examples:

myDataTable.Columns["Date"].ExtendedProperties.Add("format", "{0:F}");
myDataTable.Columns["Date"].ExtendedProperties.Add("null", "#Display for null#");

Of course column types and formatting attributes must fit each other.

Message Body

The message body parts can be added or changed quite easily either by using them as a parameter in the constructor, or by setting the properties:

mmm.HtmlText = new System.IO.StreamReader("HtmlBody.html").ReadToEnd();
mmm.PlainText = new System.IO.StreamReader("TextBody.html").ReadToEnd();

HtmlText and PlainText can contain placeholders. You can insert a text file by using the special syntax {IncludeFile:"file"}. IncludeFile is the column name of the DataTable, while file means to interpret the content as a file name.

Attachments

You may also want to add some personalized attachments by adding placeholders to the file name. E.g.:

mmm.FileAttachments.Add
    (new FileAttachment("testmail_{Nickname}.pdf", "sample.pdf", "application/pdf"));

And that's the way to add string attachments:

mmm.StringAttachments.Add(new StringAttachment
    ("Some programmatically created content", "file.txt", "text/plain"));

Mail Addresses

For sending a mail, we need to supply at least one recipient's address and the sender's address. Again, using placeholders makes it possible to create personalized e-mails.

// add to and from addresses
mmm.MailMergeAddresses.Add
    (new MailMergeAddress(MailAddressType.To, "<{Email}>", "{Nickname}"));
mmm.MailMergeAddresses.Add
    (new MailMergeAddress(MailAddressType.From, "<from@addr.com>", "From Name"));

Miscellaneous

If your data comes from a DataTable, it may well happen that you have recipients with empty e-mail fields. That's why you may want to set:

mmm.IgnoreEmptyRecipientAddr = true

This will then not throw an exception with empty addresses.

Want to change MailMergeLib's identification? Set...

mmm.Xmailer = "MailMergeLib 2.0"; 

... to anything you like.

MailMergeSender

In the beginning, MailMergeSender is much like System.Net.Mail.SmtpClient: Create an instance of the class and provide some SMTP related settings.

SMTP Settings

Setup the mail sender:

MailMergeSender mailSender = new MailMergeSender();
mailSender.MessageOutput = MessageOutput.SmtpServer;

Set up SMTP server login details:

mailSender.SmtpHost = "smtp.server.com";
mailSender.SmtpPort = 25;
mailSender.SetSmtpAuthentification("username", "password");
mailSender.LocalHostName = "my.localhostname.com"; // used in SMTP Hello command

EventHandlers

With SmtpClient, you cannot add custom event handlers. MailMergeSender will raise events OnBeforeSend, OnAfterSend, OnSendFailure, OnMergeBegin, OnMergeComplete, and OnMergeProgress:

mailSender.OnAfterSend += new EventHandler<MailSenderAfterSendEventArgs>(
         delegate(object obj, MailSenderAfterSendEventArgs args)
         {
                // do something useful here, like updating a progress bar
         });

Sending a Message

For the Send job, there are several alternatives:

  1. Start to send messages as an asynchronous operation, which will not block the calling thread:
    mailSender.SendAsync(mmm, myDataTable);
  2. Start to send messages as a synchronous operation. In this case, you will loop through the rows of your DataTable, supply the row as variables to the MailMergeMessage and then call Send for each row.
    foreach (DataRow dr in myDataTable.Rows)
    {
       mmm.Variables = dr;
       mailSender.Send(mmm);
    } 

    Or just send a single message providing a Dictionary with name/value pairs:

    Dictionary<string, string> nameValuePairs = new Dictionary<string, string>();
    nameValuePairs.Add("Email", "to@addr.com");
    nameValuePairs.Add("Nickname", "Bill");
    mmm.SetVariables(nameValuePairs);
    mailSender.Send(mmm);

Cancelling a Send Operation

Asynchronous Send operations can be cancelled at any time:

// cancel the pending mail merge immediately
mailSender.SendCancel();

Influencing Error Handling

Timeout in milliseconds:

mailSender.Timeout = 100000;

Maximum number of failures until sending a message will finally fail:

mailSender.MaxFailures = 3;

Retry delay time between failures:

mailSender.RetryDelayTime = 3000;

Delay time between each message:

mailSender.DelayBetweenMessages = 1000;

Conclusion

By fixing the bugs in System.Net.Mail, it was interesting to dig into its design and to supply modifications using System.Reflection. Although all the bugs mentioned were reported to Microsoft a very long time ago, they didn't remove them. Life could be so easy... Anyway, MailMergeLib works well (again, after .NET 3.5 SP1 and .NET 4.0 partly broke it).

Special Thanks To

  • .NET Reflector for providing this great tool to analyze System.Net.Mail and to find out ways to fix its bugs
  • Mike Bridge for his QPEncoder in DotNetOpenMail
  • People working on Mono for their AttachmentBase.MimeTypes
  • All users giving their feedback and votes in the forum

History

  • 2007-07-10: Initial release
  • 2007-07-11: Minor doc and code update
  • 2007-10-08: SSL support added (thanks to WPKF), GetEncodedMailAddress fixed
  • 2007-12-28: Re-designed bug fixes in BugFixer class that .NET 2.0 SP1 / .NET 3.5 broke:
    • CorrectSubjectEncodedWordRFC2047compliant
    • AddToAddressWithCorrectEncoding
    • ClearMessageHeaders
  • 2008-01-01: Improvement of the Tools class
    • CalcMailSize(MailMessage msg) now gives accurate size instead of an estimation
    • Added GetMailAsMimeString (MailMessage msg) to retrieve the message content (same as in an EML file)
  • 2008-01-12: Minor improvements
    • Tools.WrapLine(string input, int length)will now allow empty lines (thanks to woetertie)
    • QPEncoder from DotNetOpenMail might return a string 1 byte longer than RFC2027-compliant, fixed
  • 2008-05-22: Minor improvement
    • Fixed potential problem with zip files
  • 2008-08-30: Minor improvements
    • Verified: Bugs still exist in .NET 3.5 SP1, and bugfixing of MailMergeLib works with it
    • Updated documentation
  • 2009-08-02:
    • Verified: Bugs still exist in .NET 4.0, and bugfixing of MailMergeLib works with it
    • SmtpClient's private member localHostName (up to .NET 3.5 SP1) changed to clientDomain in .NET 4.0. MailMergeLib can deal with both now.
    • Fixed bug in System.Net.Mail with non-ASCII characters in the display name of an attachment (thanks to xrascal)
    • Updated documentation

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Norbert Bietsch


Member

Location: Germany Germany

Other popular Internet / Network articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 25 of 68 (Total in Forum: 68) (Refresh)FirstPrevNext
GeneralPeriod(dot) in the very first position of the line PinmemberMember 7274077:40 26 Oct '09  
GeneralRe: Period(dot) in the very first position of the line PinmemberMember 72740718:11 26 Oct '09  
AnswerRe: Period(dot) in the very first position of the line PinmemberNorbert Bietsch15:03 30 Oct '09  
GeneralGetting exception: "Field 'System.Net.Mail.SmtpClient.localHostName' not found. Pinmemberjwhurst8:29 13 Jul '09  
AnswerRe: Getting exception: "Field 'System.Net.Mail.SmtpClient.localHostName' not found. PinmemberNorbert Bietsch9:55 13 Jul '09  
GeneralRe: Getting exception: "Field 'System.Net.Mail.SmtpClient.localHostName' not found. Pinmemberjwhurst10:39 13 Jul '09  
AnswerFixed: Getting exception: "Field 'System.Net.Mail.SmtpClient.localHostName' not found. PinmemberNorbert Bietsch11:12 13 Jul '09  
GeneralRe: Getting exception: "Field 'System.Net.Mail.SmtpClient.localHostName' not found. Pinmemberfsifabio9:57 13 Jul '09  
GeneralField 'System.Net.Mail.SmtpClient.localHostName' not found. Pinmemberfsifabio7:53 13 Jul '09  
AnswerRe: Field 'System.Net.Mail.SmtpClient.localHostName' not found. PinmemberNorbert Bietsch8:25 13 Jul '09  
GeneralRe: Field 'System.Net.Mail.SmtpClient.localHostName' not found. Pinmemberfsifabio9:58 13 Jul '09  
AnswerFixed: Field 'System.Net.Mail.SmtpClient.localHostName' not found. PinmemberNorbert Bietsch11:09 13 Jul '09  
GeneralRe: Fixed: Field 'System.Net.Mail.SmtpClient.localHostName' not found. PinmemberJoe Gershgorin14:10 17 Oct '09  
GeneralA question about ShouldUseBase64Encoding. Pinmemberxrascal20:58 12 Jul '09  
AnswerRe: A question about ShouldUseBase64Encoding. PinmemberNorbert Bietsch7:53 13 Jul '09  
GeneralRe: A question about ShouldUseBase64Encoding. Pinmemberxrascal17:06 13 Jul '09  
GeneralA bug should be fixed - System.Net.Mail.Attachment Pinmemberxrascal19:11 11 Jul '09  
GeneralRe: A bug should be fixed - System.Net.Mail.Attachment Pinmemberxrascal23:25 15 Jul '09  
JokeRe: A bug should be fixed - System.Net.Mail.Attachment PinmemberNorbert Bietsch7:38 20 Jul '09  
GeneralProblems on some SMTP servers PinmemberJerk_ua1:06 4 Mar '09  
AnswerHacking System.Net.Mail.SmtpClient to enforce a certain type of authentification PinmemberNorbert Bietsch12:36 4 Mar '09  
GeneralUsing MailMergeLib in Web Service PinmemberMember 7274076:31 1 Mar '09  
AnswerRe: Using MailMergeLib in Web Service PinmemberNorbert Bietsch12:31 3 Mar '09  
GeneralRe: Using MailMergeLib in Web Service PinmemberMember 7274073:32 7 Mar '09  
GeneralDot being removed in QP encoding [modified] Pinmemberpantsonhead4:35 13 Feb '09  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 7 Aug 2009
Editor: Deeksha Shenoy
Copyright 2007 by Norbert Bietsch
Everything else Copyright © CodeProject, 1999-2009
Web18 | Advertise on the Code Project