Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Managed MAPI (Part 2) – New Mail Notification

5.00/5 (5 votes)
6 Feb 2013CPOL8 min read 42.8K   694  
Implement a WPF new mail notification.

Introduction

We continue with the previous article to discuss how to get new mail notification from a MAPI Message Store.

Message Store

Message store providers handle the storage and retrieval of messages and other information for the users of client applications. The message information is organized by using a hierarchical system known as a message store. The message store is implemented in multiple levels, with containers called folders holding messages of different types. There is no limit to the number of levels in a message store; folders can contain many subfolders. 

Declare the IMsgStore Interface in C#

This interface provides access to message store information and to messages and folders.

C#
[
    ComImport, ComVisible(false),
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
    Guid("00020306-0000-0000-C000-000000000046")
]
public interface IMsgStore
{
    HRESULT GetLastError(int hResult, uint ulFlags, out IntPtr lppMAPIError);
    HRESULT SaveChanges(uint ulFlags);
    HRESULT GetProps([In, MarshalAs(UnmanagedType.LPArray)] uint[] lpPropTagArray, 
                      uint ulFlags, out uint lpcValues, ref IntPtr lppPropArray);
    HRESULT GetPropList(uint ulFlags, out IntPtr lppPropTagArray);
    HRESULT OpenProperty(uint ulPropTag, ref Guid lpiid, uint ulInterfaceOptions, uint ulFlags, out IntPtr lppUnk);
    HRESULT SetProps(uint cValues, IntPtr lpPropArray, out IntPtr lppProblems);
    HRESULT DeleteProps(IntPtr lpPropTagArray, out IntPtr lppProblems);
    HRESULT CopyTo(uint ciidExclude, ref Guid rgiidExclude, 
       [In, MarshalAs(UnmanagedType.LPArray)] uint[] lpExcludeProps, IntPtr ulUIParam,
        IntPtr lpProgress, ref Guid lpInterface, IntPtr lpDestObj, uint ulFlags, IntPtr lppProblems);
    HRESULT CopyProps(IntPtr lpIncludeProps, uint ulUIParam, IntPtr lpProgress, ref Guid lpInterface,
        IntPtr lpDestObj, uint ulFlags, out IntPtr lppProblems);
    HRESULT GetNamesFromIDs(out IntPtr lppPropTags, ref Guid lpPropSetGuid, uint ulFlags,
        out uint lpcPropNames, out IntPtr lpppPropNames);
    HRESULT GetIDsFromNames(uint cPropNames, ref IntPtr lppPropNames, uint ulFlags, out IntPtr lppPropTags);
    [PreserveSig]
    HRESULT Advise(uint cbEntryID, IntPtr lpEntryID, uint ulEventMask, 
      [In, MarshalAs(UnmanagedType.Interface)] IMAPIAdviseSink pAdviseSink, out uint lpulConnection);
    [PreserveSig]
    HRESULT Unadvise(uint ulConnection);
    [PreserveSig]
    HRESULT CompareEntryIDs(uint cbEntryID1, IntPtr lpEntryID1, uint cbEntryID2, 
            IntPtr lpEntryID2, uint ulFlags, out bool lpulResult);
    [PreserveSig]
    HRESULT OpenEntry(uint cbEntryID, IntPtr lpEntryID, IntPtr lpInterface, 
            uint ulFlags, out uint lpulObjType, out IntPtr lppUnk);
    [PreserveSig]
    HRESULT SetReceiveFolder(string lpszMessageClass, uint ulFlags, uint cbEntryID, IntPtr lpEntryID);
    [PreserveSig]
    HRESULT GetReceiveFolder([MarshalAs(UnmanagedType.LPWStr)]string lpszMessageClass, uint ulFlags, 
      out uint cbEntryID, out IntPtr lppEntryID, [MarshalAs(UnmanagedType.LPWStr)]StringBuilder lppszExplicitClass);
    [PreserveSig]
    HRESULT AbortSubmit(uint cbEntryID, IntPtr lpEntryID, uint ulFlags);
}

Open Message Store

Now we have a MAPI Session object already. But how do we open a message store to access messages and folders?

In the previous article, we knew how to get the message store table and retrieve properties we are interested in. Now we need to retrieve the entry ID property and pass this entry ID to the IMsgStore.OpenMessageStore method.

First we get the message store table.

C#
IntPtr pTable = IntPtr.Zero;
session_.GetMsgStoresTable(0, out pTable);
if (pTable != IntPtr.Zero)
{
   object tableObj = null;
   tableObj = Marshal.GetObjectForIUnknown(pTable);
   content_ = new MAPITable(tableObj as IMAPITable);
}

Second we get the entry ID of the message store with the specified name. If you don’t specify the name, the default store is returned.

C#
if (Content.SetColumns(new PropTags[] { PropTags.PR_DISPLAY_NAME, 
               PropTags.PR_ENTRYID, PropTags.PR_DEFAULT_STORE }))
{
   SRow[] sRows;
   while (Content.QueryRows(1, out sRows))
   {
      if (sRows.Length != 1)
          break;
      if (string.IsNullOrEmpty(storeName))
      {
           if (sRows[0].propVals[2].AsBool)
                     bResult = true;
      }
      else if (sRows[0].propVals[0].AsString.IndexOf(storeName) > -1)
            bResult = true;
      if (bResult)
          break;
   }
}

Last we open the store with the entry ID.

C#
IntPtr pStore = IntPtr.Zero;
if (session_.OpenMsgStore(0, entryId.cb, entryId.lpb, IntPtr.Zero, 
      (uint)MAPIFlag.BEST_ACCESS, out pStore) == HRESULT.S_OK)
{
    if (pStore != IntPtr.Zero)
    {
      IMsgStore msgStore = Marshal.GetObjectForIUnknown(pStore) as IMsgStore;
      CurrentStore = new MessageStore(this, msgStore, new EntryID(entryId.AsBytes), storeName);
    }
}

EntryID class

Although we’ve introduced the SBinary structure in the previous article, it’s not easy enough to use. So now we introduce a new EntryID class to represent the MAPI entry identifier.

C#
public class EntryID
{
   const int DefaultBufferSize = 256;
   private byte[] id_;
  
   public EntryID(byte[] id)
   {
      id_ = id;
   }
  
   public byte[] AsByteArray { get { return this.id_; } }
  
   public static EntryID BuildFromPtr(uint cb, IntPtr lpb)
   {
       byte[] b = new byte[cb];
       for (int i = 0; i < cb; i++)
            b[i] = Marshal.ReadByte(lpb, i);
       return new EntryID(b);
   }
 
   public static EntryID GetEntryID(string entryID)
   {
       if (string.IsNullOrEmpty(entryID))
           return null;
       int count = entryID.Length / 2;
       StringBuilder s = new StringBuilder(entryID);
       byte[] bytes = new byte[count];
       for (int i = 0; i < count; i++)
       {
           if ((2 * i + 2) > s.Length)
              return null;
           string s1 = s.ToString(2 * i, 2);
           if (!Byte.TryParse(s1, System.Globalization.NumberStyles.HexNumber, 
                       null as IFormatProvider, out bytes[i]))
            return null;
       }
       return new EntryID(bytes);
   }
 
       
    public override string ToString()
    {
        StringBuilder s = new StringBuilder(DefaultBufferSize);
        foreach (Byte b in id_)
        {
           s.Append(b.ToString("X2"));
        }
        return s.ToString();
    }
}

Conversion between SBinary and EntryID

We use EntryID at a higher level than SBinary. But we cannot pass EntryID to a MAPI interface or an API function. We need to convert EntryID to SBinary or vice versa.

C#
SBinary sb = SBinary.SBinaryCreate(entryId.AsByteArray);
EntryID = new EntryID(sbinary.AsBytes);

Event Notification in MAPI

Event notification is the communication of information between two MAPI objects. Through one of the objects, a client or service provider registers for notification of a change or error, called an event, which may take place in the other object. After the event occurs, the first object is notified of the change or error. The object receiving the notification is called the advise sink; the object responsible for the notification is called the advise source.

Advise sinks are typically implemented by client applications to receive address book and message store notifications and support the IMAPIAdviseSink : IUnknown interface. IMAPIAdviseSink contains a single method, IMAPIAdviseSink::OnNotify.

Message store and address book providers usually support object notifications on several of their objects and table notifications on their contents and hierarchy tables. Transport providers do not support notifications directly; they rely on alternative methods of communication with clients.

Clients that implement advise sink objects call Advise when they want to register for a notification, in most cases passing in the entry identifier of the object with which registration should occur, and Unadvise when they want to cancel the registration. Clients pass a parameter to Advise that indicates which of the several types of events they want to monitor. Advise returns a nonzero number that represents a successful connection between the advise sink and the advise source.

When an event for which a client has registered occurs, the advise source notifies the advise sink by calling its IMAPIAdviseSink::OnNotify method with a notification data structure that contains information about the event. An advise sink's implementation of OnNotify can perform tasks in response to the notification, such as updating data in memory or refreshing a screen display. 

Define IMAPIAdviseSink interface in C#

The IMAPIAdviseSink interface is used to implement an Advise Sink object for handling notifications.

C#
[
    ComImport, ComVisible(false),
    InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
    Guid("00020302-0000-0000-C000-000000000046")
]
public interface IMAPIAdviseSink
{
    HRESULT OnNotify(uint cNotify, IntPtr lpNotifications);
}

Implement the IMAPIAdviseSink Interface

We have to create our own advise sink object to receive the message store event notification.

C#
delegate void OnAdviseCallbackHandler(IntPtr pContext, uint cNotification, IntPtr lpNotifications);
class MAPIAdviseSink : IMAPIAdviseSink
{
   OnAdviseCallbackHandler callbackHandler_;
   IntPtr pContext_;
       
   public MAPIAdviseSink(IntPtr pContext, OnAdviseCallbackHandler callbackHandler)
   {
        pContext_ = pContext;
        callbackHandler_ = callbackHandler;
   }
  
   public HRESULT OnNotify(uint cNotify, IntPtr lpNotifications)
   {
       if (callbackHandler_ != null)
           callbackHandler_(pContext_, cNotify, lpNotifications);
       return HRESULT.S_OK;
    }
}

Advise/ UnAdvise Message Store New Mail Notification

To register for notification, a pointer to an Advise Sink object is passed in a call to a service provider's IMAPISession::Advise method.

Let’s review the advise and unadvised methods definition in the IMsgStore interface.

C#
HRESULT Advise(uint cbEntryID, IntPtr lpEntryID, uint ulEventMask, 
  [In, MarshalAs(UnmanagedType.Interface)] IMAPIAdviseSink pAdviseSink, out uint lpulConnection);

HRESULT Unadvise(uint ulConnection);

Then we advise the message store new mail notification with the below code.

C#
public bool RegisterEvents(EEventMask eventMask)
{
    callbackHandler_ = new OnAdviseCallbackHandler(OnNotifyCallback);
    HRESULT hresult = HRESULT.S_OK;
    try
    {
        pAdviseSink_ = new MAPIAdviseSink(IntPtr.Zero, callbackHandler_);
        hresult = MAPIStore.Advise(0, IntPtr.Zero, (uint)eventMask, pAdviseSink_, out ulConnection_);
    }
    catch (Exception ex)
    {
        System.Diagnostics.Trace.WriteLine(ex.Message);
        return false;
    }
    return hresult == HRESULT.S_OK;
}

Call Unadvise as the below.

C#
MAPIStore.Unadvise(ulConnection_);

What’s EEventMask?

Advise establishes a connection between the caller's advise sink object and either the message store or an object in the message store. This connection is used to send notifications to the advise sink when one or more events, as specified in the ulEventMask parameter, occur to the advise source object.

The EEventMask is a bitmask composed of one or more of the flags.

C#
public enum EEventMask : uint
{
    /// <summary>
    /// Registers for notifications about severe errors, such as insufficient memory.
    /// </summary>
    fnevCriticalError = 0x00000001,
    /// <summary>
    /// Registers for notifications about the arrival of new messages.
    /// </summary>
    fnevNewMail = 0x00000002,
    /// <summary>
    /// Registers for notifications about the creation of a new folder or message.
    /// </summary>
    fnevObjectCreated = 0x00000004,
    /// <summary>
    /// Registers for notifications about a folder or message being deleted.
    /// </summary>
    fnevObjectDeleted = 0x00000008,
    /// <summary>
    /// Registers for notifications about a folder or message being modified.
    /// </summary>
    fnevObjectModified = 0x00000010,
    /// <summary>
    /// Registers for notifications about a folder or message being moved.
    /// </summary>
    fnevObjectMoved = 0x00000020,
    /// <summary>
    /// Registers for notifications about a folder or message being copied.
    /// </summary>
    fnevObjectCopied = 0x00000040,
    /// <summary>
    /// Registers for notifications about the completion of a search operation.
    /// </summary>
    fnevSearchComplete = 0x00000080,
    /// <summary>
    /// Registers for notifications about a table being modified.
    /// </summary>
    fnevTableModified = 0x00000100,
    /// <summary>
    /// Registers for notifications about a status object being modified.
    /// </summary>
    fnevStatusObjectModified = 0x00000200,
    /// <summary>
    /// Reserved
    /// </summary>
    fnevReservedForMapi = 0x40000000,
    /// <summary>
    /// Registers for notifications about events specific to the particular message store provider.
    /// </summary>
    fnevExtended = 0x80000000,
}

On Notfiy Callback function

Defines a callback function that MAPI calls to send an event notification. This callback function can only be used when wrapped in an advise sink object created.

The below is the C++ notify callback function definition:

C++
ULONG (STDAPICALLTYPE NOTIFCALLBACK)(
  LPVOID lpvContext,
  ULONG cNotification,
  LPNOTIFICATION lpNotifications
);

We convert it to C#:

C#
void OnNotifyCallback(IntPtr pContext, uint cNotification, IntPtr lpNotifications);
  • cNotification: Count of event notifications in the array indicated by the lpNotifications parameter.
  • lpNotifications: Pointer to the location where this function writes an array of notifications structures that contain the event notifications.

Parsing a Notification Structure from Integer Pointer

The NOTIFICATION structure contains information about an event that has occurred and the data that has been effected by the event.

Notification structure definition in C++:

C++
struct {
  ULONG   ulEventType;
    union {
          ERROR_NOTIFICATION          err;
          NEWMAIL_NOTIFICATION        newmail;
          OBJECT_NOTIFICATION         obj;
          TABLE_NOTIFICATION          tab;
          EXTENDED_NOTIFICATION       ext;
          STATUS_OBJECT_NOTIFICATION  statobj;
    } info;
} NOTIFICATION, FAR *LPNOTIFICATION;

In this article, we only discuss new mail notification, so we focus on the NEWMAIL_NOTIFICATION structure. The NEWMAIL_NOTIFICATION structure describes information relating to the arrival of a new message.

Here is the NEWMAIL_NOTIFICATION structure definition in C++:

C++
struct {

  ULONG cbEntryID;

  LPENTRYID lpEntryID;

  ULONG cbParentID;

  LPENTRYID lpParentID;

  ULONG ulFlags;

  LPTSTR lpszMessageClass;

  ULONG ulMessageFlags;

} NEWMAIL_NOTIFICATION;

We convert this definition to C# as below:

C#
[StructLayout(LayoutKind.Sequential)]
public struct NEWMAIL_NOTIFICATION
{
    /// <summary>
    /// Count of bytes in the entry identifier pointed to by the lpEntryID member.
    /// </summary>
    public uint cbEntryID;
    /// <summary>
    /// Pointer to the entry identifier of the newly arrived message.
    /// </summary>
    public IntPtr pEntryID;
    /// <summary>
    /// Count of bytes in the entry identifier pointed to by the lpParentID member.
    /// </summary>
    public uint cbParentID;
    /// <summary>
    /// Pointer to the entry identifier of the receive folder for the newly arrived message.
    /// </summary>
    public IntPtr pParentID;
    /// <summary>
    /// Bitmask of flags used to describe the format of the string properties included with the message.
    /// </summary>
    public uint Flags;
    /// <summary>
    /// The message class of the newly arrived message.
    /// </summary>
    public IntPtr MessageClass;
    /// <summary>
    /// Bitmask of flags that describes the current state of the newly arrived message.
    /// </summary>
    public uint MessageFlags;
}

According to the definition of the NOTIFICATION structure, we first extract an integer to determine the event type. Then we extract the relevant structure per the event type. Shown below is the code:

C#
EEventMask eventType = (EEventMask)Marshal.ReadInt32(lpNotifications);
int intSize = Marshal.SizeOf(typeof(int));
IntPtr sPtr = lpNotifications + intSize * 2;
switch (eventType)
{
    case EEventMask.fnevNewMail:
        {
           Console.WriteLine("New mail");
           if (this.OnNewMail == null)
              break;
           NEWMAIL_NOTIFICATION notification = 
             (NEWMAIL_NOTIFICATION)Marshal.PtrToStructure(sPtr, typeof(NEWMAIL_NOTIFICATION));
        } 
        break;
}

Message Store NewMail Event and MsgStoreNewMailEventArgs

When we get the MAPI notification, it’s ready to fire a Message Store NewMail event.

First we create MsgStoreNewMailEventArgs per NEWMAIL_NOTIFICATION. When marshalling an integer pointer to a string, we use different Marshal functions for ANSI and Unicode. Fortunately, the notification flag tells us if MAPI is using ANSI or Unicode. That’s what we do to get the message class string from the notification structure.

C#
public class MsgStoreNewMailEventArgs : EventArgs
{
    /// <summary>
    /// Entry identification of message store.
    /// </summary>
    public EntryID StoreID { get; private set; }
    /// <summary>
    /// Entry identification of the newly arrived message.
    /// </summary>
    public EntryID EntryID { get; private set; }
    /// <summary>
    /// The entry identifier of the receive folder for the newly arrived messag.
    /// </summary>
    public EntryID ParentID { get; private set; }
    /// <summary>
    /// Bitmask of flags that describes the current state of the newly arrived message.
    /// </summary>
    public int MessageFlags { get; private set; }
    /// <summary>
    /// The message class of the newly arrived message.
    /// </summary>
    public string MessageClass { get; private set; }
       
    public MsgStoreNewMailEventArgs(EntryID storeID, NEWMAIL_NOTIFICATION notification)
    {
        StoreID = storeID;
        SBinary sbEntry = new SBinary() { cb = notification.cbEntryID, lpb = notification.pEntryID };
        SBinary sbParent = new SBinary() { cb = notification.cbParentID, lpb = notification.pParentID };
        EntryID = sbEntry.cb > 0 ? new EntryID(sbEntry.AsBytes) : null;
        ParentID = sbParent.cb > 0 ? new EntryID(sbParent.AsBytes) : null;
        MessageFlags = (int)notification.MessageFlags;
        if ((notification.Flags & (uint)CharacterSet.UNICODE) == (uint)CharacterSet.UNICODE)
            MessageClass = Marshal.PtrToStringUni(notification.MessageClass);
        else
            MessageClass = Marshal.PtrToStringAnsi(notification.MessageClass);
    }
}

Fire the Message Store OnNewMail event:

C#
MsgStoreNewMailEventArgs n = new MsgStoreNewMailEventArgs(StoreID, notification);
if (this.OnNewMail != null)
  this.OnNewMail(this, n);

Message StoreInfo class

Although we can get all the information from the MessageStore class, we need a light class to store the basic information of a message store.

C#
public class StoreInfo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

MAPISession session_;

public StoreInfo(MessageStore store)
{
    session_ = store.Session;
    Name = store.Name;
    EntryId = store.StoreID;
}

public StoreInfo(MAPISession session, string name, EntryID storeId)
{
    session_ = session;
    Name = name;
    EntryId = storeId;
}

public string Name { get; private set; }
public EntryID EntryId { get; private set; }

public bool IsDefault
{
    get
    {
        if (session_ == null)
            return false;
        return Equals(session_.DefaultStore);
     }
}

public bool IsOpened
{
    get
    {
        if (session_ == null)
            return false;
        return Equals(new StoreInfo(session_.CurrentStore));
    }
}

public override bool Equals(object obj)
{
    if (obj is StoreInfo && session_ != null)
    {
        StoreInfo st = obj as StoreInfo;
        if (session_ != st.session_)
            return false;
        return session_.CompareEntryIDs(EntryId, st.EntryId);
    }
    return base.Equals(obj);
}

public override int GetHashCode()
{
    return base.GetHashCode();
}

public void NotifyPropertiesChanged()
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs("IsDefault"));
        PropertyChanged(this, new PropertyChangedEventArgs("IsOpened"));
    }
}
}

Because the StoreInfo class only has the Name and EntryId properties, how do we determine if two StoreInfo objects represent the same message store? Don’t assume we can compare the EntryIDs, because different objects may have the same entry ID in different sessions. We must use the CompareEntryIDs method in the IMAPISession object to check if two entry IDs are the same.

C#
public bool CompareEntryIDs(EntryID entryid1, EntryID entryid2)
{
  SBinary sb1 = SBinary.SBinaryCreate(entryid1.AsByteArray);
  SBinary sb2 = SBinary.SBinaryCreate(entryid2.AsByteArray);
  bool result;
  session_.CompareEntryIDs(sb1.cb, sb1.lpb, sb2.cb, sb2.lpb, 0, out result);
  SBinary.SBinaryRelease(ref sb1);
  SBinary.SBinaryRelease(ref sb2);
  return result;
}

WPF new mail notification application

Subscribe to Message Store OnNewMail event

We subscribe to the message store OnNewMail event when opening a message store.

C#
public bool OpenMessageStore(string storeName)
{
    if (session_.CurrentStore != null)
    {
        session_.CurrentStore.UnRegisteEvents();
        session_.CurrentStore.OnNewMail -= new EventHandler<MsgStoreNewMailEventArgs>(OnNewMail);
    }
    bool ret = session_.OpenMessageStore(storeName);
    if (ret)
    {
        session_.CurrentStore.RegisterEvents(EEventMask.fnevNewMail);
        session_.CurrentStore.OnNewMail += new EventHandler<MsgStoreNewMailEventArgs>(OnNewMail);
    }
    return ret;
}

When the message store OnNewMail event is fired, show the balloon tips:

C#
private void OnNewMail(object sender, MsgStoreNewMailEventArgs e)
{
   ni_.BalloonTipText = "You get a new mail";
   ni_.ShowBalloonTip(500);
}

ni_ is a NotifyIcon that we introduced in the previous article.

C#
ni_ = new System.Windows.Forms.NotifyIcon();

Handle multiple Message Stores

Sometimes there are multiple message stores in a MAPI profile. Although we open the default message store from the start, you can open another store and get its new mail notification from the Settings window.

The red flag means the currently opened store. We use the open command to open the selected message store.

OpenStore command

Commands in MVVM are defined using the ICommand interface which basically defines methods for determining whether a command can be fired, and what it does when you do.

C#
class OpenCommand : ICommand
{
    private SettingsViewModel viewModel_;

    public OpenCommand(SettingsViewModel viewModel)
    {
        viewModel_ = viewModel;
    }

    public bool CanExecute(object parameter)
    {
        if (viewModel_.SelectedStore.IsOpened)
            return false;
        return true;
    }

    public void Execute(object parameter)
    {
        SessionSingleton.Instance.OpenMessageStore(viewModel_.SelectedStore.Name);
        viewModel_.NotifyStoreInfoPropertiesChanged();
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }
}

The Execute method is what the command is doing. In this case, it opens a message store and notifies GUI refresh. The CanExecute method determines if the command can be fired. In this case, if the selected store has been opened already, the open command cannot be fired.

Then how about the CanExecuteChanged event?

This event is raised by the command to notify its consumers that its CanExecute property may have changed. In general, this event simply exposes the CommandManager.RequerySuggested event. The RequerySuggested event is fired quite often, as focus is moved, text selection is changed.

Define an Open Store command member in the ViewModel.

C#
public OpenCommand OpenStore { get; private set; }

Binding the OpenCommand with the Open button in XAML

XML
<Button Grid.Row="2" HorizontalAlignment="Left" Command="{Binding Path=OpenStore}">
    <Button.Style>
        <Style TargetType="{x:Type Button}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <Grid>
                            <Grid.Resources>
                                <Style x:Key="CommandText" TargetType="{x:Type TextBlock}">
                                    <Style.Triggers>
                                        <Trigger Property="IsMouseOver" Value="True">
                                            <Setter Property="Background" Value="#FF448DCA" />
                                            <Setter Property="Foreground" Value="White" />
                                        </Trigger>
                                    </Style.Triggers>
                                </Style>
                            </Grid.Resources>
                            <DockPanel x:Name="imageContainer" 
                                       HorizontalAlignment="Center" DockPanel.Dock="Left">
                                <Image x:Name="image" VerticalAlignment="Center" 
                                   Source="..\Resources\openfile.png" Width="16" Height="16" />
                                <TextBlock Style="{StaticResource CommandText}" 
                                  VerticalAlignment="Center" Margin="2,0,0,0">Open</TextBlock>
                            </DockPanel>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Button.Style>
</Button>

Conclusion

In this article, we introduced how to register and unregister MAPI notification from a message store. Actually we can even go further to show more details of the new mail message, like subject, from address, and so on, because we have the entry ID from MsgStoreNewMailEventArgs. This New Mail Notification application works pretty well for Outlook Exchange accounts. For POP3, IMAP accounts, it only works if Outlook is running. With the Exchange provider, send/receive isn’t really needed. POP3, IMAP providers depend on Outlook running in order to perform send/receive operations. So it’s not that you’re not getting notifications – the mail isn’t arriving at all. We can solve this problem by implementing our own Transport Provider. But that’s not what we discussed here.

A while ago when I looked at the MAPI stuff, a lot of people told me you couldn’t do it in pure .NET level. Although I’m pretty happy with my C++ skills, I don’t like a .NET application being dependant on an unmanaged DLL. It’s not a good architecture. That’s why I wanted to make some adventures on the managed MAPI and publish these two articles.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)