Click here to Skip to main content
12,820,306 members (30,516 online)
Click here to Skip to main content
Add your own
alternative version

Stats

494.6K views
9.6K downloads
529 bookmarked
Posted 10 Jul 2007

MailMergeLib - A .NET Mail Client Library

, 2 Jan 2017 MIT
Rate this:
Please Sign up or sign in to vote.
MailMergeLib is an SMTP template mail client library written in C# which provides comfortable mail merge capabilities and SMTP fail-over features.

Screenshot of a merged email

The Evolution

Back in 2007, after MailMergeLib was completed, it turned out that Microsoft System.Net.Mail had about 20 ugly issues. Many of them could be fixed by hacking their system with System.Reflection. The drawback was, that every new release of the .NET Framework required extensive testing and rewriting the bug fixes.

The bugs were reported to Microsoft, and eventually half of them were corrected.

In the beginning of 2015 Jeffrey Stedfast introduced the first releases of his MimeKit and MailKit open source libraries. As he wrote in his Code Review he was convinced that there could be something better than System.Net.Mail. And he really succeeded - not only in generating RFC compliant outgoing mail messages, but also in providing IMAP and POP3 facilities. This was the starting point for rewriting the code of MailMergeLib, now using MailKit and MimeKit instead of System.Net.Mail.

Introduction

There are numerous occasions where an application has to send e-mails: Feedback forms in the web, webmailers, loggers and so forth. And there are many commercial solutions available: EASendMail SMTP Component, MailBee.NET SMTP Component, Aspose.Network.Mail or aspNetEmail, just to mention some of them. If you're looking for a free solution with source code, supplying comparable features, you'll find it worthwhile to have a closer look at MailMergeLib:

Mail message generation

  • Email templates can be fully individualized in terms of recipients, subject, HTML and/or plain text, attachments and even headers. Placeholders are inserted as variable names from data source between curly braces like so: {MailboxAddress.Name} or with formatting arguments like {Date:yyyy-MM-dd}. (In the first releases of MailMergeLib this was accomplished with regular expressions. Now the extremely fast SmartFormat parser is implemented.)
  • HTML text may contain images from local hard disk, which will be automatically inserted as inline attachments.
  • For HTML text MailMergeLib can generate a plain text representation.
  • Attachment sources can be files, streams or strings.
  • The data source for email merge messages to a number of recipients and be any IEnumerable object as well as DataTables. The data source for single emails can be any of the following types: Dictionary<string,object>, ExpandoObject, DataRow, any class instances or anonymous types. For class instances it's even allowed to use the name of parameter less methods.
  • Placeholders in the email can be formatted with any of the features known from string.Format by using SmartFormat.NET. SmartFormat is a parser coming close to string.Format's speed, but bringing a lot of additional options like easy pluralization for many languages.
  • Resulting emails are MimeMessages from MimeKit, an outstanding tool for creating and parsing emails, covering all relevant MIME standards making sure that emails are not qualified as SPAM.
  • Support for international email address format.

Sending email messages

  • Practically unlimited number of parallel tasks to send out individualized emails to a big number of recipients.
  • SmptClients for each task can get their own preconfigured settings, so that e.g. several mail servers can be used for one send job.
  • Progress of processing emails can easily be observed with a number of events.
  • SMTP failures can automatically be resolved supplying a backup configuration. This fault-tolerance is essential for unattended production systems.
  • SMTP configuration can be stored/restored to/from standard XML files.
  • Emails are sent using the SmptClients from MailKit, the sister project to MimeKit. MailKit is highly flexible and can be configured for literally every scenario you can think of.
  • Instead of sending, emails can also be stored in MIME formatted text files, e.g. if a "pickup directory" from IIS or Microsoft Exchange shall be used. If needed, these files can be loaded back into a MimeMessage from MimeKit.

General

  • Fine grained control over the whole process of email message generation and distribution.
  • Clearly out-performs .NET System.Net.Mail.
  • Configuration settings for messages and SMTP can be stored to and loaded from an XML file.
  • RFC standards compliant.
  • We ask you not to use MailMergeLib for sending unsolicited bulk email.

Using the Code

Configuration

Basic settings

The configuration of MailMergeLib consists of two major parts: MessageConfig and SenderConfig.

First of all set the key which is used to encrypt and decrypt network credentials when the configuration is saved to file. This is not absolutely safe, but better than showing credentials as plain text:

Settings.CryptoKey = "SecretCryptoKey";  // don't use this key

A rather minimal configuration looks like this:

var settings = new Settings
{
    MessageConfig =
    {
        CharacterEncoding = Encoding.UTF8,
        StandardFromAddress = new MailboxAddress("sender name", "sender@example.com"),
        // necessary for proper formatting of placeholders:
        CultureInfo = CultureInfo.CurrentCulture,
        // especially in case a placeholder results in an empty address:
        IgnoreIllegalRecipientAddresses = true,
        Xmailer = "MailMergeLib 5"
    },
    SenderConfig =
    {
        SmtpClientConfig = new[]
        {
            new SmtpClientConfig()
            {
                MessageOutput = MessageOutput.SmtpServer,
                SmtpHost = "some.host.com",
                SmtpPort = 587,
                NetworkCredential = new Credential("user", "password"),
                // identify active configuration in case you have more than one
                Name = "StandardConfig",
                // number of trials until an exception is thrown:
                MaxFailures = 3,  
                // number of milliseconds before a retry to send will happen:
                RetryDelayTime = 1000,   
                // number of milliseconds between messages:
                DelayBetweenMessages = 0, 
                // used in SMTP Hello command
                ClientDomain = "mail.example.com"  
            }
        }
    }
};

Settings can be saved and restored:

var settings = Settings.Deserialize("MailMergeLib.config");
settings.Serialize("MailMergeLib.config");

Event handlers

MailMergeSender allows for custom event handlers. MailMergeSender will raise events OnBeforeSend, OnAfterSend, OnSendFailure, OnMergeBegin, OnMergeComplete, and OnMergeProgress. Example:

var mms = new MailMergeSender {Config = settings.SenderConfig};
mms.OnAfterSend += (sender, args) => { // do something useful here };

Create a new message

The Data Source for a Mail Message

Of course the data source is the most important topic for mail merging.

First let's create a Dictionary which will contain the values for {placeholders} in the message.  Placeholders are the names in curly braces of e.g. IList item properties, or the column names of a data table.

var variables = new Dictionary<string, object>() { { "Email", "sample@example.com" }, {"Name", "John Specimen"} };

{placeholders} can be used in email addresses, in the subject, in the plain text or html body, in text attachment content and in attachment names (including embedded images of the html body).

So in case of 2 recipients you could create an anonymous type:

var variables = new[]
     {
        new {Email = "sample1@example.com", Name = "John Specimen"}
        new {Email = "sample2@example.com", Name = "Mary Specimen"}
     };

When using O/R mappers, an entity field could look like this: Department.Leader.FirstName. So you could use the field name as a placeholder with "dot notation": {Department.Leader.FirstName}.

Message Body

Next we create a new message with a subject, plain text and a HTML body part.

var mmm = new MailMergeMessage("Personal subject for {Name}", "This is pure text for {Name}",
            "<html><head><title>No title</title></head><body>This is HTML text for {Name}</body></html>") {Config = settings.MessageConfig};

In case the HTML part contains an image, MailMergeLib will automatically add it to the email as a "linked resource". The image source must contain the path to a local file. The path may contain {placeholders} as well.

<img src="file:///full-path-to-your-image-file.jpeg" alt="Image" width="100" height=100"/>

Instead of supplying the full path for each image, it's possible to set the path for images: mmm.Config.FileBaseDirectory = "Path-to-images";
You want to see how the final message will look like? Just save it to a file and explore it with a text editor or your email application (e.g. Microsoft Outlook or Mozilla Thunderbird):

mmm.GetMimeMessage(variables).WriteTo("enter-your-filename-here.eml");

If the plain text part should not be supplied manually, MailMergeLib can do the work to convert HTML to plain text:

mmm.PlainText = mmm.ConvertHtmlToPlainText();

To do this job MailMergeMessage supplies the ParsingHtmlConverter for converting HTML to plain text. It uses the open source library AngleSharp^ and has a similar approach like Markdown^ by John Gruber. It converts the most common HTML tags and attributes which appear in emails, not on web pages, to plain text with quite good results. If AngleSharp is not available or fails, the very basic RegExHtmlConverter is used (similar to this solution^), unless you provide your custom converter implementing IHtmlConverter.

Formatting Capabilities

The placeholders and the formatting capabilities allows separation of code and design. In order to change the content of an email just change the template file(s) for the plain or HTML text and you're done without a need to touch your code.

Generally, formatting of {placeholders} is fully compatible with string.Format by means of SmartFormat.Net which is built into MailMergeLib. For details please refer to the SmartFormat Wiki. SmartFormat.Net is exposed as the SmartFormatter property of a MailMergeMessage.

Here are some examples which have an equivalent to string.Format:

"{Date:yyy-MM-dd}"  for a variable of type DateTime
"You are visitor number {NumOfVisitors}"  for a variable of type int
"{Name} from {Address.City}, {Address.State}"  for a variable which is an instance of class "user"

Formatting extensions coming with SmartFormat.Net:

"You have {emails.Count} new {emails.Count:message|messages}"  // pluralization
"{Users:{Name}|, |, and } liked your comment"  // a list of users
"{Name:choose(null|):N/A|empty|{Name}}"  // assuming an object with property "Name" which can be null, empty or a string

Attachments

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

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

And that's the way to add string attachments:

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

Mail Addresses

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

mmm.MailMergeAddresses.Add(new MailMergeAddress(MailAddressType.From, "whatever@example.com"));
mmm.MailMergeAddresses.Add(new MailMergeAddress(MailAddressType.To, "{Name}", "{Email}"));

Miscellaneous

If you're using a data source with variables, it may well happen that you have recipients with empty e-mail fields. That's why you may want to not throw an exception with empty addresses.

mmm.Config.IgnoreEmptyRecipientAddr = true;

Want to change MailMergeLib's identification? Set the mail's Xmailer header to anything you like.

mmm.Config.Xmailer = "MailMergeLib 5.0";

Sending a Message

Using the MailMergeSender is quite straight forward: Create instance of the class and provide some settings explained in the Configuration chapter.

Creating a configured MailMergeSender

Setup the mail sender:

var mailSender = new MailMergeSender {Config = settings.SenderConfig};

Start the transfer

For the Send job, there are several alternatives:

  1. Send a single message as an asynchronous operation:
    var dict = new Dictionary<string, object>() { { "Email", "sample@example.com" }, {"Name", "John Specimen"} };
    mailSender.SendAsync(mmm, (object) dict);
  2. Send all messages for all items in the DataSource as an asynchronous operation:
    var variables = new[]
       {
         new {Email = "sample1@example.com", Name = "John Specimen"}
         new {Email = "sample2@example.com", Name = "Mary Specimen"}
       };
     mailSender.SendAsync(mmm, variables);
  3. Send messages as a synchronous operation for all items in the DataSource:
    mailSender.Send(mmm, variables);
  4. Or just send a single message providing a Dictionary with name/value pairs synchronously:
    mailSender.Send(mmm, (object) dict);

SMTP Fail-over Configuration

When you define more than one SMTP configurations in the SenderConfig, then the first SMTP configuration will be the standard one used to send messages. The second one will be used in case sending over the standard configuration will fail.

This can be extremely useful in unattended environments (e.g. a scheduled task on a web server).

SenderConfig =
{
    SmtpClientConfig = new[]
    {
        new SmtpClientConfig()
        {
            MessageOutput = MessageOutput.SmtpServer,
            SmtpHost = "some.host.com",
            SmtpPort = 25,
            NetworkCredential = new Credential("user", "password"),
            Name = "Standard Config",
            MaxFailures = 3,
            DelayBetweenMessages = 500
        },
        new SmtpClientConfig()
        {
            MessageOutput = MessageOutput.SmtpServer,
            SmtpHost = "some.otherhost.com",
            SmtpPort = 587,
            NetworkCredential = new Credential("user2", "password2"),
            Name = "Backup Config",
            DelayBetweenMessages = 1000
        }
    },
    MaxNumOfSmtpClients = 5
}

Using more than one SmtpClient for Sending Messages

Setting mailSender.Config.MaxNumOfSmtpClients = 5 will use five parallel SmtpClients for sending messages asynchronously.

When defining more than one SMTP configuration, each of the configurations will be assigned alternating to the SmtpClients. Fail-over will also work slightly differently: in case of failure, the first configuration other than the current one will be used.

Cancelling a Send Operation

Asynchronous Send operations can be cancelled at any time:

mailSender.SendCancel();

Influencing Error Handling

Timeout in milliseconds:

mailSender.Config.Timeout = 100000;

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

mailSender.Config.MaxFailures = 3;

Retry delay time between failures:

mailSender.Config.RetryDelayTime = 3000;

Delay time between each message:

mailSender.Config.DelayBetweenMessages = 1000;

Conclusion

The new version of MailMergeLib is using MimeKit and MailKit. They are excellent open source libraries which give a very fine grained control over the whole process of email message generation and distribution. And last not least they produce RFC standards compliant email messages.

It is recommended to migrate from former versions to MailMergeLib version 5.

Special Thanks To

  • Jeffrey Stedfast for his MimeKit and MailKit open source libraries
  • Florian Rappl for his open source AngleSharp HTML parser
  • John Gruber for sharing his Markdown xslt-based text-to-HTML conversion tool
  • All users giving their feedback and votes in the forum

Releases

  • 2007-07-10: Initial public release MailMergeLib 2.0 using System.Net.Mail
  • 2009-12-23: MailMergeLib 3.0 using System.Net.Mail
  • 2010-05-01: MailMergeLib 4.0 using System.Net.Mail
  • 2016-09-04: MailMergeLib 5.0 - a major rewrite using MimeKit and MailKit for low level operations
  • 2016-10-23: Since MailMergeLib 5.1.0 also supporting .Net Core

The latest version of MailMergeLib is available on GitHub: https://github.com/axuno/MailMergeLib

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

axuno
Germany Germany
No Biography provided

You may also be interested in...

Comments and Discussions

 
BugImage with same name embled in HTML serveral time will not be show up. Pin
Vik beo16-Mar-17 22:36
memberVik beo16-Mar-17 22:36 
BugAfter Send method Inline Attachment file is locked and can't be deleted Pin
pit2714-Mar-17 12:51
memberpit2714-Mar-17 12:51 
QuestionUndeliverable notification Pin
Member 1302000823-Feb-17 8:30
memberMember 1302000823-Feb-17 8:30 
QuestionRe: Undeliverable notification Pin
axuno4-Mar-17 13:20
memberaxuno4-Mar-17 13:20 
QuestionBroken link download old MailmergeLib 4.3 Pin
FranCastillo15-Dec-16 0:42
memberFranCastillo15-Dec-16 0:42 
AnswerRe: Broken link download old MailmergeLib 4.3 Pin
axuno2-Jan-17 11:10
memberaxuno2-Jan-17 11:10 
QuestionGood job Pin
SophieVerdier15-Nov-16 23:24
memberSophieVerdier15-Nov-16 23:24 
QuestionCatalog of E-Mail texts Pin
Chidd8-Nov-16 0:42
memberChidd8-Nov-16 0:42 
AnswerRe: Catalog of E-Mail texts Pin
axuno8-Nov-16 13:14
memberaxuno8-Nov-16 13:14 
QuestionFileNotFoundException with MailMergeLib from NuGet Pin
Chidd4-Nov-16 5:14
memberChidd4-Nov-16 5:14 
AnswerRe: FileNotFoundException with MailMergeLib from NuGet Pin
axuno7-Nov-16 9:47
memberaxuno7-Nov-16 9:47 
GeneralRe: FileNotFoundException with MailMergeLib from NuGet Pin
Chidd8-Nov-16 0:30
memberChidd8-Nov-16 0:30 
QuestionMessage Removed Pin
Member 167816-Sep-16 0:41
memberMember 167816-Sep-16 0:41 
QuestionSSL Support? Pin
Member 167816-Sep-16 0:41
memberMember 167816-Sep-16 0:41 
AnswerRe: SSL Support? Pin
axuno16-Sep-16 8:34
memberaxuno16-Sep-16 8:34 
QuestionPossible to add the Mergelib 5 Project to an asp.net 4 Project Pin
Jaime Premy14-Sep-16 12:06
professionalJaime Premy14-Sep-16 12:06 
AnswerRe: Possible to add the Mergelib 5 Project to an asp.net 4 Project Pin
axuno15-Sep-16 21:21
memberaxuno15-Sep-16 21:21 
PraiseRe: Possible to add the Mergelib 5 Project to an asp.net 4 Project Pin
Jaime Premy16-Sep-16 4:20
professionalJaime Premy16-Sep-16 4:20 
SuggestionRe: Possible to add the Mergelib 5 Project to an asp.net 4 Project Pin
axuno16-Sep-16 8:36
memberaxuno16-Sep-16 8:36 
GeneralRe: Possible to add the Mergelib 5 Project to an asp.net 4 Project Pin
Jaime Premy16-Sep-16 17:39
professionalJaime Premy16-Sep-16 17:39 
AnswerRe: Possible to add the Mergelib 5 Project to an asp.net 4 Project Pin
axuno16-Sep-16 22:14
memberaxuno16-Sep-16 22:14 
GeneralRe: Possible to add the Mergelib 5 Project to an asp.net 4 Project Pin
Jaime Premy17-Sep-16 4:39
professionalJaime Premy17-Sep-16 4:39 
GeneralRe: Possible to add the Mergelib 5 Project to an asp.net 4 Project Pin
Jaime Premy17-Sep-16 5:50
professionalJaime Premy17-Sep-16 5:50 
QuestionLikes the article, but I found a couple of typos you may want to fix. Pin
pharry2213-Sep-16 16:55
memberpharry2213-Sep-16 16:55 
AnswerRe: Likes the article, but I found a couple of typos you may want to fix. Pin
axuno15-Sep-16 21:14
memberaxuno15-Sep-16 21:14 
GeneralMy vote of 5 Pin
Member 123643901-Sep-16 22:09
memberMember 123643901-Sep-16 22:09 
GeneralRe: My vote of 5 Pin
axuno2-Sep-16 5:55
memberaxuno2-Sep-16 5:55 
QuestionGreat to hear you release a new version. But I can not compile the code on GitHub. Pin
Vik beo1-Sep-16 7:13
memberVik beo1-Sep-16 7:13 
AnswerRe: Great to hear you release a new version. But I can not compile the code on GitHub. Pin
axuno1-Sep-16 10:12
memberaxuno1-Sep-16 10:12 
GeneralRe: Great to hear you release a new version. But I can not compile the code on GitHub. Pin
Vik beo2-Sep-16 16:57
memberVik beo2-Sep-16 16:57 
AnswerRe: Great to hear you release a new version. But I can not compile the code on GitHub. Pin
axuno3-Sep-16 3:51
memberaxuno3-Sep-16 3:51 
GeneralRe: Great to hear you release a new version. But I can not compile the code on GitHub. Pin
Vik beo3-Sep-16 4:05
memberVik beo3-Sep-16 4:05 
GeneralRe: Great to hear you release a new version. But I can not compile the code on GitHub. Pin
Vik beo20-Sep-16 23:54
memberVik beo20-Sep-16 23:54 
AnswerRe: Great to hear you release a new version. But I can not compile the code on GitHub. Pin
axuno23-Sep-16 12:43
memberaxuno23-Sep-16 12:43 
GeneralRe: Great to hear you release a new version. But I can not compile the code on GitHub. Pin
Vik beo24-Sep-16 0:43
memberVik beo24-Sep-16 0:43 
GeneralRe: Great to hear you release a new version. But I can not compile the code on GitHub. Pin
Vik beo28-Oct-16 6:28
memberVik beo28-Oct-16 6:28 
GeneralRe: Great to hear you release a new version. But I can not compile the code on GitHub. Pin
axuno28-Oct-16 22:47
memberaxuno28-Oct-16 22:47 
GeneralRe: Great to hear you release a new version. But I can not compile the code on GitHub. Pin
Vik beo28-Oct-16 22:52
memberVik beo28-Oct-16 22:52 
AnswerRe: Great to hear you release a new version. But I can not compile the code on GitHub. Pin
axuno28-Oct-16 22:37
memberaxuno28-Oct-16 22:37 
QuestionVery Nice Lib Pin
M,AqibShehzad31-Aug-16 20:01
professionalM,AqibShehzad31-Aug-16 20:01 
AnswerRe: Very Nice Lib Pin
axuno1-Sep-16 10:02
memberaxuno1-Sep-16 10:02 
QuestionSo what a purpose of library? Pin
Thornik29-Aug-16 13:09
memberThornik29-Aug-16 13:09 
AnswerRe: So what a purpose of library? Pin
axuno31-Aug-16 9:06
memberaxuno31-Aug-16 9:06 
GeneralRe: So what a purpose of library? Pin
Thornik31-Aug-16 10:31
memberThornik31-Aug-16 10:31 
Questiondownload link problem Pin
yrodev29-Aug-16 0:14
memberyrodev29-Aug-16 0:14 
AnswerRe: download link problem Pin
axuno31-Aug-16 8:27
memberaxuno31-Aug-16 8:27 
Questionusage on 64bit System possible ? Pin
NG-web - Ihr EDV Spezialist17-Jan-15 6:15
memberNG-web - Ihr EDV Spezialist17-Jan-15 6:15 
AnswerRe: usage on 64bit System possible ? Pin
Norbert Bietsch19-Jan-15 8:01
memberNorbert Bietsch19-Jan-15 8:01 
QuestionThe app.config file is missing Pin
Matt Esterak14-Jan-15 5:27
memberMatt Esterak14-Jan-15 5:27 
AnswerRe: The app.config file is missing Pin
Norbert Bietsch14-Jan-15 9:24
memberNorbert Bietsch14-Jan-15 9:24 

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.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170308.1 | Last Updated 2 Jan 2017
Article Copyright 2007 by axuno
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid