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 can be any
IEnumerable
object as well as DataTable
s. 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
MimeMessage
s 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.
SmptClient
s 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
SmptClient
s 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";
A rather minimal configuration looks like this:
var settings = new Settings
{
MessageConfig =
{
CharacterEncoding = Encoding.UTF8,
StandardFromAddress = new MailboxAddress("sender name", "sender@example.com"),
CultureInfo = CultureInfo.CurrentCulture,
IgnoreIllegalRecipientAddresses = true,
Xmailer = "MailMergeLib 5"
},
SenderConfig =
{
SmtpClientConfig = new[]
{
new SmtpClientConfig()
{
MessageOutput = MessageOutput.SmtpServer,
SmtpHost = "some.host.com",
SmtpPort = 587,
NetworkCredential = new Credential("user", "password"),
Name = "StandardConfig",
MaxFailures = 3,
RetryDelayTime = 1000,
DelayBetweenMessages = 0,
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) => {
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 an 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}"
"{Users:{Name}|, |, and } liked your comment"
"{Name:choose(null|):N/A|empty|{Name}}"
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 to 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:
- 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);
- 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);
- Send messages as a synchronous operation for all items in the
DataSource
:
mailSender.Send(mmm, variables);
- 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 SmtpClient
s for sending messages asynchronously.
When defining more than one SMTP configuration, each of the configurations will be assigned alternating to the SmtpClient
s. 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 but not the 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