All user's Activity Streams

31th May 2011 18:20

27th Jan 2011 17:00

25th Oct 2010 23:26

Blog post 9:26pm

Searching emails from an Outlook add-in 104419 reads

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.

28th Aug 2010 00:33

Blog post 10:33pm

Adding custom data to Outlook e-mails 180738 reads

This article explains possible ways to attach custom information to Outlook emails. Let’s say you are writing a managed Outlook add-in and you would like to be able to add your own data to individual emails and then work with the data.

1. User defined fields

The first approach you can take is very simple. Every Outlook email has fields (like “From”, “Subject” and many others) and you can also add your own fields, like this:
public static void SetUserProperty(this MailItem mail, string value)
{
    mail.UserProperties.Add("myKey", OlUserPropertyType.olText,
        true, OlFormatText.olFormatTextText);
    mail.UserProperties["myKey"].Value = value;
}
The third (true) parameter addToFolderFields specifies whether this property should be defined just on the email or also on the folder in which the email resides. Defining the property also on the folder is necessary if you want this property to be available in user defined views. Now, we would like to be able to read the value of our property from the email. This is how to do it:
public static string GetUserProperty(this MailItem mail)
{
    if (mail.UserProperties["myKey"] == null)
        return null;
    return (string)mail.UserProperties["myKey"].Value;
}
Of course you can use other data type than string, or serialize an object to string. So far so good. The bad thing with User properties is that their value can get lost. When the MailItem is in a folder on IMAP account and it is moved to another folder, the value gets lost. On Exchange and POP3 accounts, the value should survive the move. In general Outlook’s support for some functionality is limited on IMAP compared to Exchange or POP3. When testing some functionality, always be sure to also test it on IMAP. The other bad thing is that when you realize you would also like to search all emails with certain value of your property, it turns out that it is quite slow. More on this in my next blogpost.

2. External database

This is the approach we are actually using in TaskConnect. It sounds quite simple as well:
  • For an email, store its unique ID and the extra data in a database.
  • To set the data, update the record having the email’s ID.
  • To get the data, find the record with the email’s ID.
It seems that the problem is solved. Every item in Outlook should have a unique identifier, right? Indeed it has, it is the EntryId. But unfortunately EntryId changes when the email is moved to different folder (on IMAP and even Exchange). It seems that there is no single unique permanent identifier of an email in Outlook’s database… Luckily there is one. It is the standard Message-ID, specified by RFC 2822. Although this RFC uses the word SHOULD, in practice it is always present. MessageId can be accessed from OOM like this:
public static string GetMessageId(this MailItem mailItem)
{
    var propertyAccessor = mailItem.PropertyAccessor;
    return (string)propertyAccessor.GetProperty(
        "http://schemas.microsoft.com/mapi/proptag/0x1035001E");
}
0x1035001E is a proptag, i.e. numeric identifier of “MessageId” field. To explore all the email fields and their proptags, see tools for viewing Outlook internals. Now the problem of adding custom data to an email seems solved, with 2 “subtle” issues:
  • In Outlook 2007, the MessageId is not present for outgoing emails in the Sent folder
  • If you realize that you would also like to find all emails having certain value of your custom data, it will be slow. More on this (and solution) in my next blogpost.

Other types of data than emails

Note that the approaches with UserProperties and EntryId described will also work for Contacts, Appointments, and all the other types of Outlook items. Unlike emails, some of them could be actually suitable for the UserProperties approach (because they are not stored on IMAP server for example).

Tools for viewing Outlook “internals”

Emails and everything else that Outlook works with is stored in so called MAPI store. MAPI is a COM API to work with this data basically. The managed Outlook Object Model (OOM) which you are using by referencing Microsoft.Office.Interop.Outlook is a wrapper around this COM API and actually it provides only the subset of all available functionality. If you need some functionality which is missing in OOM, try Redemption, which is a more complete wrapper around the COM API. The tools to view the “raw” underlying data in the MAPI store are OutlookSpy, and MFCMAPI. Sometimes they can prove very useful. OutlookSpy has the advantage that it is an Outlook add-in, while MFCMAPI is free and provides more functionality. This screenshot shows all the fields of an email. We can see our “myKey” field there. This is an email on POP3 account. Now we get to the reason for lost value on IMAP account: on IMAP the value is not stored among the fields of the email – it is stored somewhere else separately so it does not move with the email. Note that this is not necessarily Microsoft’s fault but it can be design decision caused by limitations of the IMAP protocol.

11th Aug 2010 06:30

30th Apr 2010 15:56

14th Apr 2010 15:30

13th Apr 2010 05:33

Blog post 12:12am

Today, we have almost released our first public beta... 164675 reads

... but then Murphy kicked in as we finished everything else and started testing the final build in a clean virtual machine:

Everything had been going well until the moment when we wanted to create first TFS work item. We clicked the WI type and... Outlook crashed. Our error reporting didn't have a chance to kick in.

Bit of debugging and poking around showed that our main pre-installation requirement, Team Explorer 2008 hadn't installed the crucial assembly Microsoft.TeamFoundation.WorkItemTracking.Controls into GAC. This assembly is required to render the form for editing a work item and although Team Explorer installs all the others, it keeps this assembly for itself in PrivateAssemblies folder.

Fortunately, a google search brought up this excellent solution from a Hippie coder:

He adds an AssemblyResolve handler to the current AppDomain. When the application requires the missing assembly, the handler looks up the path to private assemblies from the registry and loads the required assembly from Visual Studio's PrivateAssemblies folder.

We would like to say thanks to Hippie coder and hopefully we will release the beta tomorrow.

… unless something unexpected happens :).

24th Mar 2010 10:30