Pieter van der Westhuizen

How to determine when a user has read an Outlook message

In today’s article we’ll look at how to determine when a user has read an Outlook mail message. There are a variety of ways a message can be marked as read, e.g.:

  • The user right-clicked the message in the Explorer and selected the “Mark as Read” context-menu item; or
  • The mail message was opened in an inspector window, usually by double-clicking the message in the Outlook Explorer; or
  • Viewing the message in the Outlook reading pane.

We’ll create an Outlook add-in that illustrates the various methods of responding to the the fact that a user has read an e-mail in Outlook.

Creating the Outlook add-in

To start, create a new ADX COM Add-in project in Visual Studio using Add-in Express for Office and .net.

Creating the Outlook add-in in Visual Studio using Add-in Express for Office and .net.

Choose the programming language you would like to create the add-in in (VB.NET, C# or C++.NET) as well as the minimum version of Office the add-in needs to support (from Outlook 2000 to Outlook 2013).

Choose the programming language and minimum version of Office the add-in needs to support.

Finally, select the supported Office applications for the add-in. In this example, well only select Microsoft Outlook.

Select Outlook as the only supported Office application for the add-in.

Responding to the Unread property change in Outlook

With our Outlook add-in project created we’ll first turn our attention to determining when the user marked the message as read or opened it in the reading pane and moved on to another email.

In order to determine when the Outlook’s Unread property has been changed we need to attach an event handler to the PropertyChange event.

Add-in Express makes it very easy to respond to an item event by providing an Outlook Item Events Class.

Add a new Outlook Item Events Class to your Outlook add-in project.

Add a new Outlook Item Events Class to your Outlook addin project. You’ll find the item template under Add-in Express Items > Outlook.

In order for the Outlook Item Events Class to work correctly, we first need to make a number of changes to the AddinModule class. First, add the following declaration to the top of the class:

OutlookItemEventsClass itemEvents = null;

Next, switch to the AddinModule designer view and double-click in its AddinStartupComplete event in the property grid to generate an empty event handler.

Generating an empty event handler

Add the following code to it:

private void AddinModule_AddinStartupComplete(object sender, EventArgs e)
{
    itemEvents = new OutlookItemEventsClass(this);
}

Add a new method called ConnetToSelectedItem to the AddinModule class. This method will be used to connect the Item Events class to the currently selected item in the Outlook Explorer. The code for the method follows below:

private void ConnectToSelectedItem(object explorer)
{
    Outlook.Selection sel = null;
    try
    {
        Outlook._Explorer expl = explorer as Outlook._Explorer;
        sel = expl.Selection;
        if (sel.Count == 1)
        {
            object item = sel[1];
            if (item is Outlook._MailItem)
                itemEvents.ConnectTo(item, true);
            else
                Marshal.ReleaseComObject(item);
        }
    }
    finally
    {
        if (sel != null)
            Marshal.ReleaseComObject(sel);
    }
}

Next, switch back to the AddinModule.cs designer and add a new Microsoft Outlook events component.

Adding a new Microsoft Outlook events component

Select the Microsoft Outlook events component and generate an event handler for the InspectoActivate event and add the following code to it:

private void adxOutlookEvents_InspectorActivate(object sender,
    object inspector, string folderName)
{
    if (canConnect)
    {
        Outlook._Inspector olInsp = (Outlook._Inspector)inspector;
        object item = olInsp.CurrentItem;
 
        if (item is Outlook._MailItem)
        {
            itemEvents.ConnectTo(item, true);
        }
        else
            Marshal.ReleaseComObject(item);
    }
}

The canConnect variable is declared at the top of the AddinModule class:

private bool canConnect = true;

Now, add an event handler for the ExplorerClose, ExporerActivate, InspectorClose, ExplorerSelectionChange and AddinBeginShutdown events:

private void adxOutlookEvents_ExplorerClose(object sender, object explorer)
{
    int count = 0;
    Outlook._Explorers expls = null;
 
    try
    {
        expls = OutlookApp.Explorers;
        count = expls.Count;
    }
    catch { }
    finally
    {
        if (expls != null)
            Marshal.ReleaseComObject(expls);
    }
 
    if (count == 0)
        canConnect = false;
}
 
private void adxOutlookEvents_ExplorerActivate(object sender, object explorer)
{
    ConnectToSelectedItem(explorer);
}
 
private void adxOutlookEvents_InspectorClose(object sender, object inspector,
    string folderName)
{
    if (itemEvents != null)
        itemEvents.RemoveConnection();
}
 
private void adxOutlookEvents_ExplorerSelectionChange(object sender, object explorer)
{
    ConnectToSelectedItem(explorer);
}
 
 
private void AddinModule_AddinBeginShutdown(object sender, EventArgs e)
{
    if (itemEvents != null)
        itemEvents.RemoveConnection();
}

With the necessary event handlers and methods in place, we can move our focus to detecting when the user reads an Outlook message. To do this, open the Outlook Item Events Class we’ve added earlier and navigate to its ProcessPropertyChange method.

This method will fire each time a property on an item in Outlook is changed. To determine whether the email has been read or not, we must first check if the UnRead property on the email has changed and if it has, whether it is true or false. If the Unread property is false, it means the e-mail has been read in Outlook.

In the sample add-in we want to track the number of times the message has been read. We’ll assume when the user marks the Outlook message as unread they want the read count to be reset to zero.

Add the following code to the ProcessPropertyChange method:

public override void ProcessPropertyChange(string name)
{
    if (this.ItemObj is MailItem)
    {
        Outlook.MailItem mail = (Outlook.MailItem)this.ItemObj;
        Outlook.UserProperties userProperties = null;
        Outlook.UserProperty readCountProperty = null;
 
        try
        {
 
            if (name.ToLower() == "unread")
            {
                int readCount = 0;
                userProperties = mail.UserProperties;
                readCountProperty = userProperties["ReadCount"];
                if (readCountProperty == null)
                    readCountProperty = userProperties.Add("ReadCount", OlUserPropertyType.olInteger)
 
                readCount = Convert.ToInt32(readCountProperty.Value);
                readCount++;
 
                if (mail.UnRead)
                {
                    readCountProperty.Value = 0;
                }
                else
                {
                    readCountProperty.Value = readCount;
                }
                mail.Save();
            }
        }
        finally
        {
            if (readCountProperty != null) Marshal.ReleaseComObject(readCountProperty);
            if (userProperties != null) Marshal.ReleaseComObject(userProperties);
        }
 
    }
}

In the code above, we first got a reference to the mail message that was read, checked to see whether the user property called “ReadCount” existed and increased the value. If the user property did not exist, we first created it before increasing its value.

Processing the Open, Close and Read events

The Open event is raised when a user open an Outlook item, in this case a mail message in an Inspector window. The Close event is the opposite of the Open event and is raised when the user closes the inspector in which the mail item is open.

With both the Open and Close events the UnRead property will always be set to false since Outlook assumes if the message was opened in an Inspector it has been read. The Read event is similar to the Open event, in the fact that it is raised when the user opens the Outlook mail item in an Inspector, but it is also raised when the mail item is opened in a view that supports in-cell editing.

Since, we want to track the number of times the user reads our e-mail, we’ll use the same code that we used for ProcessPropertyChange to increase the “ReadCount” user property. We’ll refactor the code into a single method called ProcessReadCount, which we’ll call from the Open, Read and ProcessChange events.

private void ProcessReadCount(MailItem mail)
{
    if (this.ItemObj is MailItem)
    {
        Outlook.UserProperties userProperties = null;
        Outlook.UserProperty readCountProperty = null;
 
        try
        {
            int readCount = 0;
            userProperties = mail.UserProperties;
            readCountProperty = userProperties["ReadCount"];
            if (readCountProperty == null)
                readCountProperty = userProperties.Add("ReadCount", OlUserPropertyType.olInteger);
 
            readCount = Convert.ToInt32(readCountProperty.Value);
            if (mail.UnRead)
            {
                readCountProperty.Value = 0;
            }
            else
            {
                readCount++;
                readCountProperty.Value = readCount;
            }
            mail.Save();
        }
        finally
        {
            if (readCountProperty != null)
                Marshal.ReleaseComObject(readCountProperty);
            if (userProperties != null)
                Marshal.ReleaseComObject(userProperties);
        }
    }
}

The code for the three events will then be much simpler to read:

public override void ProcessRead()
{
    if (this.ItemObj is MailItem)
    {
        Outlook.MailItem mail = (Outlook.MailItem)this.ItemObj;
        ProcessReadCount(mail);
    }
}
public override void ProcessOpen(AddinExpress.MSO.ADXCancelEventArgs e)
{
    if (this.ItemObj is MailItem)
    {
        Outlook.MailItem mail = (Outlook.MailItem)this.ItemObj;
        ProcessReadCount(mail);
    }
}
public override void ProcessPropertyChange(string name)
{
    if (this.ItemObj is MailItem)
    {
        Outlook.MailItem mail = (Outlook.MailItem)this.ItemObj;
        if (name.ToLower() == "unread")
        {
            ProcessReadCount(mail);
        }
    }
}

If you’re using the Outlook Reading Pane, make sure you check the “Mark items as read when viewed in the Reading Pane” option for this example to work.

Make sure you check the 'Mark items as read when viewed in the Reading Pane' option.

Thank you for reading. Until next time, keep coding!

Available downloads:

This sample Excel add-in was developed using Add-in Express for Office and .net:

Outlook ReadMail sample add-in

4 Comments

  • https://secure.gravatar.com/avatar/c9ce63c400bf6408cfb2a92b961b13e7?s=32&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G German_Mso says:

    Hello Pieter,

    Good meaty article. It has helped me quite a bit and I often refer to it.

    First I’ll make a point and ask a question.

    1) There’s a typo early in this sample
    OutlookItemsEventsClass itemsEvents = null;

    It’s clear you meant OutlookItemEventsClass (No ‘s’ after “Item”). Normally I wouldn’t bother pointing this out, but you have other code out there that handles the “OutlookItemsEvents” that I also use. It caused some confusion. Nothing major, but still it slowed me up a little since I was just starting out.

    2) In the function “private void ConnectToSelectedItem(object explorer)” you release the the object pointed to by “item” if the object is not a “_MailItem”. Since the reference count on an RCW is not incremented on assignments, I’m wondering if it’s incremented in this case because of the array.

  • https://secure.gravatar.com/avatar/c9ce63c400bf6408cfb2a92b961b13e7?s=32&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G German_Mso says:

    In this code you subscribe to an Item’s events if it is open in an inspector window. However, how would you handle an Item that can be modified outside of an Inspector window?

    The AppointmentItem object in someone’s calendar can be modified by either right clicking on it and making a selection from the context menu or by simply drag/dropping the appointment to a new time slot.

    I’m thinking “SelectionChange” event would be appropriate. Not sure.

  • https://secure.gravatar.com/avatar/ab4ec2858cfdf1e44dadf8c50fae314d?s=32&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G Dmitry Kostochko (Add-in Express Team) says:

    Hi German,

    Thank you for your feedback and the detailed information.

    >> 1) There’s a typo early in this sample

    Fixed.

    >> 2) In the function “private void ConnectToSelectedItem(object explorer)” you release the the object pointed to by “item” if the object is not a “_MailItem”….

    We get access to the first element of the Selection collection. If it is the Outlook._MailItem then we connect to its events and pass true to the ConnectTo method of the OutlookItemEventsClass class for this class to release the COM object. Otherwise, i.e. if the first element is a post item, you need to release it yourself.

    >> In this code you subscribe to an Item’s events if it is open in an inspector window. However, how would you handle an Item that can be modified outside of an Inspector window?

    Your assumption about the SelectionChange event is absolutely correct. BTW, the ExplorerSelectionChange event is handled in this article.

  • https://secure.gravatar.com/avatar/c9ce63c400bf6408cfb2a92b961b13e7?s=32&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G German_Mso says:

    Excellent. thank you very much

Post a comment

Have any questions? Ask us right now!