How to access To, Cc, Bcc fields

IMail To, Cc, Bcc and ReplyTo properties are of IList<MailAddress> type (that is IList Of MailAddress in VB.NET).

The reason for this, is to handle not only regular mailboxes but also email groups.

In those collections you can find two kinds of objects:

  • MailBox – which represents single mailbox (e.g. “John” <john@example.org>),
  • MailGroup – which represents group of email addresses (e.g. Accounting: <pat@example.org>, “John” <john@example.com>; ).

Limilabs.Mail.Headers namespace

First remember that you need to import Limilabs.Mail.Headers namespace:

// C#

using Limilabs.Mail.Headers;
' VB.NET

Imports Limilabs.Mail.Headers

GetMailboxes method

The simplest way to access actual email addresses is to use overloaded GetMailboxes method.

This method returns all mailboxes represented by an email address: in case of MailGroup it returns all mailboxes represented by the group and all child groups, in case of MailBox returns a list containing a single mailbox:

// C#

IMail mail = ...;
foreach (MailAddress address in mail.To)
{
    foreach (MailBox mailbox in address.GetMailboxes())
    {
        Console.WriteLine("{0} <{1}>", mailbox.Name, mailbox.Address);
    }    
}
' VB.NET

Dim mail As IMail = ...
For Each address As MailAddress In mail.To
    For Each mailbox As MailBox In address.GetMailboxes()
        Console.WriteLine("{0} <{1}>", mailbox.Name, mailbox.Address)
    Next
Next

You can also use LINQ:

// C#

IMail mail = ...;
var mailboxes = email.To.SelectMany(address => address.GetMailboxes());  

Ignore groups

If for some reason you want to ignore MailGroups you can use following code:

// C#

IMail mail = ...;
foreach (MailBox mailbox in mail.To.OfType<MailBox>())
{
    Console.WriteLine("{0} <{1}>", mailbox.Name, mailbox.Address);
}
' VB.NET

Dim mail As IMail = ...
For Each mailbox As MailBox In mail.[To].OfType(Of MailBox)()
	Console.WriteLine("{0} <{1}>", mailbox.Name, mailbox.Address)
Next

Extract all mailboxes

Here are sample functions that print all mailboxes (including those in groups) to string:

// C#

private string PrintMailboxes(IEnumerable<MailAddress> addresses)
{
    List<MailBox> mailboxes = new List<MailBox>();
    foreach (MailAddress address in addresses)
    {
        mailboxes.AddRange(address.GetMailboxes());
    }
    return string.Join(", ", mailboxes.ConvertAll(
        x => string.Format("'{0}' <{1}>", x.Name, x.Address)).ToArray());
}
' VB.NET
Imports Limilabs.Mail.Headers

Private Function PrintMailboxes(addresses As IEnumerable(Of MailAddress)) As String
    Dim mailboxes As New List(Of MailBox)()
    For Each address As MailAddress In addresses
    	mailboxes.AddRange(address.GetMailboxes())
    Next
    Return String.Join(", ", mailboxes.ConvertAll( _
        Function(x) String.Format("'{0}' <{1}>", x.Name, x.Address)).ToArray())
End Function

Handle groups and mailboxes

If you need to handle email groups differently than regular mailboxes, you need to use is operator:

// C#

private string PrintAddresses(IEnumerable<MailAddress> addresses)
{
    List<string> parts = new List<string>();
    foreach (MailAddress address in addresses)
    {
        if (address is MailGroup)
        {
            MailGroup group = (MailGroup)address;
            parts.Add(string.Format("'{0}': ({1})",
                group.Name,
                PrintAddresses(group.Addresses))); // recursion
        }
        if (address is MailBox)
        {
            MailBox mailbox = (MailBox)address;
            parts.Add(string.Format("'{0}' <{1}>",
                mailbox.Name,
                mailbox.Address));
        }
    }
    return string.Join(", ", parts.ToArray());
}
' VB.NET

Private Function PrintAddresses(addresses As IEnumerable(Of MailAddress)) As String
    Dim parts As New List(Of String)()
    For Each address As MailAddress In addresses
        If TypeOf address Is MailGroup Then
            Dim group As MailGroup = DirectCast(address, MailGroup) ' recursion
            parts.Add(String.Format("'{0}': ({1})", _
                group.Name, _
                PrintAddresses(group.Addresses)))
        End If
        If TypeOf address Is MailBox Then
            Dim mailbox As MailBox = DirectCast(address, MailBox)
            parts.Add(String.Format("'{0}' <{1}>", _
                mailbox.Name, _
                mailbox.Address))
        End If
    Next
    Return String.Join(", ", parts.ToArray())
End Function

Check if address list contains email

Finally here’s a helper function, you can use when, you want to know, if specified email address is in the recipients list:

// C#

if (ContainsEmail(email.To, "pat@example.com"))
{
    // ...
}

private static bool ContainsEmail(IEnumerable<MailAddress> list, string email)
{
    foreach (MailAddress address in list)
    {
        if (address.GetMailboxes().ConvertAll(x => x.Address).Contains(email))
            return true;
    }
    return false;
}

// Linq version:
private static bool ContainsEmail(IEnumerable<MailAddress> list, string email)
{
    return list.Any(address => address.GetMailboxes().ConvertAll(
        x => x.Address).Contains(email));
}
' VB.NET

If ContainsEmail(email.[To], "pat@example.com") Then
    ' ...
End If

Private Shared Function ContainsEmail(list As IEnumerable(Of MailAddress), email As String) As Boolean
    For Each address As MailAddress In list
        If address.GetMailboxes().ConvertAll(Function(x) x.Address).Contains(email) Then
            Return True
        End If
    Next
    Return False
End Function

' Linq version:
Private Shared Function ContainsEmail(list As IEnumerable(Of MailAddress), email As String) As Boolean
    Return list.Any(Function(address) address.GetMailboxes().ConvertAll( _
        Function(x) x.Address).Contains(email))
End Function

Get supported authentication methods (IMAP, POP3, SMTP)

Not all servers support all authentication methods. It is sometimes good to know which methods are supported and which are not. This post explains how to get supported authentication methods supported by the email server in .NET.

It is very common for email servers to support different authentication methods for plain text connections and different methods for SSL (ConnectSSL) or TLS (StartTLS, STLS) secured sessions.

Even though protocol commands and responses are different between server types (IMAP, POP3, and SMTP), API is very similar. We take great care to make it similar for all protocols (IMAP, POP3, SMTP). You can use SupportedAuthenticationMethods on Imap, Pop3 or Smtp class to retrieve all authentication methods supported by the server.

In return you’ll get a list of

  • ImapAuthenticationMethod objects in case of IMAP server
  • Pop3AuthenticationMethod objects in case of POP3 server
  • SmtpAuthenticationMethod objects in case of SMTP server

Each class contains static properties that represent known authentication methods for example: ImapAuthenticationMethod.CramMD5, ImapAuthenticationMethod.Login

IMAP

// C#

using (Imap client = new Imap())
{
    client.Connect("imap.example.org");     // or ConnectSSL for SSL
    client.UseBestLogin("user", "password");

    Console.WriteLine("Supported methods:");

    foreach (var method in client.SupportedAuthenticationMethods())
    {
        Console.WriteLine(method.Name);
    }

    Console.WriteLine("Supports CramMD5:");

    bool supportsCramMD5 = client.SupportedAuthenticationMethods()
        .Contains(ImapAuthenticationMethod.CramMD5);

    Console.WriteLine(supportsCramMD5);

    client.Close();
}

' VB.NET

Using client As New Imap()
    client.Connect("imap.example.org")     ' or ConnectSSL for SSL
    client.UseBestLogin("user", "password")

    Console.WriteLine("Supported methods:")

    For Each method As ImapAuthenticationMethod In client.SupportedAuthenticationMethods()
	    Console.WriteLine(method.Name)
    Next

    Console.WriteLine("Supports CramMD5:")

    Dim supportsCramMD5 As Boolean = client.SupportedAuthenticationMethods() _
        .Contains(ImapAuthenticationMethod.CramMD5)

    Console.WriteLine(supportsCramMD5)

    client.Close()
End Using

For example Gmail produces following output:

Supported methods:
XOAUTH
XOAUTH2

Supports CramMD5:
False

As you can see this list is not entirely accurate as LOGIN command is supported by Google.

The current IMAP protocol specification requires the implementation of the LOGIN command which uses clear-text passwords.

Please also note that although LOGIN command send user and password in clear text, Gmail requires clients to use SSL (To connect you must use ConnectSSL method). This means that entire communication channel is secured using SSL (in the same way https is) and it is impossible to eavesdrop the password.

LOGINDISABLED

The current IMAP protocol specification requires the implementation of the LOGIN command which uses clear-text passwords.

Many sites may choose to disable this command unless encryption is active (ConnectSSL or StartTLS) for security reasons.

An IMAP server, especially the one that allows connecting without SSL, MAY advertise that the LOGIN command is disabled by including the LOGINDISABLED capability in the capability response.

Here’s how to check what extensions IMAP server supports.

The bottom line is that in most cases UseBestLogin method is going to chose appropriate authentication method for you and authenticate you.

POP3

Here’s how to get supported authentication methods for POP3 protocol:

// C#

using (Pop3 client = new Pop3())
{
    client.Connect("pop3.example.org");     // or ConnectSSL for SSL
    client.UseBestLogin("user", "password");

    Console.WriteLine("Supported methods:");

    foreach (var method in client.SupportedAuthenticationMethods())
    {
        Console.WriteLine(method.Name);
    }

    Console.WriteLine("Supports CramMD5:");

    bool supportsCramMD5 = client.SupportedAuthenticationMethods()
        .Contains(Pop3AuthenticationMethod.CramMD5);

    Console.WriteLine(supportsCramMD5);

    client.Close();
}

' VB.NET

Using client As New Pop3()
    client.Connect("pop3.example.org")     ' or ConnectSSL for SSL
    client.UseBestLogin("user", "password")

    Console.WriteLine("Supported methods:")

    For Each method As Pop3AuthenticationMethod In client.SupportedAuthenticationMethods()
	    Console.WriteLine(method.Name)
    Next

    Console.WriteLine("Supports CramMD5:")

    Dim supportsCramMD5 As Boolean = client.SupportedAuthenticationMethods() _
        .Contains(Pop3AuthenticationMethod.CramMD5)

    Console.WriteLine(supportsCramMD5)

    client.Close()
End Using

In most cases UseBestLogin method is going to chose appropriate authentication method for you and authenticate you.

SMTP

Here’s how to get supported authentication methods for SMTP protocol:

// C#

using (Smtpclient = new Smtp())
{
    client.Connect("smtp.example.org");     // or ConnectSSL for SSL
    client.UseBestLogin("smtp", "password");

    Console.WriteLine("Supported methods:");

    foreach (var method in client.SupportedAuthenticationMethods())
    {
        Console.WriteLine(method.Name);
    }

    Console.WriteLine("Supports CramMD5:");

    bool supportsCramMD5 = client.SupportedAuthenticationMethods()
        .Contains(SmtpAuthenticationMethod.CramMD5);

    Console.WriteLine(supportsCramMD5);

    client.Close();
}

' VB.NET

Using client As New Smtp()
    client.Connect("smtp.example.org")     ' or ConnectSSL for SSL
    client.UseBestLogin("user", "password")

    Console.WriteLine("Supported methods:")

    For Each method As SmtpAuthenticationMethod In client.SupportedAuthenticationMethods()
	    Console.WriteLine(method.Name)
    Next

    Console.WriteLine("Supports CramMD5:")

    Dim supportsCramMD5 As Boolean = client.SupportedAuthenticationMethods() _
        .Contains(SmtpAuthenticationMethod.CramMD5)

    Console.WriteLine(supportsCramMD5)

    client.Close()
End Using

In most cases UseBestLogin method is going to chose appropriate authentication method for you and authenticate you.

Get supported server extensions (IMAP, POP3, SMTP)

Not every server supports same extensions. This post explains how to get custom extensions supported by the email server in .NET.

Even though protocol commands and responses are different between server types (IMAP, POP3, and SMTP), client API is very similar. Great care is taken to make it almost the same for all email protocols. You can use SupportedExtensions on Imap, Pop3 or Smtp class to retrieve extensions supported by the server.

There are 3 different classes that represent server extensions for each email protocol: ImapExtension, Pop3Extension and SmtpExtension. Those classes contain static properties with well-know extensions like: ImapExtension.Idle or ImapExtension.Sort or Pop3Extension.STLS etc. SupportedExtensions returns a list of them.

You can use SupportedExtensions method to retrieve all protocol extensions supported by the server.

Get supported IMAP server extensions

// C#

using (Imap client = new Imap())
{
    client.ConnectSSL("imap.example.org");
    client.UseBestLogin("user", "password");

    Console.WriteLine("Supported extensions:");

    foreach (ImapExtension extension in client.SupportedExtensions())
    {
        Console.WriteLine(extension.Name);
    }

    Console.WriteLine("Supports IDLE:");

    bool supportsIdle = client.SupportedExtensions()
        .Contains(ImapExtension.Idle);

    Console.WriteLine(supportsIdle);

    client.Close();
}

' VB.NET

Using client As New Imap()
    client.ConnectSSL("imap.example.org")
    client.UseBestLogin("user", "password")

    Console.WriteLine("Supported extensions:")

    For Each extension As ImapExtension In client.SupportedExtensions()
	    Console.WriteLine(extension.Name)
    Next

    Console.WriteLine("Supports IDLE:")

    Dim supportsIdle As Boolean = client.SupportedExtensions() _
        .Contains(ImapExtension.Idle)

    Console.WriteLine(supportsIdle)

    client.Close()
End Using

For example Gmail produces following output:

Supported extensions:
IMAP4rev1
UNSELECT
IDLE
NAMESPACE
QUOTA
ID
XLIST
CHILDREN
X-GM-EXT-1
UIDPLUS
COMPRESS

Supports IDLE:
True

We take great care for the API to look similar for all protocols (IMAP, POP3, SMTP).

Get supported POP3 server extensions

// C#

using (Pop3 client = new Pop3())
{
    client.ConnectSSL("pop3.example.org");
    client.UseBestLogin("user", "password");

    Console.WriteLine("Supported extensions:");

    foreach (Pop3Extension extension in client.SupportedExtensions())
    {
        Console.WriteLine(extension.Name);
    }

    Console.WriteLine("Supports TOP:");

    bool supportsTop= client.SupportedExtensions()
        .Contains(Pop3Extension.Top);

    Console.WriteLine(supportsTop);

    client.Close();
}

' VB.NET

Using client As New Pop3()
    client.ConnectSSL("pop3.example.org")
    client.UseBestLogin("user", "password")

    Console.WriteLine("Supported extensions:")

    For Each extension As Pop3Extension In client.SupportedExtensions()
	    Console.WriteLine(extension.Name)
    Next

    Console.WriteLine("Supports TOP:")

    Dim supportsTop As Boolean = client.SupportedExtensions() _
        .Contains(Pop3Extension.Top)

    Console.WriteLine(supportsTop)

    client.Close()
End Using

Get supported SMTP server extensions

// C#

using (Smtpclient = new Smtp())
{
    client.Connect("smtp.example.org");
    client.UseBestLogin("smtp", "password");

    Console.WriteLine("Supported extensions:");

    foreach (SmtpExtension  extension in client.SupportedExtensions())
    {
        Console.WriteLine(extension.Name);
    }

    Console.WriteLine("Supports STARTTLS:");

    bool supportsStartTLS = client.SupportedExtensions()
        .Contains(SmtpExtension.StartTLS);

    Console.WriteLine(supportsStartTLS);

    client.Close();
}

' VB.NET

Using client As New Smtp()
    client.Connect("smtp.example.org")
    client.UseBestLogin("user", "password")

    Console.WriteLine("Supported extensions:")

    For Each extension As SmtpExtension In client.SupportedExtensions()
	    Console.WriteLine(extension.Name)
    Next

    Console.WriteLine("Supports STARTTLS:")

    Dim supportsStartTLS As Boolean = client.SupportedExtensions() _
        .Contains(SmtpExtension.StartTLS)

    Console.WriteLine(supportsStartTLS)

    client.Close()
End Using

Create Gmail url-ID via IMAP

This is Gmail link that points to certain conversation (entire thread):

https://mail.google.com/mail/u/0/#inbox/13216515baefe747

“13216515baefe747” is the Gmail thread-ID in hex.

Here’s the code that:

  1. Selects “All Mail” folder
  2. Gets the newest message UID
  3. Obtains Gmail thread ID for this message (X-GM-THRID)
  4. Converts it to hex
  5. Creates the url that points to the Gmail conversation
// C# version
 
using (Imap client = new Imap())
{
    client.ConnectSSL("imap.gmail.com");
    client.UseBestLogin("pat@gmail.com", "app-password");
 
    // Select 'All Mail' folder
    CommonFolders common = new CommonFolders(
        client.GetFolders());
    client.Select(common.AllMail);
 
    // get IMAP uid of the newest message
    long lastUid = client.GetAll().Last();
 
    // get message info
    MessageInfo info = client.GetMessageInfoByUID(lastUid);
 
    // extract Gmail thread ID
    decimal threadId = info.Envelope.GmailThreadId;
    string threadIdAsHex = threadId.ToString("x");
 
    // create url
    string url = string.Format(
        "https://mail.google.com/mail/u/0/#inbox/{0}",
        threadIdAsHex);
 
    Console.WriteLine(url);
 
    client.Close();
}
' VB.NET version
 
Using client As New Imap()
    client.ConnectSSL("imap.gmail.com")
    client.UseBestLogin("pat@gmail.com", "app-password")
 
    ' Select 'All Mail' folder
    Dim common As New CommonFolders(client.GetFolders())
    client.Select(common.AllMail)
 
    ' get IMAP uid of the newest message
    Dim lastUid As Long = client.GetAll().Last()
 
    ' get message info
    Dim info As MessageInfo = client.GetMessageInfoByUID(lastUid)
 
    ' extract Gmail thread ID
    Dim threadId As Decimal = info.Envelope.GmailThreadId
    Dim threadIdAsHex As String = threadId.ToString("x")
 
    ' create url
    Dim url As String = String.Format( _
        "https://mail.google.com/mail/u/0/#inbox/{0}", _
        threadIdAsHex)
 
    Console.WriteLine(url)
 
    client.Close()
End Using

Here you can find some more information about how to search by X-GM-THRID and all other Gmail IMAP extensions.

Bounce handling

Bounce message, also called a (failed) Delivery Status Notification (DSN) is an automated electronic email message from a mail system informing the sender about a delivery problem. The original message is said to have bounced.

Errors may occur at multiple places during email delivery. A sender may sometimes receive a bounce message from a recipient’s mail server. Bounce messages from the recipient’s mail server are required when a mail server accepted a message that was undeliverable.

You can use Mail.dll .NET email component to analyze the email message and determine if it is a bounce.

// C#

IMail email = ...;

Bounce bounce = new Bounce();
List<BounceResult> results = bounce.ExamineAll(email);

bool isDeliveryFailure = results.Count > 0;

foreach(var result in results)
{
    Console.WriteLine(result.Recipient);

    Console.WriteLine(result.Action);    // DSNAction.Failed or DSNAction.Delayed

    Console.WriteLine(result.Reason);
    Console.WriteLine(result.Status);
}
' VB.NET

Dim email As IMail = ...

Dim bounce As New Bounce()
Dim results As List(Of BounceResult) = bounce.ExamineAll(email)

Dim isDeliveryFailure As Boolean = results.Count > 0

For Each result As var In results
	Console.WriteLine(result.Recipient)

	Console.WriteLine(result.Action)	' DSNAction.Failed or DSNAction.Delayed

	Console.WriteLine(result.Reason)
	Console.WriteLine(result.Status)
Next

Mail.dll processes Multipart/Report mime parts (defined in RFC 6522) to recognize bounce messages, as well as other heuristics (X-Failed-Recipients header, subject and body scanning).

Unfortunately, most bounce messages have historically been designed to be read by users, not automatically handled by software. It’s nearly impossible to reliably interpret the meaning of every single bounce message. Bounce class handles large proportion of bounces, but it will never be 100% reliable.

You may also consider using other techniques to recognize bounces such as Variable Envelope Return Path (VERP) method.