Compose complex e-mail with multiple parts and attachments

This guide demonstrates how to send e-mails consisting of several parts. The code snippets also show various methods of how you can set sender and recipients, set SMTP relay server, use quick send, logging and other things related to composing and sending e-mail.


Send e-mail with separate HTML and plain-text parts

Well-designed HTML e-mail should also provide plain-text alternative for those e-mail readers which do not support HTML. Even if you know that all your recipients use HTML-capable e-mail programs, keep in mind that spam filters don't HTML e-mails without plain-text part.

Although MailBee.NET can generate plain-text version of your HTML body automatically, you may need to override this default behavior. For instance, if your HTML data cannot be represented as plain-text at all, and you want to inform users that they must upgrade their e-mail client to be able to see HTML version, or provide a link on your web site where they can view this e-mail online:

Smtp mailer = new Smtp();

// Enable logging, useful for trouble-shooting.
mailer.Log.Enabled = true;
mailer.Log.Filename = @"C:\Temp\log.txt";
mailer.Log.Clear();

// Use SMTP relay server with authentication.
mailer.SmtpServers.Add("mail.here.com", "john.doe", "password");

// Set From and To as EmailAddress objects.
mailer.Message.From = new EmailAddress("john.doe@here.com", "John Doe");
mailer.Message.To.Add("jane.doe@there.com", "Jane Doe");

// The idea is that the message body contains sections in different color, and
// this coloring is essential for understanding this document. Because plain-text 
// cannot display colors, it's useless in this case. Inform the user of that.
mailer.Message.Subject = "Revised document, changes marked red";
mailer.Message.BodyPlainText = "You need HTML-capable program to view this e-mail";
mailer.Message.BodyHtmlText =
    "<html><body>Old data<br/><font color=red>New data</font></body></html>";

mailer.Send();
Dim mailer As Smtp = New Smtp()

' Enable logging, useful for trouble-shooting.
mailer.Log.Enabled = True
mailer.Log.Filename = "C:\Temp\log.txt"
mailer.Log.Clear()

' Use SMTP relay server with authentication.
mailer.SmtpServers.Add("mail.here.com", "john.doe", "password")

' Set From and To as EmailAddress objects.
mailer.Message.From = New EmailAddress("john.doe@here.com", "John Doe")
mailer.Message.To.Add("jane.doe@there.com", "Jane Doe")

' The idea is that the message body contains sections in different color, and
' this coloring is essential for understanding this document. Because plain-text
' cannot display colors, it's useless in this case. Inform the user of that.
mailer.Message.Subject = "Revised document, changes marked red"
mailer.Message.BodyPlainText = "You need HTML-capable program to view this e-mail"
mailer.Message.BodyHtmlText = _
    "<html><body>Old data<br/><font color=red>New data</font></body></html>"

mailer.Send()

For all the samples, we assume MailBee, MailBee.SmtpMail and MailBee.Mime namespaces are imported and the license key is set. See Import namespaces and set license key topic for details.


Send e-mail with HTML part and auto-generated plain-text part

If you set only HTML version of the body, MailBee.NET generates plain-text version automatically:

Smtp mailer = new Smtp();

// Use SMTP relay server with authentication.
mailer.SmtpServers.Add("mail.here.com", " john.doe@here.com", "password");

// Set From and To in various ways.
mailer.Message.From.Email = "john.doe@here.com";
mailer.Message.From.DisplayName = "John Doe";
mailer.Message.To.Add(new EmailAddress("jane.doe@there.com", "Jane Doe"));

// HTML body here contains some HTML-only formatting but it's not
// crucial to understanding this document.
mailer.Message.Subject = "Rich-formatted e-mail";
mailer.Message.BodyHtmlText =
    "<html><body>Data<br/><i>Regards, John</i></body></html>";

mailer.Send();
Dim mailer As Smtp = New Smtp()

' Use SMTP relay server with authentication.
mailer.SmtpServers.Add("mail.here.com", " john.doe@here.com", "password")

' Set From and To in various ways.
mailer.Message.From.Email = "john.doe@here.com"
mailer.Message.From.DisplayName = "John Doe"
mailer.Message.To.Add(New EmailAddress("jane.doe@there.com", "Jane Doe"))

' HTML body here contains some HTML-only formatting but it's not
' crucial to understanding this document.
mailer.Message.Subject = "Rich-formatted e-mail"
mailer.Message.BodyHtmlText = _
    "<html><body>Data<br/><i>Regards, John</i></body></html>"

mailer.Send()

If you would like to produce HTML-only e-mail and disable the auto-creation of plain-text from HTML at all, add this line before mailer.Send():

mailer.Message.Builder.HtmlToPlainMode = HtmlToPlainAutoConvert.Never

Send e-mail with attachments from stream, memory, another message

To just add an attachment from a file, refer to Send HTML e-mail with attachment topic. This guide demonstrates more advanced matters like different filenames on disk and in the e-mail, memory attachments, and "forward as attachment" techniques.

This sample adds 4 attachments, each is loaded differently:

  1. From a file, the attachment receives a different filename from its original filename on disk.
  2. From a Stream object which represents a file loaded from arbitrary source like database.
  3. From a memory array (in the sample, this array is the result of WebClient.DownloadData method call).
  4. From another MailMessage object which represents another e-mail such as received from IMAP or POP3 server.
// Create MailMessage object and set headers.
MailMessage msg = new MailMessage();
msg.From.Email = "john.doe@here.com";
msg.From.DisplayName = "John Doe";
msg.To.Add(new EmailAddress("jane.doe@there.com", "Jane Doe"));
msg.Subject = "E-mail with 4 attachments";

// Add attachment from file with temp filename.
msg.Attachments.Add(@"C:\Temp\A13EF8B0.TMP", "report.doc");

// Add attachment from Stream. Here, we'll fill the stream
// with only 1 byte which is zero.
System.IO.Stream dataStream = new System.IO.MemoryStream();
dataStream.WriteByte(0);
dataStream.Position = 0;
msg.Attachments.Add(dataStream, "0.dat", null, null, null,
    NewAttachmentOptions.None, MailTransferEncoding.Base64);

// Add attachment from byte array. This byte array is the content
// of www.afterlogic.com web page.
System.Net.WebClient web = new System.Net.WebClient();
byte[] data = web.DownloadData("http://www.afterlogic.com");
msg.Attachments.Add(data, "afterlogic.html", null, null, null,
    NewAttachmentOptions.None, MailTransferEncoding.QuotedPrintable);

// Download an e-mail from IMAP server and attach it
// as forwarded message to the e-mail being constructed.
MailMessage existingMsg = MailBee.ImapMail.Imap.QuickDownloadMessage(
    "mail.domain.com", "user@domain.com", "secret", "Inbox", -1);
msg.Attachments.Add(existingMsg, null, null, null, null,
    NewAttachmentOptions.None, MailTransferEncoding.None);

// For variety's sake, use QuickSend to send the constructed e-mail.
// We could have also used regular Send as in previous samples.
Smtp.QuickSend(msg);
' Create MailMessage object and set headers.
Dim msg As MailMessage = New MailMessage()
msg.From.Email = "john.doe@here.com"
msg.From.DisplayName = "John Doe"
msg.To.Add(New EmailAddress("jane.doe", "Jane Doe"))
msg.Subject = "E-mail with 4 attachments" 

' Add attachment from file with temp filename.
msg.Attachments.Add("C:\Temp\A13EF8B0.TMP", "report.doc")

' Add attachment from Stream. Here, we'll fill the stream
' with only 1 byte which is zero.
Dim dataStream As System.IO.Stream = New System.IO.MemoryStream()
dataStream.WriteByte(0)
dataStream.Position = 0
msg.Attachments.Add(dataStream, "0.dat", Nothing, Nothing, Nothing, _
    NewAttachmentOptions.None, MailTransferEncoding.Base64)

' Add attachment from byte array. This byte array is the content
' of www.afterlogic.com web page.
Dim web As System.Net.WebClient = New System.Net.WebClient()
Dim data() As Byte = web.DownloadData("http://www.afterlogic.com")
msg.Attachments.Add(data, "afterlogic.html", Nothing, Nothing, Nothing, _
    NewAttachmentOptions.None, MailTransferEncoding.QuotedPrintable)

' Download an e-mail from IMAP server and attach it
' as forwarded message to the e-mail being constructed.
Dim existingMsg As MailMessage = MailBee.ImapMail.Imap.QuickDownloadMessage( _
    "mail.domain.com", "user@domain.com", "secret", "Inbox", -1)
msg.Attachments.Add(existingMsg, Nothing, Nothing, Nothing, Nothing, _
    NewAttachmentOptions.None, MailTransferEncoding.None)

' For variety's sake, use QuickSend to send the constructed e-mail.
' We could have also used regular Send as in previous samples.
Smtp.QuickSend(msg)

To learn more on how to reply or forward existing e-mails (received from POP3 or IMAP, loaded from file, etc), refer to Receive e-mail with POP3, forward it as attachment of another e-mail and send with SMTP and Receive e-mail with IMAP, reply to it and send via SMTP topics.


Send HTML e-mail with embedded pictures

MailBee.NET supports several methods which let you embed linked resources like pictures, CSS tables and so on, directly into the e-mail. You also have an option to embed such resources automatically.

Typically, you have an HTML file along with a set of related files like pictures, and want to import this file and all the linked files into the e-mail being constructed. That's how you can do this:

Smtp mailer = new Smtp();

// Use SMTP relay server without authentication.
mailer.SmtpServers.Add("mail.company.com");

// Set From and To in without friendly names.
mailer.Message.From.Email = "john.doe@company.com";
mailer.Message.To.Add("jane.doe@example.com");

mailer.Subject = "HTML e-mail with embedded pictures";

// Load HTML file, analyze it, attach all linked resources, generate and
// assign a Content-ID (CID) to each resource, and finally adjust HTML body
// to replace the resources' filenames with their corresponding Content-IDs.
mailer.Message.LoadBodyText(@"C:\Temp\document.html", MessageBodyType.Html, null,
    ImportBodyOptions.ImportRelatedFiles);

mailer.Send();
Dim mailer As Smtp = New Smtp()

' Use SMTP relay server without authentication.
mailer.SmtpServers.Add("mail.company.com")

' Set From and To in without friendly names.
mailer.Message.From.Email = "john.doe@company.com"
mailer.Message.To.Add("jane.doe@example.com")

mailer.Subject = "HTML e-mail with embedded pictures" 

' Load HTML file, analyze it, attach all linked resources, generate and
' assign a Content-ID (CID) to each resource, and finally adjust HTML body
' to replace the resources' filenames with their corresponding Content-IDs.
mailer.Message.LoadBodyText("C:\Temp\document.html", MessageBodyType.Html, Nothing, _
    ImportBodyOptions.ImportRelatedFiles)

mailer.Send()

ImportBodyOptions.ImportRelatedFiles flag tells MailMessage.LoadBodyText method that it also needs to embed linked resources.

Note that both the HTML document and its related files must reside on the filesystem (i.e. not on the web). All non-local references will stay as is. If you want MailBee.NET to download them from the web and embed to the e-mail just like local resources, add ImportBodyOptions.ImportRelatedFilesFromUris flag in addition to ImportBodyOptions.ImportRelatedFiles:

Smtp mailer = new Smtp();

// Use SMTP relay server with authentication.
mailer.SmtpServers.Add("mail.company.com", "john.doe@company.com", "secret");

// Set From and To as strings (with and without friendly names).
mailer.Message.From.AsString = "john.doe@company.com";
mailer.Message.To.AsString = "Jane Doe <jane.doe@example.com>";

mailer.Subject = "HTML e-mail with embedded pictures";

// Load HTML file, analyze it, attach all linked resources (downloading from the web
// if necessary), generate and assign a Content-ID to each resource, and finally adjust
// HTML body to replace the resources' original URIs with their assigned Content-IDs.
mailer.Message.LoadBodyText(@"C:\Temp\document.html", MessageBodyType.Html, null,
    ImportBodyOptions.ImportRelatedFiles |
    ImportBodyOptions.ImportRelatedFilesFromUris);

mailer.Send();
Dim mailer As Smtp = New Smtp()

' Use SMTP relay server with authentication.
mailer.SmtpServers.Add("mail.company.com", "john.doe@company.com", "secret")

' Set From and To as strings (with and without friendly names).
mailer.Message.From.AsString = "john.doe@company.com"
mailer.Message.To.AsString = "Jane Doe <jane.doe@example.com>"

mailer.Subject = "HTML e-mail with embedded pictures" 

' Load HTML file, analyze it, attach all linked resources (downloading from the web
' if necessary), generate and assign a Content-ID to each resource, and finally adjust
' HTML body to replace the resources' original URIs with their assigned Content-IDs.
mailer.Message.LoadBodyText("C:\Temp\document.html", MessageBodyType.Html, Nothing, _
    ImportBodyOptions.ImportRelatedFiles Or _
    ImportBodyOptions.ImportRelatedFilesFromUris)

mailer.Send()

It's assumed the links in the source HTML document match the locations of the files they relate to. For example, if the HTML file resides in C:\Temp folder and contains <IMG SRC="images/logo.gif"> tag, logo.gif file must reside in C:\Temp\images folder.

If the above is not the case (your HTML document and its related files are in very different locations), you can either use absolute paths in your HMTL (e.g. <IMG SRC="C:\Data\images\logo.gif">) or set the related files folder root with MessageBuilderConfig.RelatedFilesFolder property (you can access it using MailMessage.Builder.RelatedFilesFolder syntax). See Create HTML in memory and automatically embed linked resources topic for details.


Send e-mail created from web page content

The previous topic demonstrated how you can embed linked resources which can reside on the web but the HTML document itself was required to reside on the local filesystem or a network share (not on the web).

To load HTML document from a web location, add ImportBodyOptions.PathIsUri flag in addition to ImportBodyOptions.ImportRelatedFiles and ImportBodyOptions.ImportRelatedFilesFromUris flags when callingMailMessage.LoadBodyText method:

Smtp mailer = new Smtp();

// Use SMTP relay server with authentication.
mailer.SmtpServers.Add("mail.company.com", "john.doe", "secret");

// Set From and To as strings (with and without friendly names).
mailer.Message.From.AsString = "John Doe <john.doe@company.com>";
mailer.Message.To.AsString = "jane.doe@example.com";

mailer.Subject = "HTML e-mail with embedded pictures";

// Load HTML from the web, analyze it, download and attach all linked resources,
// generate and assign a Content-ID to each resource, and finally adjust HTML body
// to replace the resources' original URIs with their assigned Content-IDs.
mailer.Message.LoadBodyText("http://www.afterlogic.com/mailbee-net/smtp-component",
    MessageBodyType.Html, null,
    ImportBodyOptions.PathIsUri |
    ImportBodyOptions.ImportRelatedFiles |
    ImportBodyOptions.ImportRelatedFilesFromUris);

mailer.Send();
Dim mailer As Smtp = New Smtp()

' Use SMTP relay server with authentication.
mailer.SmtpServers.Add("mail.company.com", "john.doe", "secret")

' Set From and To as strings (with and without friendly names).
mailer.Message.From.AsString = "John Doe <john.doe@company.com>"
mailer.Message.To.AsString = "jane.doe@example.com"

mailer.Subject = "HTML e-mail with embedded pictures" 

' Load HTML from the web, analyze it, download and attach all linked resources,
' generate and assign a Content-ID to each resource, and finally adjust HTML body
' to replace the resources' original URIs with their assigned Content-IDs.
mailer.Message.LoadBodyText("http://www.afterlogic.com/mailbee-net/smtp-component", _
    MessageBodyType.Html, Nothing, _
    ImportBodyOptions.PathIsUri Or _
    ImportBodyOptions.ImportRelatedFiles Or _
    ImportBodyOptions.ImportRelatedFilesFromUris)

mailer.Send()

Note that the actual content you'll receive in the message body may be different from what you see when open the same page in the web browser. The possible reasons of that:


Create HTML in memory and automatically embed linked resources

Previous topics assumed the HTML body was imported from a file or web location and this HTML had actual references to the linked resources. In other words, we assumed that if the HTML contained <IMG SRC="pictures/logo.gif"> and we took this HTML from C:\Temp folder, the picture's physical location was C:\Temp\pictures\logo.gif. This way, MailBee.NET used the location of the HTML document as a root to resolve all relative paths to the resources referenced in this HTML.

However, if you create the HTML document directly in memory (e.g. you set MailMessage.BodyHtmlText property instead of calling MailMessage.LoadBodyText), MailBee.NET does not know the root location. You can specify it with with MessageBuilderConfig.RelatedFilesFolder property (to access it, use MailMessage.Builder.RelatedFilesFolder syntax). It works for web locations as well.

For demonstration purposes, the sample below loads the HTML data into MailMessage object not using file I/O features of MailBee.NET, then sets the root for resolving relative paths, and finally imports all the linked resources:

Smtp mailer = new Smtp();

// Use SMTP relay server with authentication.
mailer.SmtpServers.Add("mail.company.com", "john.doe@company.com", "secret");

// Set From and To as strings (without friendly names).
mailer.Message.From.AsString = "john.doe@company.com";
mailer.Message.To.AsString = "jane.doe@example.com";

mailer.Subject = "HTML e-mail with embedded pictures";

// For demonstration purposes, compose HTML body by downloading it from a web location
// (in real application, it's easier to use MailMessage.LoadBodyText method).
System.Net.WebClient web = new System.Net.WebClient();
mailer.Message.BodyHtmlText =
    web.DownloadString("http://www.afterlogic.com/mailbee-net/smtp-component");

// Set the root for all relative paths in the HTML document.
mailer.Message.Builder.RelatedFilesFolder = "http://www.afterlogic.com/mailbee-net/";

// Analyze BodyHtmlText content, download and attach all linked resources,
// generate and assign a Content-ID to each resource, and finally adjust HTML body
// to replace the resources' original URIs with their assigned Content-IDs.
mailer.Message.ImportRelatedFiles(ImportRelatedFilesOptions.ImportFromUris);

mailer.Send();
Dim mailer As Smtp = New Smtp()

' Use SMTP relay server with authentication.
mailer.SmtpServers.Add("mail.company.com", "john.doe@company.com", "secret")

' Set From and To as strings (without friendly names).
mailer.Message.From.AsString = "john.doe@company.com"
mailer.Message.To.AsString = "jane.doe@example.com"

mailer.Subject = "HTML e-mail with embedded pictures" 

' For demonstration purposes, compose HTML body by downloading it from a web location
' (in real application, it's easier to use MailMessage.LoadBodyText method).
Dim web As System.Net.WebClient = New System.Net.WebClient() 
mailer.Message.BodyHtmlText = _
    web.DownloadString("http://www.afterlogic.com/mailbee-net/smtp-component")

' Set the root for all relative paths in the HTML document.
mailer.Message.Builder.RelatedFilesFolder = "http://www.afterlogic.com/mailbee-net/" 

' Analyze BodyHtmlText content, download and attach all linked resources,
' generate and assign a Content-ID to each resource, and finally adjust HTML body
' to replace the resources' original URIs with their assigned Content-IDs.
mailer.Message.ImportRelatedFiles(ImportRelatedFilesOptions.ImportFromUris)

mailer.Send()

Create HTML in memory and manually embed linked resources

In the previous sample, we stated that the HTML is created by the application but in fact we simply downloaded an existing HTML document (we could have also used MailMessage.LoadBodyText method for the same purpose).

In case if you actually craft the HTML body contents directly in your application, can can make all the links point to Content-IDs from the very beginning instead of making "normal" links and then having MailBee.NET replace them with Content-IDs. This is faster as no extra processing of the HTML body is being made (can make a difference if your HTML document is very large).

The sample below crafts an e-mail with 1 non-embedded picture (used to track when the recipient opens the e-mail) and 2 embedded pictures, all Content-IDs for embedded pictures are created manually:

Smtp mailer = new Smtp();

// Use SMTP relay server with authentication.
mailer.SmtpServers.Add("mail.company.com", "john.doe", "secret");

// Set From and To as strings (with friendly names).
mailer.Message.From.AsString = "John Doe <john.doe@here.com>";
mailer.Message.To.AsString = "Jane Doe <jane.doe@there.com>";

mailer.Subject = "HTML e-mail with embedded and non-embedded pictures";

// Use StringBuilder object for efficient manipulation with strings.
System.Text.StringBuilder strBuilder = new System.Text.StringBuilder();
strBuilder.Append("<html><body>");

// Declare embedded pictures in HTML.
strBuilder.Append("<img src='cid:picture_01'><br/>");
strBuilder.Append("<img src='cid:picture_02'><br/>");

// We also add external (non-embedded) image. Usually, they are tiny 1x1 pixel
// pictures used to track the user's activity with the e-mail (for instance,
// to get to know when the user viewed it). We assume the assigned ID of this e-mail
// to jane.doe@there.com within your system is 100.
int i = 100;

// Once Jane Doe opens the message, her e-mail program will try to open
// http://www.domain.com/?id=100 URL to display the image,
// provided that viewing external images is enabled in Jane Doe's e-mail program.
strBuilder.Append("<img src='http://www.domain.com/?id=" + i.ToString() + "'>");

strBuilder.Append("</body></html>");
mailer.Message.BodyHtmlText = strBuilder.ToString();

mailer.Message.Attachments.Add(@"C:\Temp\logo.gif", null, "picture_01");
mailer.Message.Attachments.Add(@"C:\Temp\photo.jpg", null, "picture_02");

// The e-mail contains only graphics and cannot be presented in plain-text.
mailer.Message.BodyPlainText = "HTML-capable e-mail program needed to view this e-mail";

mailer.Send();
Dim mailer As Smtp = New Smtp()

' Use SMTP relay server with authentication.
mailer.SmtpServers.Add("mail.company.com", "john.doe", "secret")

' Set From and To as strings (with friendly names).
mailer.Message.From.AsString = "John Doe <john.doe@here.com>"
mailer.Message.To.AsString = "Jane Doe <jane.doe@there.com>"

mailer.Subject = "HTML e-mail with embedded and non-embedded pictures" 

' Use StringBuilder object for efficient manipulation with strings.
Dim strBuilder As System.Text.StringBuilder = New System.Text.StringBuilder()
strBuilder.Append("<html><body>")

' Declare embedded pictures in HTML.
strBuilder.Append("<img src='cid:picture_01'><br/>")
strBuilder.Append("<img src='cid:picture_02'><br/>")

' We also add external (non-embedded) image. Usually, they are tiny 1x1 pixel
' pictures used to track the user's activity with the e-mail (for instance,
' to get to know when the user viewed it). We assume the assigned ID of this e-mail
' to jane.doe@there.com within your system is 100.
Dim i As Integer = 100 

' Once Jane Doe opens the message, her e-mail program will try to open
' http://www.domain.com/?id=100 URL to display the image,
' provided that viewing external images is enabled in Jane Doe's e-mail program.
strBuilder.Append("<img src='http://www.domain.com/?id=" & i.ToString() & "'>")
 
strBuilder.Append("</body></html>")
mailer.Message.BodyHtmlText = strBuilder.ToString()

mailer.Message.Attachments.Add("C:\Temp\logo.gif", Nothing, "picture_01")
mailer.Message.Attachments.Add("C:\Temp\photo.jpg", Nothing, "picture_02")

' The e-mail contains only graphics and cannot be presented in plain-text.
mailer.Message.BodyPlainText = "HTML-capable e-mail program needed to view this e-mail"

mailer.Send()

You can mix automatic and manual embedding of linked resources. For instance, MailBee.NET might not automatically embed certain resource (if it was referenced from CSS rather than directly from HTML), and you want to fix this. The below is how you can do this.

After mailer.MailMessage.LoadBodyText has done its job, you can then attach the resource in question to the message and assign certain Content-ID for it, then replace the URL of this resource in mailer.MailMessage.BodyHtmlText value with "cid:" followed by the Content-ID value (it's assumed mailer is an Smtp instance).

If the resource is not local, you can use WebClient.DownloadData to download it and then attach it using mailer.MailMessage.Attachments.Add syntax (AttachmentCollection.Add method has overloads which accept byte array or stream). See Send e-mail with attachments from file, stream, memory, another message topic for details.


Send feedback to AfterLogic

Copyright © 2006-2017 AfterLogic Corporation. All rights reserved.