Searching emails from an Outlook add-in

This article is about searching emails in Outlook programatically from an Outlook add-in.

1. Searching by EntryId

Let’s say you have an EntryId of an email message and want to obtain a MailItem representing this message. Just use this utility function:

public static MailItem GetMailItemById(this Application application, string entryId)
{
    try
    {
        return application.Session.GetItemFromID(entryId, Type.Missing) as MailItem;
    }
    catch (COMException e)
    {    
        if ((uint)e.ErrorCode == 0x80040107)
        {
            // MAPI_E_NOT_FOUND
            return null;
        }
        throw;
    }
}

This finds the email in any folder under any account. GetItemFromID is fast.

Note: It is better to check the ErrorCode when handling COMExceptions. ErrorCode is .NET analogy of Exception type and by simply handling all COMExceptions your code will hide unexpected problems.

2. Searching by arbitrary field (e.g. Subject, Message-ID, User property)

My last article explained that every email in Outlook has fields. Some of them are visible by default in the user interface (like Subject), some are hidden (like Message-ID). You can even define your own fields.

Indeed you can search emails by any of these fields but unfortunately it will be slower than search by EntryId.

This is how to search email in Outlook by Message-ID:

public static IEnumerable<MailItem> FindEmailsByMessageId(this Application application, string messageId)
{
    string query = string.Format("@SQL=\"http://schemas.microsoft.com/mapi/proptag/0x1035001E\" = '{0}'", messageId);
    foreach (Folder folder in application.Session.GetAllFolders())
    {
        MailItem foundMail = (MailItem)folder.Items.Find(query);
        yield return foundMail;
        while ((foundMail = (MailItem)folder.Items.FindNext()) != null)
        {
            yield return foundMail;
        }
    }    
}

The 0x1035001E in the query is the numeric identifier of Message-ID property. The obvious problem is that we have to do the search folder by folder. You can also use Application.AdvancedSearch which searches in subfolders. The method GetAllFolders is my utility method that recursively walks all folders and subfolders in all stores. This search is slower than search by EntryId.

3. Searching by our custom data attached to emails

This part builds on the last article and actually describes how we search “Related emails” in TaskConnect.

Let’s say you’re using the “External database” approach to add extra data to emails as described in the last article - you store email’s unique identifier (Message-ID) and the custom data in a database table. This allows you to answer the following question:

“Which emails in Outlook have the value of our custom data equal to x?”

This is how to answer it:

  • Look into the database for all records that have value of our data equal to x.
  • Take MessageIds of these records.
  • Find all emails with these MessageIds as described in part 2.

That’s it, job done.

Wait. This is not very clever, is it?

It will be quite slow due to the slow search by MessageId. What we could actually do is to combine the advantages of EntryId and MessageId:

  • In the database, store both EntryId and MessageId of the email plus our data

Now to answer the question:

  • Look into the database for all records that have value of our data equal to x.
  • For each such record:
    • Find email in Outlook by record’s EntryId (fast). If found, done.
    • If not found it means the email has been moved or deleted:
      • Find email in Outlook by record’s MessageId.
      • If found, update the EntryId of the record in the database so that future searches are fast.
      • If not found, the email does not exist (was deleted).

You can probably see that “our custom data” in TaskConnect means “ids of attached work items”.

4. Note to folder.Items.Find(query) used in search by MessageId

You may notice that Items object, apart from Find method, also has a Restrict method which seemingly provides the same functionality as Find. The difference between Find and Restrict is that results of Restrict query are cached on Exchange server. Using Restrict in our case would kill Exchange server eventually because the query is almost always different – a lot of results would be cached and almost never reused, eating up memory on the server. In case you know the queries will be very often the same you can gain performance benefit by using Restrict. As we said, the third way to search apart of Find and Restrict is Application.AdvancedSearch.