OpaqueMail Library Tutorial

Bert Johnson edited this page Jan 6, 2017 · 2 revisions

It's easy to get started with the OpaqueMail .NET library.

  1. Downloading the latest version
  2. Using the test client
  3. Sending email with SMTP
  4. Handling email with IMAP
  5. Handling email with POP3
  6. Advanced techniques

Downloading the latest version

The latest release of the OpaqueMail .NET library is always available on GitHub from https://github.com/bertjohnson/OpaqueMail/zipball/master.

The OpaqueMail .NET library is based on Microsoft .NET 4.5.

Using the test client

The OpaqueMail .NET library ships with a fully-featured test client named OpaqueMail.Net.TestClient.exe.

The test client can be used to send test messages with or without S/MIME signing and encryption. These can then be accessed via IMAP and POP3 interfaces, which load messages with all images embedded and scripts removed. The test client supports multiple encodings and character sets.

A. Enter OpaqueMail .NET test client settings

The first step in using the test client is to enter your server settings. By default, SMTP, IMAP, and POP3 settings are listed for Gmail.

If you plan to use the OpaqueMail .NET test client multiple times, you may save and load your server settings using the buttons on the bottom.

B. Load and view IMAP or POP3 messages

Using the "IMAP" or "POP3" tabs, first click "Retrieve Messages". Up to 25 of the most recent messages will load asynchronously.

Once loaded, select a message's title from the lefthand panel. This will load the email's properties and a preview of the message.

C. Send S/MIME signed or encrypted messages via SMTP

Under the SMTP tab, messages can be composed and sent to one or more recipient. Attachments can be specified, the full path of each on its own line.

The OpaqueMail .NET test client supports sending S/MIME signed, encrypted, or triple-wrapped messages if the sender has a valid email certificate (by specifying the hexadecimal serial number). To obtain a certificate, view the tutorial.

Sending email with SMTP:

The Simple Mail Transfer Protocol (SMTP) is the ubiquitous standard for sending email.

1. Sending a standard email

The OpaqueMail.Net.SmtpClient inherits from the System.Net.Mail.SmtpClient base class. It adds multiple properties and methods for working with S/MIME messages.

If sending a standard, unsigned and unencrypted message, either of the System.Net.Mail.SmtpClient or OpaqueMail.Net.SmtpClient classes can be used with the Send() or SendAsync() methods. As you'll see in upcoming examples, differences only emerge when working with S/MIME messages.

// Instantiate a new SMTP connection to Gmail using TLS/SSL protection.
SmtpClient smtpClient = new SmtpClient("smtp.gmail.com", 587);
smtpClient.Credentials = new NetworkCredential("username@gmail.com", "Pass@word1");
smtpClient.EnableSsl = true;

// Create a new MailMessage class with lorem ipsum.
MailMessage message = new MailMessage("username@gmail.com", "user@example.com", "Example subject", "Lorem ipsum body.");

// Send the message asynchronously.
await smtpClient.SendAsync(message);

2. Sending an S/MIME signed email

Sending an S/MIME signed message is very similar to sending a standard email message using the SmtpClient class.

To specify that the message should be signed, first set the SmimeSigned property to true. You can then (optionally) choose signing options such as whether the signature should be timestamped using the Timestamp Protocol (TSP).

Finally, an S/MIME signing certificate needs to be assigned. This can be simplified using the CertHelper class's GetCertificateBySerialNumber or GetCertificateBySubjectName methods.

// Instantiate a new SMTP connection to Gmail using TLS/SSL protection.
SmtpClient smtpClient = new SmtpClient("smtp.gmail.com", 587);
smtpClient.Credentials = new NetworkCredential("username@gmail.com", "Pass@word1");
smtpClient.EnableSsl = true;

// Create a new MailMessage class with lorem ipsum.
MailMessage message = new MailMessage("username@gmail.com", "user@example.com", "Example subject", "Lorem ipsum body.");

// Specify that the message should be S/MIME signed, including its timestamp.
message.SmimeSigned = true;
message.SmimeSigningOptionFlags = SmimeSigningOptionFlags.SignTime;

// Load the signing certificate from the Local Machine store.            
message.SmimeSigningCertificate = CertHelper.GetCertificateBySubjectName(StoreLocation.LocalMachine, "username@gmail.com");

// Send the message.
await smtpClient.SendAsync(message);

3. Sending an S/MIME encrypted email

Sending an S/MIME message with its envelope encrypted resembles sending a signed message using the SmtpClient class.

To specify that the message's envelope should be encrypted, simply set the SmimeEncryptedEnvelope property to true, then use Send() or SendAsync() as normal.

// Instantiate a new SMTP connection to Gmail using TLS/SSL protection.
SmtpClient smtpClient = new SmtpClient("smtp.gmail.com", 587);
smtpClient.Credentials = new NetworkCredential("username@gmail.com", "Pass@word1");
smtpClient.EnableSsl = true;

// Create a new MailMessage class with lorem ipsum.
MailMessage message = new MailMessage("username@gmail.com", "user@example.com", "Example subject", "Lorem ipsum body.");

// Specify that the message's envelope should be S/MIME encrypted.
message.SmimeEncryptedEnvelope = true;

// Send the message.
await smtpClient.SendAsync(message);

4. Sending an S/MIME triple-wrapped email

Triple-wrapped messages provide additional safeguards over simply encrypted S/MIME messages. To triple wrap a message, it is first signed, then that signed message is encrypted, and finally that encrypted portion is again signed. This provides additional assurance regarding the message's origin and that it has not been tampered with.

Sending a triple-wrapped S/MIME message combines the steps of signing and encrypting a message using the SmtpClient class. In addition to setting the SmimeSigned and SmimeEncryptedEnvelope properties to true, the SmimeTripleWrapped property needs to be true.

// Instantiate a new SMTP connection to Gmail using TLS/SSL protection.
SmtpClient smtpClient = new SmtpClient("smtp.gmail.com", 587);
smtpClient.Credentials = new NetworkCredential("username@gmail.com", "Pass@word1");
smtpClient.EnableSsl = true;

// Create a new MailMessage class with lorem ipsum.
MailMessage message = new MailMessage("username@gmail.com", "user@example.com", "Example subject", "Lorem ipsum body.");

// Specify that the message should be signed, have its envelope encrypted, and then be signed again (triple-wrapped).
message.SmimeSigned = true;
message.SmimeEncryptedEnvelope = true;
message.SmimeTripleWrapped = true;

// Specify that the message should be timestamped.
message.SmimeSigningOptionFlags = SmimeSigningOptionFlags.SignTime;

// Load the signing certificate from the Local Machine store.            
message.SmimeSigningCertificate = CertHelper.GetCertificateBySubjectName(StoreLocation.LocalMachine, "username@gmail.com");

// Send the message.
await smtpClient.SendAsync(message);

Handling email with IMAP:

Internet Message Access Protocol (IMAP) is a common option for email query and retrieval. It offers more capabilities than POP3 and continues to evolve. IMAP is supported by most enterprise hosts.

1. Connecting to an IMAP server

Connecting to an IMAP server with the OpaqueMail .NET library requires only a few steps. It requires instantiating an ImapClient class, calling the Connect() method, and calling the Authenticate() method.

// Instantiate a new IMAP connection to Gmail using TLS/SSL protection.
ImapClient imapClient = new ImapClient("imap.gmail.com", 993, "username@gmail.com", "Pass@word1", true);
imapClient.Connect();
imapClient.Authenticate();

2. Retrieving a list of IMAP messages

IMAP messages can be retrieved in bulk using the ImapClient class. The GetMessages() and GetMessagesAsync() methods are overloaded with multiple options to retrieve a certain number of messages, in either ascending or descending order, with either headers only or entire messages.

// Instantiate a new IMAP connection to Gmail using TLS/SSL protection.
ImapClient imapClient = new ImapClient("imap.gmail.com", 993, "username@gmail.com", "Pass@word1", true);
imapClient.Connect();
imapClient.Authenticate();

// Retrieve up to 50 of the most recent messages from the "Inbox" mailbox.
List<MailMessage> recentMessages = imapClient.GetMessages("Inbox", 50);

// Retrieve up to 10 of the most recent messages from the "Inbox" mailbox asynchronously, with headers only.
List<MailMessage> messagesWithHeadersOnly = await imapClient.GetMessagesAsync("Inbox", 10, true);

// Retrieve up to 25 messages from the "Drafts" mailbox in reverse order, not setting the "Seen" flag.
List<MailMessage> messagesInReverse = imapClient.GetMessages("Drafts", 25, 0, true, false, false);

3. Downloading a specific IMAP message

IMAP messages can be downloaded individually using the ImapClient class.

The GetMessage() and GetMessageAsync() methods are overloaded with multiple options to retrieve a certain message by its index on the server. The GetMessageUid() and GetMessageUidAsync() methods do the same based on a message's Unique ID (UID). A boolean determines whether the entire message is returned or only its headers.

// Instantiate a new IMAP connection to Gmail using TLS/SSL protection.
ImapClient imapClient = new ImapClient("imap.gmail.com", 993, "username@gmail.com", "Pass@word1", true);
imapClient.Connect();
imapClient.Authenticate();

// Retrieve the 5th most recent message.
MailMessage fifthIndexMessage = imapClient.GetMessage(5);

// Retrieve the most recent message asynchronously, with headers only.
MailMessage messageHeadersOnly = await imapClient.GetMessageAsync("Inbox", 1, true);

// Retrieve the message with UID 57.
MailMessage uidlMessage = imapClient.GetMessageUid(57);

4. Searching IMAP messages

Similar to simply retrieving messages using the GetMessages() and GetMessagesAsync() methods, the ImapClient class offers the Search() and SearchAsync() methods for querying and retrieving messages.

Queries are defined directly as well-formatted strings in accordance with the IMAP standard.

// Instantiate a new IMAP connection to Gmail using TLS/SSL protection.
ImapClient imapClient = new ImapClient("imap.gmail.com", 993, "username@gmail.com", "Pass@word1", true);
imapClient.Connect();
imapClient.Authenticate();

// Search the headers and body of each message asynchronously for the string "opaque".
List<MailMessage> searchOpaqueMessages = await imapClient.SearchAsync("TEXT \"opaque\"");

// Search each message for a sender with the word "Bert".
List<MailMessage> searchBertMessages = imapClient.Search("FROM \"Bert\"");

5. Deleting IMAP messages

IMAP messages can be deleted individually or as an array using the ImapClient class.

The DeleteMessage(), DeleteMessageAsync(), DeleteMessages(), and DeleteMessagesAsync() delete one or more messages identified by index. The DeleteMessageUid(), DeleteMessageUidAsync(), DeleteMessagesUid(), and DeleteMessagesUidAsync() delete one or more messages identified by UID.

// Instantiate a new IMAP connection to Gmail using TLS/SSL protection.
ImapClient imapClient = new ImapClient("imap.gmail.com", 993, "username@gmail.com", "Pass@word1", true);
imapClient.Connect();
imapClient.Authenticate();

// Delete the 7th most recent message from the Inbox mailbox.
imapClient.DeleteMessage("Inbox", 7);

// Delete the most recent message asynchronously.
await imapClient.DeleteMessageAsync("Inbox", 1);

// Delete the message with UID 215.
imapClient.DeleteMessageUid("Inbox", 215);

// Delete the 2nd, 3rd, and 5th most recent messages in the Drafts mailbox.
imapClient.DeleteMessages("Drafts", new int[] { 2, 3, 5 });

6. Getting a list of IMAP capabilities

IMAP has evolved substantially and server implementations have widely varying capabilities. Built-in OpaqueMail .NET library functions check for server capabilities before using them. For example, the library checks if the server supports the "Move" command before moving messages. If so, it uses that; otherwise, it first copies the message then deletes the original.

Sometimes, it may be useful to check a server's capability directly. This can be done through the ImapClient class's GetCapabilities() method, which returns a string array of server capabilities. Alternatively, there are several properties that start with Supports* (such as SupportsIdle) to check a specific capability directly.

These capabilities are calculated upon logging into the IMAP server, so there are no roundtrips needed to verify a capability.

// Instantiate a new IMAP connection to Gmail using TLS/SSL protection.
ImapClient imapClient = new ImapClient("imap.gmail.com", 993, "username@gmail.com", "Pass@word1", true);
imapClient.Connect();
imapClient.Authenticate();

// Get and output the IMAP server version and list of capabilities.
string imapVersion;
string[] capabilities = imapClient.GetCapabilities(out imapVersion);
Console.WriteLine("IMAP Server Version: " + imapVersion);
int capabilityIndex = 0;
foreach (string capability in capabilities)
    Console.WriteLine("Capability #" + (++capabilityIndex) + ": " + capability);

// Alternatively, check for specific capabilities.
Console.WriteLine("Supports Idle: " + imapClient.SupportsIdle);
Console.WriteLine("Supports Multi-Append: " + imapClient.SupportsMultiAppend);
Console.WriteLine("Supports UID+: " + imapClient.SupportsUIDPlus);

7. Appending IMAP messages

The OpaqueMail .NET library's ImapClient class supports individual message appending through AppendMessage() and AppendMessageAsync, as well as batch message appending with AppendMessages() and AppendMessagesAsync().

When using AppendMessages() or AppendMessagesAsync(), the client first checks to see if the server supports IMAP's "MULTIAPPEND" extension, and uses that if so. If not, each message is appended individually.

// Instantiate a new IMAP connection to Gmail using TLS/SSL protection.
ImapClient imapClient = new ImapClient("imap.gmail.com", 993, "username@gmail.com", "Pass@word1", true);
imapClient.Connect();
imapClient.Authenticate();

// Append an individual message.
imapClient.AppendMessage("INBOX",
    @"Date: " + DateTime.Now.ToString("dd-MM-yyyy hh:mm:ss zzzz") + @"
    From: Test User <username@gmail.com>
    To: Me <user@example.com>
    Message-Id: <" + Guid.NewGuid().ToString().Replace("{", "").Replace("}", "") + @"@gmail.com>
    Subject: Test Append Message
    MIME-Version: 1.0
    Content-Type: Text/Plain; Charset=US-ASCII

    This is a test of the APPEND command.", new string[] { @"\Seen" }, DateTime.Now);

// Append multiple message.
await imapClient.AppendMessagesAsync("INBOX", messageArray);

8. Copying IMAP messages

Messages can be copied between IMAP mailboxes using the ImapClient's CopyMessage(), CopyMessageAsync(), CopyMessageUid(), and CopyMessageUidAsync() methods.

// Instantiate a new IMAP connection to Gmail using TLS/SSL protection.
ImapClient imapClient = new ImapClient("imap.gmail.com", 993, "username@gmail.com", "Pass@word1", true);
imapClient.Connect();
imapClient.Authenticate();

// Copy the 2nd message in the "Inbox" mailbox to the user's "Memories" mailbox asynchronously.
await imapClient.CopyMessageAsync("Inbox", 2, "Memories");

// Copy the message with UID "410" from the "Inbox" mailbox to the "Junk" mailbox.
imapClient.CopyMessageUid("Inbox", 410, "Junk");

9. Working with IMAP mailboxes

The OpaqueMail .NET library's ImapClient class supports numerous options for working with mailboxes.

Mailboxes can be selected with the SelectMailbox() and SelectMailboxAsync() methods. Certain methods that accept "mailboxName" as a parameter (such as GetMessage()) automatically perform a SelectMailbox() or SelectMailboxAsync() when needed.

Mailboxes can be deployed with the CreateMailbox() and CreateMailboxAsync() methods. Mailboxes can then be deleted using DeleteMailbox() and DeleteMailboxAsync(). Messages marked for deletion can be discarded using the ExpungeMailbox() and ExpungeMailboxAsync() methods.

ListMailboxes() and ListMailboxesAsync() return arrays of Mailbox objects with information about each whereas ListMailboxNames() and ListMailboxNamesAsync() return arrays of the mailbox names only.

If the extension is available, OpaqueMail uses the "XLIST" verb. ExamineMailbox() and ExamineMailboxAsync() return information about a particular mailbox via a Mailbox object, such as its size and number of messages.

// Instantiate a new IMAP connection to Gmail using TLS/SSL protection.
ImapClient imapClient = new ImapClient("imap.gmail.com", 993, "username@gmail.com", "Pass@word1", true);
imapClient.Connect();
imapClient.Authenticate();

// Retrieve and print the list of all mailboxes, including the full hierarchy.
string[] mailboxNames = imapClient.ListMailboxNames(true);
int mailboxId = 0;
foreach (string mailboxName in mailboxNames)
    Console.WriteLine("Mailbox #" + (++mailboxId) + ": " + mailboxName);

// Select the "Drafts" mailbox asynchronously, ensuring subsequent commands take place within it.
await imapClient.SelectMailboxAsync("Drafts");

// Examine the "Important" mailbox and output some of its properties.
Mailbox importantMailbox = imapClient.ExamineMailbox("Important");
Console.WriteLine("Mailbox message count: " + importantMailbox.Count);
Console.WriteLine("Mailbox recent message count: " + importantMailbox.Recent);
Console.WriteLine("Mailbox next UID: " + importantMailbox.UidNext);

// Asyncrhonously delete the "Unused" mailbox.
await imapClient.DeleteMailboxAsync("Unused");

10. Working with IMAP subscriptions

The OpaqueMail .NET library supports ImapClient mailbox subscriptions through several commands.

The ListSubscriptions() and ListSubscriptionsAsync() methods return arrays of Mailbox objects, whereas the ListSubscriptionNames() and ListSubscriptionNamesAsync() methods return array of the mailbox names only.

Subscriptions can be created through the SubscribeMailbox(), SubscribeMailboxAsync(), UnsubscribeMailbox(), and UnsubscribeMailboxAsync() methods.

// Instantiate a new IMAP connection to Gmail using TLS/SSL protection.
ImapClient imapClient = new ImapClient("imap.gmail.com", 993, "username@gmail.com", "Pass@word1", true);
imapClient.Connect();
imapClient.Authenticate();

// Retrieve and print the list of all subscriptions, including the full hierarchy.
string[] mailboxNames = imapClient.ListSubscriptionNames(true);
int mailboxId = 0;
foreach (string mailboxName in mailboxNames)
    Console.WriteLine("Mailbox #" + (++mailboxId) + ": " + mailboxName);

// Create a new subscription for the "Announcements" mailbox asynchronously.
await imapClient.SubscribeMailboxAsync("Announcements");

11. Working with IMAP quotas

The OpaqueMail .NET library enabled getting and settings quotas on supported IMAP servers. To check if the server supports quota commands, the ">ImapClient's SupportsQuota property can be checked.

If supported, the methods GetQuota(), GetQuotaAsync(), GetQuotaRoot(), GetQuotaRootAsync(), SetQuota(), and SetQuotaRoot() can be used, which interact with QuotaUsage objects.

// Confirm the server supports quota commands.
if (imapClient.SupportsQuota)
{
    // Determine and output the root quota usage.
    QuotaUsage quotaRoot = imapClient.GetQuotaRoot("Inbox");
    Console.WriteLine("Quota usage for Inbox: " + quotaRoot.Usage + " out of " + quotaRoot.QuotaMaximum);

    // Asynchronously set the "Memories" mailbox to have a quota of 1 GB (1024 MB).
    await imapClient.SetQuotaAsync("Memories", 1024);
}

12. Encrypting IMAP with STARTTLS and compressing IMAP with DEFLATE

IMAP traffic can be protected by TLS/SSL encryption either upon connecting to the remote server or after issuing the "STARTTLS" command. The ">ImapClient's StartTLS() method performs a TLS handshake and replaces the underlying NetworkStream objects with SslStream equivalents.

If a server supports the "DEFLATE" compression algorithm, the StartDeflateCompression() can be used to minimize bandwidth. If the server doesn't support that algorithm, the session continues as normal.

// Instantiate a new IMAP connection to Gmail using TLS/SSL protection.
ImapClient imapClient = new ImapClient("imap.gmail.com", 993, "username@gmail.com", "Pass@word1", true);
imapClient.Connect();

// Now that we're connected, start TLS protection.
imapClient.StartTLS();

// Authenticate.
imapClient.Authenticate();

// If the remote server supports it, negotate DEFLATE compression.
imapClient.StartDeflateCompression();

13. Keeping IMAP connections alive

There are multiple ways to keep an IMAP connection alive in order to be notified of server events (such as new messages). One of the most basic ways is to issue a command on a regular basis in order to prevent the session from timing out. The OpaqueMail .NET library's ">ImapClient can issue NoOp() commands to do so.

Some servers also support the "Idle" command set. A client can start "idling" by calling the IdleStart() or IdleStartAsync() methods. When idling, a timer runs on a regular basis (by default, every minute) to check for and process new messages from the server. When ready to issue other commands, the client should then run the IdleStop() or IdleStopAsync() which stops the timer.

"Idling" is useful when paired with IMAP events.

You can check if an ImapClient is currently "idling" and running a command processing timer by accessing its IsIdle property.

// Instantiate a new IMAP connection to Gmail using TLS/SSL protection.
ImapClient imapClient = new ImapClient("imap.gmail.com", 993, "username@gmail.com", "Pass@word1", true);
imapClient.Connect();
imapClient.Authenticate();

// Check how many messages there currently are.
int messageCount = await imapClient.GetMessageCountAsync();
Console.WriteLine("There are currently " + messageCount + " messages>");

// Sleep for 20 minutes, but process messages while idle.
imapClient.IdleStart();
System.Threading.Thread.Sleep(new TimeSpan(0, 20, 0));
imapClient.IdleStop();

14. Handling IMAP events

The OpaqueMail .NET ImapClient class supports four events that can be responded to using .NET's eventing model.

If the client is unexpectedly disconnected from the server, the ImapClientDisconnectEvent is fired.

If the IMAP connection speed is throttled by the remote server, the ImapClientThrottleEvent is fired. Whenever a new message notification is received, the ImapClientNewMessageEvent is fired.

Whenever a message expunge notification is received, the ImapClientMessageExpungeEvent is fired.

// Whenever a message is expunged, show an alert.
private void imapClient_ImapClientMessageExpungeEvent(object sender, ImapClientEventArgs e)
{
    MessageBox.Show("Message expunged.\r\n\r\nMailbox: " + e.MailboxName + "\r\nMessage ID: " + e.MessageId, "Message expunged.", MessageBoxButtons.OK, MessageBoxIcon.Information);
}

// Whenever a new message arrives, show an alert.
private void imapClient_ImapClientNewMessageEvent(object sender, ImapClientEventArgs e)
{
    MessageBox.Show("New message received.\r\n\r\nMailbox: " + e.MailboxName + "\r\nMessage ID: " + e.MessageId, "New message received", MessageBoxButtons.OK, MessageBoxIcon.Information);
}

private async void Main()
{
    // Instantiate a new IMAP connection to Gmail using TLS/SSL protection.
    ImapClient imapClient = new ImapClient("imap.gmail.com", 993, "username@gmail.com", "Pass@word1", true);
    imapClient.Connect();
    imapClient.Authenticate();

    // Create event handler for new messages and expunged messages.
    imapClient.ImapClientNewMessageEvent += imapClient_ImapClientNewMessageEvent;
    imapClient.ImapClientMessageExpungeEvent += imapClient_ImapClientMessageExpungeEvent;
    await myImapClient.IdleStartAsync();
}

Handling email with POP3:

The Post Office Protocol version 3 (POP3) is the simplest mechanism for retrieving email. It doesn't feature as much functionality as IMAP, but it's supported by the vast majority of email hosts.

1. Connecting to a POP3 server

Connecting to a POP3 server with OpaqueMail is straightforward. It only requires instantiating a Pop3Client, calling the Connect() method, and optionally calling the Authenticate() method. Some POP3 servers are anonymous, and thus don't need the Authenticate() call.

// Instantiate a new POP3 connection to Gmail using TLS/SSL protection.
Pop3Client pop3Client = new Pop3Client("pop.gmail.com", 995, "username@gmail.com", "Pass@word1", true);
pop3Client.Connect();
pop3Client.Authenticate();

2. Retrieving a list of POP3 messages

POP3 messages can be retrieved in bulk using the Pop3Client class. The GetMessages() and GetMessagesAsync() methods are overloaded with multiple options to retrieve a certain number of messages, in either ascending or descending order, with either headers only or entire messages.

// Instantiate a new POP3 connection to Gmail using TLS/SSL protection.
Pop3Client pop3Client = new Pop3Client("pop.gmail.com", 995, "username@gmail.com", "Pass@word1", true);
pop3Client.Connect();
pop3Client.Authenticate();

// Retrieve up to 50 of the most recent messages.
List<MailMessage> recentMessages = pop3Client.GetMessages(50);

// Retrieve up to 10 of the most recent messages asynchronously, with headers only.
List<MailMessage> messagesWithHeadersOnly = await pop3Client.GetMessagesAsync(10, true);

// Retrieve up to 25 messages in reverse order.
List<MailMessage> messagesInReverse = pop3Client.GetMessages(25, 0, true);

3. Downloading a specific POP3 message

POP3 messages can be downloaded individually using the Pop3Client class.

The GetMessage() and GetMessageAsync() methods are overloaded with multiple options to retrieve a certain message by its index on the server. The GetMessageUid() and GetMessageUidAsync() methods do the same based on a message's UID as returned by the UIDL command. A boolean determines whether the entire message is returned or only its headers.

// Instantiate a new POP3 connection to Gmail using TLS/SSL protection.
Pop3Client pop3Client = new Pop3Client("pop.gmail.com", 995, "username@gmail.com", "Pass@word1", true);
pop3Client.Connect();
pop3Client.Authenticate();

// Retrieve the 5th most recent message.
MailMessage fifthIndexMessage = pop3Client.GetMessage(5);

// Retrieve the most recent message asynchronously, with headers only.
MailMessage messageHeadersOnly = await pop3Client.GetMessageAsync(1, true);

// Retrieve the message with UID "whqtswO00WBw418f9t5JxYwZ" as returned by the UIDL command.
MailMessage uidlMessage = pop3Client.GetMessageUid("whqtswO00WBw418f9t5JxYwZ");

4. Deleting POP3 messages

POP3 messages can be deleted individually or as an array using the Pop3Client class.

The DeleteMessage(), DeleteMessageAsync(), DeleteMessages(), and DeleteMessagesAsync() delete one or more messages identified by index. The DeleteMessageUid(), DeleteMessageUidAsync(), DeleteMessagesUid(), and DeleteMessagesUidAsync() delete one or more messages identified by UID.

// Instantiate a new POP3 connection to Gmail using TLS/SSL protection.
Pop3Client pop3Client = new Pop3Client("pop.gmail.com", 995, "username@gmail.com", "Pass@word1", true);
pop3Client.Connect();
pop3Client.Authenticate();

// Delete the 7th most recent message.
pop3Client.DeleteMessage(7);

// Delete the most recent message asynchronously.
await pop3Client.DeleteMessageAsync(7);

// Delete the message with UID "whqtswO00WBw418f9t5JxYwZ".
pop3Client.DeleteMessageUid("whqtswO00WBw418f9t5JxYwZ");

// Delete the 2nd, 3rd, and 5th most recent messages.
pop3Client.DeleteMessages(new int[] { 2, 3, 5 });

5. Getting a list of POP3 capabilities

Modern POP3 servers advertise their extensions via the "CAPA" command, which can be queried from the Pop3Client class.

The GetCapabilities() and GetCapabilitiesAsync() methods return a string array of reported capabilities.

// Instantiate a new POP3 connection to Gmail using TLS/SSL protection.
Pop3Client pop3Client = new Pop3Client("pop.gmail.com", 995, "username@gmail.com", "Pass@word1", true);
pop3Client.Connect();
pop3Client.Authenticate();

// Get a list of capabilities, then loop through and print them out.
string[] capabilities = await pop3Client.GetCapabilitiesAsync();
foreach (string capability in capabilities)
    Console.WriteLine("Capability reported: " + capability);

Advanced techniques

The following topics are more advanced. Message processing applies equally whether working with IMAP or POP3.

1. Determining whether a message was S/MIME signed or encrypted

The OpaqueMail .NET library processes S/MIME messages automatically. Regardless of whether a message was signed, encrypted, or triple-wrapped, the MailMessage class will attempt to decrypt and extract the message contained within.

The MailMessage class can be interrogated to check whether S/MIME operations were performed. If it was signed, the SmimeSigningCertificateChain will be populated.

// Instantiate a new POP3 connection to Gmail using TLS/SSL protection.
Pop3Client pop3Client = new Pop3Client("pop.gmail.com", 995, "username@gmail.com", "Pass@word1", true);
pop3Client.Connect();
pop3Client.Authenticate();

// Retrieve the most recent message.
MailMessage mostRecentMessage = pop3Client.GetMessage(1);

// Output information about S/MIME operations performed on the message.
Console.WriteLine("S/MIME Signed?: " + mostRecentMessage.SmimeSigned);
if (mostRecentMessage.SmimeSigned)
{
    // If the most recent message was signed, output its signing certificate chain.
    int certIndex = 0;
    foreach (X509Certificate2 cert in mostRecentMessage.SmimeSigningCertificateChain)
        Console.WriteLine("S/MIME Certificate #" + (++certIndex) + ": " + cert.Subject);
}
Console.WriteLine("S/MIME Encrypted Envelope?: " + mostRecentMessage.SmimeEncryptedEnvelope);
Console.WriteLine("S/MIME Triple-Wrapped?: " + mostRecentMessage.SmimeTripleWrapped);

2. Processing TNEF-encoded messages

Transport-Neutral Encapsulation Format (TNEF) is a proprietary encoding format created by Microsoft. It was common with early versions of Outlook and Outlook Express and had a reputation for combining email attachments into a file called "winmail.dat".

While not as common as it used to be, TNEF-encoded messages are still transmitted. The OpaqueMail .NET library automatically parses TNEF-encoded messages and extracts their attachments.

If you'd like to manually decode a TNEF-encoded message using the OpaqueMail .NET library, you can do so with the following code.

// Instantiate a TNEF Encoding object to process the byte array "tnefEncoddedBytes".
TnefEncoding tnefEncoding = new TnefEncoding(tnefEncodedBytes);

// Loop through the TNEF-encoded attachments, outputting their names, content types, and sizes.
foreach (MimePart mimePart in tnefEncoding.MimeAttachments)
{
    Console.WriteLine("MIME Part Name: " + mimePart.Name);
    Console.WriteLine("MIME Part Content Type: " + mimePart.ContentType);
    Console.WriteLine("MIME Part Size: " + mimePart.BodyBytes.Length);
}

3. Loading a message from a .eml file

The OpaqueMail .NET library can load email messages directly from plaintext .eml and .msg files. To do so, use the MailMessage's static LoadFile() method to hydrate a new object. The following example uses the Functions helper class's ToMailAddressString helper method, which accepts a MailAddress or MailAddress collection and returns a readable string.

// Load a message from its .eml file representation, then output its subject, sender, and recipients.
MailMessage loadedMessage = MailMessage.LoadFile("C:\\message.eml");

Console.WriteLine("Message Subject: " + loadedMessage.Subject);
Console.WriteLine("Message Sender: " + Functions.ToMailAddressString(loadedMessage.From));
Console.WriteLine("Message Recipients: " + Functions.ToMailAddressString(loadedMessage.To));

4. Removing tags from a message

The OpaqueMail .NET library includes several helper functions that tackle common email tasks. Javascript code easily can be stripped from a message by calling RemoveScriptTags() from the Functions helper class. In addition to removing blocks, embedded event handlers like onclick will be removed.

// Load a message from its .eml file representation.
MailMessage loadedMessage = MailMessage.LoadFile("C:\\message.eml");

// Strip Javascript from the message's body prior to rendering it.
string scrubbedBodyToDisplay = Functions.RemoveScriptTags(loadedMessage.Body);

5. Embedding attachments inline

One of the helper functions in OpaqueMail .NET's Functions helper class is EmbedAttachments(). This method will take a MIME attachment collection and replace any CID: attachment references in the body with their Base-64 encoded bytes. This is a common requirement for text/html emails.

// Load a message from its .eml file representation.
MailMessage loadedMessage = MailMessage.LoadFile("C:\\message.eml");

// Load a web browser control and load it with the message's body, with attachments embedded.
WebBrowser browser = new WebBrowser();
browser.DocumentText = Functions.EmbedAttachments(message.Body, message.Attachments);

6. Manually encoding or decoding text

The OpaqueMail .NET library automatically handles message encoding/decoding automatically converts character sets (e.g. between Unicode and UTF-8). It was designed with internationalization (i18n) and localization in mind.

Its Functions helper class exposes several encoding and decoding functions that may be useful.

// Attempt to convert a text/plain body to text/html.
Functions.ConvertPlainTextToHTML("This is a test link to http://opaquemail.org/");
// The example above outputs "This is a test link to <a href="http://opaquemail.org/">http://opaquemail.org/</a>".

// Escape Base-64 and quoted-printable encodings in message headers, as indicated by ?B? or ?Q?.
string decodedMailHeader = Functions.DecodeMailHeader(mailHeader);

// Encode a mail header, choosing the proper encoding.
string encodedMailHeader = Functions.EncodeMailHeader(mailHeader);

// Decode a Base-64 encoded message.
string decodedString = Functions.FromBase64(base64EncodedString);

// Decode a Modified UTF-7 string, as used for IMAP mailbox names.
string decodedMailBoxName = Functions.FromModifiedUTF7(mailBoxName);

// Decode a quoted-printable message.
decodedString = Functions.FromQuotedPrintable(quotedPrintableEncodedString);

// Span a mail header over multiple lines of 70 characters or fewer, per SMTP guidelines.
string spannedMailHeader = Functions.SpanHeaderLines(mailHeader);

// Encode a message in Base-64.
Functions.ToBase64String(decodedString);

// Encode a string as Modified UTF-7, as used for IMAP mailbox names.
string encodedMailboxName = Functions.ToModifiedUTF7(mailBoxName);

// Decode a UUEncoded string.
Functions.UUDecode(uuEncodedString);

// UUEncode a message.
Functions.UUEncode(unencodedString);