Click here to Skip to main content
15,890,609 members
Articles / Mobile Apps

MAPIEx: Extended MAPI Wrapper

Rate me:
Please Sign up or sign in to vote.
4.93/5 (137 votes)
5 Dec 2009CDDL7 min read 4.4M   18.3K   228   2.2K
A (hopefully) complete extended MAPI wrapper for WinXP, WinCE, and .NET

Introduction

I've been using simple MAPI for years, and recently, I had to send some mail programmatically and was reminded of the dreaded Outlook security warnings. So, I decided to look into the extended MAPI. What I found was a huge collection of samples that did one thing or another, but nothing that really did everything related to messages that I needed. This class, hopefully, covers everything an average user would need to do, including sending and receiving mail including attachments, multiple recipients, CC, BCC, etc. I did not put any code to manipulate information stores or services. See the example CMapiAdmin on CodeProject for this functionality. I've also tested this class using Unicode as well as ANSI strings.

Using CMapiEx

I've included the sample TestMAPI (link above) to show two cases. The project has been updated to Visual Studio 8 (2005), but I believe could be adapted to nearly every version of Visual Studio and Embedded Visual Studio. It's been tested in WinXP using Office 2003, and on Windows Mobile 2003. With a small amount of work removing the MFC code from the class (basically just CString), the class will work on Windows SmartPhone as well. Some of the newer code, including Outlook Forms support and AddressBook support is not supported in Windows Mobile.

To use the class, simply do the following:

  • Call CMAPIEx::Init() and Login() to initialize and login to MAPI.
  • Use OpenMessageStore() to open the desired store, i.e., "SMS"; (NULL for the default store).
  • To receive, call OpenInbox() and GetContents() followed by a looping GetNextMessage() call for each message (optionally reading in only the unread messages).
  • Use HasAttachment and SaveAttachments() to access the message's attachments.
C#
void ReceiveTest(CMAPIEx& mapi)
{
    if(mapi.OpenInbox() && mapi.GetContents()) {
        CMAPIMessage message;
        while(mapi.GetNextMessage(message,TRUE)) {
            printf("Message from '%s' subject '%s'\n", 
              message.GetSenderName(),message.GetSubject());
            if(message.HasAttachments()) {
                printf("saving attachments...");
                message.SaveAttachments(MSG_ATTACHMENT_FOLDER);
                printf("done\n");
            }
        }
    }
}

To send, call OpenOutbox() and CMapiMessage::Create(). Then, set the message properties, and then call CMapiMessage::Send():

C#
void SendTest(CMAPIEx& mapi)
{
    if(mapi.OpenOutbox()) {
        CMAPIMessage message;
        if(message.Create(&mapi,IMPORTANCE_LOW)) {
            message.SetSenderName(FROM_NAME);
            message.SetSenderEmail(FROM_EMAIL);
            message.SetSubject(_T("Subject"));
            message.SetBody(_T("Body"));

            message.AddRecipient(TO_EMAIL);
            message.AddRecipient(TO_EMAIL2);
            message.AddAttachment(MSG_ATTACHMENT);

            if(message.Send()) printf("Sent Successfully");
        }
    }
}

Use Logout() and CMAPIEx::Term() to exit MAPI. Obviously, if you want to try CMAPIEx in a Unicode project, you'll have to use wprintf instead.

RTF/HTML Support

A lot of people requested support for HTML messages. After doing a bunch of research and testing, I found that HTML is actually usually stored in the PR_RTF_COMPRESSED property, and that it can be decoded into HTML if it contains the text \\fromhtml. Thanks to Lucian Wischik for his example, the decoding code in MAPIEx is basically lifted right from his example (see the source for the address). I took it a step further by allowing you to set the RTF property with HTML in order to send an HTML email. Take a look at the SetRTF example in the sample TestMAPI project above, for more details.

.NET Wrapper for CMAPIEx

I originally wrote the NetMAPI wrapper as a proof of concept; it has little functionality beyond the basics. However over the last few months, I received so many questions and requests for enhancements for .NET that I have completely rewritten NetMAPI to be a thin layer above the Win32 DLL, CMAPIEx. NetMAPI offers support for nearly every feature provided in the C++ interface. The included sample above contains a .NET console project, TestNetMAPI, illustrating its usage.

To use the wrapper, first make sure your program has a reference to NetMAPI, and that the compiled MAPIEx.dll is in your output folder or in your path. Then, use NetMAPI.Init() and Login functions to access it:

C#
if(NetMAPI.Init()) {
    NetMAPI mapi=new NetMAPI();
    if(mapi.Login()) {
        // Do some MAPI stuff here...
        mapi.Logout();
    }
    NetMAPI.Term();
}

To receive in .NET (assumes you've logged in successfully):

  • Open the message store you want to access
  • Then open the inbox and get the contents table
  • Iterate through the message using GetNextMessage
  • Don't forget to Dispose of the message when you're finished with it!
C#
public static void ReceiveTest(NetMAPI mapi)
{
    if(mapi.OpenInbox() && mapi.GetContents()) {
        mapi.SortContents(false);

        MAPIMessage message;
        StringBuilder s=new StringBuilder(NetMAPI.DefaultBufferSize);
        while(mapi.GetNextMessage(out message,true)) {
            Console.Write("Message from '");
            message.GetSenderName(s);
            Console.Write(s.ToString()+"' (");
            message.GetSenderEmail(s);
            Console.Write(s.ToString()+"), subject '");
            message.GetSubject(s);
            Console.Write(s.ToString()+"', received: ");
            message.GetReceivedTime(s,"%m/%d/%Y %I:%M %p");
            Console.Write(s.ToString()+"\n");
            // use message.GetBody() to get the ANSI text body
            // use message.GetRTF() to get the RTF or decoded HTML email
            message.Dispose();
        }
    }
}

To send a message (assumes you've logged in successfully):

  • Open the message store you want to access
  • Open the outbox
  • Create a new message, set its priority if you like
  • Set its properties, recipients and attachments
  • Call Send
C#
public static void SendTest(NetMAPI mapi)
{
    if(mapi.OpenOutbox()) {
        MAPIMessage message=new MAPIMessage();
        if(message.Create(mapi,MAPIMessage.Priority.IMPORTANCE_LOW)) {
            message.SetSenderName("Noel");
            message.SetSenderEmail("noel@nospam.com");
            message.SetSubject("Subject");

            // user SetBody for ANSI text, SetRTF for HTML and Rich Text
            message.SetBody("Body");
            
            message.AddRecipient("noel@nospam.com");
            message.AddRecipient("noel@nospam2.com");

            if(message.Send()) Console.WriteLine("Sent Successfully");
        }
    }
}

See the ContactsTest and FoldersTest functions in TestNetMAPI for more examples.

Use the Logout and NetMAPI.Term() call to close the wrapper. To use Unicode, compile CMAPIEx with Unicode set, and change the DefaultCharSet variable in NetMAPI to CharSet.Unicode.

Future Improvements

I'd like to add more support for Calendar and Task items. Outlook 2010 support is another thing coming down the pipeline, currently no tests have been done.

I couldn't figure out how to create a One-Off EntryID in Windows Mobile, as the IAddressBook interface is not implemented. This has the annoying effect of not sending the outgoing emails on these devices until you manually open the message in your Outbox and hit Send. If anyone knows how this is done, please let me know, and I will update it.

This code is free to use as long as the copyright notice remains at the top of the file and any enhancements or bug fixes are posted here for the community. I hope it helps someone skip the pain I went through!

History

  • 07/01/2005 - Initial release
  • 07/13/2005 - Small bug fixes and additions, see below for details
  • 08/25/2005 - Small modifications and a .NET wrapper (see below)
  • 01/27/2006 - Added a bunch of new commands for handling folders and messages (see below)
  • 05/16/2006 - Added RTF and HTML support
  • 06/02/2006 - Added external folder support, and fixed a couple of small bugs (thanks to all who reported the issues)
  • 08/21/2006 - Reorganized files, added support for reading contacts, notifications, and fixed some minor bugs
  • 10/02/2006 - Complete rewrite of the .NET wrapper, with full MAPIEx access from .NET
  • 11/01/2006 - Added support for many new fields, added IMessage Form support and writeable contacts
  • 12/04/2009 - Enhanced performance, completed contacts and added rudimentary Calendar support

Detailed History

  • Fixed a bug with attachments (thanks alan, see forum below for details).
  • While in the attachment code, I modified PR_ATTACH_FILENAME to be the filename of the attachment instead of the full path.
  • Added support for Outlook 2000. To use this class with Outlook 2000, you must call CMAPIEx::Init with a value of zero instead of the default MAPI_UNICODE. Also, building the project for Unicode doesn't work with Office 2000 as far as I can tell (it responds with ANSI strings).
  • Fixed the "mail stays in Outbox after it's sent" problem. See the posting below for more info.
  • Added a priority field to CMAPIMessage::Create, you can now optionally set IMPORTANCE_LOW or IMPORTANCE_HIGH.
  • You can now call AddRecipient repeatedly to send to more than one user.
  • Updated project to Visual Studio 2008.
  • Added a post build step to copy MAPIEx.dll to the test projects' output folders.
  • Added subfolder support to the DLL, you can now iterate through and directly open subfolders.
  • Modified the Login command to take a profile name (thanks Chris, see forum for details).
  • Added the GetProfileName command to find out what the current profile's name is (thanks Jason, author of CMAPIAdmin).
  • Added the ReceivedTime property and the GetReceivedTime function to the message class (thanks again Chris, see forum).
  • Added a CopyMessage function to copy a message between folders as well as the DeleteMessage and MoveMessage functions.
  • Added the CreateSubFolder, DeleteSubFolder functions.
  • Added a OpenSentItems function.
  • Added a new RTF property, this allows us to set RTF text and even HTML (see TestMAPI, for an example).
  • Changed GetBody and GetRTF to be filled on demand rather than when opening the message.
  • MAPIEx can now extract SMTP email addresses from Exchange (EX) native addresses (see FillSenderEmail, for details).
  • MAPIEx can now receive notifications from MAPI (i.e., for new mail arrivals, message deletion etc., see NotificationTest for an example.
  • Added read only support for contacts, see ContactsTest for a sample).
  • Fixed a bug with bad strings from MAPI properties (see GetValidString for more info).
  • Added support for the MAPI_NO_CACHE flag (for Outlook 2003).
  • Added the OpenContacts and OpenDrafts functions.
  • Added a GetRowCount for providing progress feedback for long folder operations.
  • Added support for the AddressList form to get a list of recipients via the common UI. (C++ only, C# if people request it.)
  • Added support for custom Named Properties in Contacts and Messages.
  • Added IMessageForm support to show the default form for editing messages.
  • Added Save, SubmitTime, Sensitivity, MessageFlags, and DeliveryReceipt properties to messages.
  • Added support for enumerating recipients of a message.
  • MAPIContact now supports the three email fields.
  • Added many new fields to contacts such as Company, HomePage, DisplayAs, etc.
  • Added support for creating contacts.
  • Added rudimentary Calendar support (no create appointment functionality yet).
  • Added extensive folder support, see FolderTest for an example.
  • Added CreateProfile and DeleteProfile because of multiple requests.
  • Extended RTF and HTML support, added GetMessageEditorFormat to detect formats.
  • Many small improvements and additions requested by the userbase (too many to list, see forum below for details).

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Software Developer (Senior)
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: Problem to get Birthday and anniversary Pin
Noel Dillabough4-Sep-08 4:23
Noel Dillabough4-Sep-08 4:23 
GeneralRe: Problem to get Birthday and anniversary Pin
manish.patel4-Sep-08 18:31
manish.patel4-Sep-08 18:31 
GeneralRe: Problem to get Birthday and anniversary Pin
Noel Dillabough4-Sep-08 19:38
Noel Dillabough4-Sep-08 19:38 
GeneralRe: Problem to get Birthday and anniversary Pin
manish.patel4-Sep-08 23:24
manish.patel4-Sep-08 23:24 
GeneralRe: Problem to get Birthday and anniversary Pin
manish.patel5-Sep-08 18:55
manish.patel5-Sep-08 18:55 
GeneralRe: Problem to get Birthday and anniversary Pin
Noel Dillabough5-Sep-08 19:17
Noel Dillabough5-Sep-08 19:17 
GeneralRe: Problem to get Birthday and anniversary Pin
manish.patel5-Sep-08 19:20
manish.patel5-Sep-08 19:20 
GeneralUsing MAPIEx from a C# System.Threading.Timer event [modified] Pin
GPalmer29-Aug-08 0:15
GPalmer29-Aug-08 0:15 
Hello,

First let me take a line to thanks Noel for all the work she has put in on this project. It really saved my butt when the security team decided that they did not want to support SMTP and forced MAPI on me. It's a wonderful project in many ways and the code is a great start as a learning exercise. Very well coded and wonderful to read! (Is my GeekBoy background showing? Smile | :) )

I wanted to write up a little code sample for MAPIEx. I think that this problem might be what's bitten some people who were writing services in C# using MAPIEx and in the little bit of reading I did, I didn't see an answer to it. Of course, I might be the only guy in the room who didn't know this information already in which case please be gentle. Smile | :)

One characteristic of MAPIEx is that it needs to be executed in a thread using the Single Threaded Apartment model. This is what Noel's [STAThread] decoration does in the TestNetMAPI project. This works fine on the entry points to services in C# also. In these cases, the [STAThread] decoration does set the threading model for the service's main thread and MAPIEx runs great in places like the Start and Stop events since they are executed in the main thread.

The problem occurs when you try to send mail from either a System.Threading.Timer or a System.Timers.Timer event. These timers fire events using ThreadPools which are running using the Multi Threaded Apartment model. Unfortunately when you try using MAPIEx directly out of one of these events it fails to initialize because you are calling it from an MTA thread. Since sending mail from these events is what I care about most, and I had no stomach for duplicating Noel's efforts, I needed to find a way to use MAPI from one of the fired timer events.

While the Windows.Forms.Timer control does execute in the main thread and therefore would be set to STA by the [STAThread] decoration it is unstable in a Windows service. In my case I have a Windows service written in C# which runs and periodically checks for input, if it doesn't find it, it should generate an exception mail via MAPI. It would normally be possible to marshal the threads using the designer controls of the service and use them to marshal the thread to STA. In my case though, I have the service setup so that it can be started either as a Console application (for debugging) or as a service so these controls are not guaranteed to be available.

In the code below, the Main method is entered using the MTA threading model which is the default for C#. This means that if you attempted to call NetMAPI.Init( ) in the Main it would fail. This is analogous to what happens when the timer event is triggered and you attempt to Init MAPIEx from an event. When you are in the timer event there is no way to force the thread you are executing on to transition from an MTA apartment model to an STA apartment model since that thread belongs to the program calling you. Allowing the change of the calling thread would allow you to change the thread of any program which called you or controls which you called to change the caller thread's threading model silently which would probably cause serious issues.

I have prepared the sample below to be as simple as possible. It is meant to illustrate how you can easily call a component using a STA thread from a thread using MTA. It is not the easiest way to do this in a Console main function where you can directly set the model of the thread since you own it. It is the simplest program I could think of to illustrate the transitions that happen when the Timer triggers an event in a service.

The point here is to demonstrate transitioning from the MTA model to the STA model and the console application is the easiest way to demonstrate the technique. In the example below, the CurrentThread thread is running using the same MTA model as the threads on which you enter the Timer events. As mentioned above, since the thread is using the MTA model directly initializing the MAPIEx component would fail in the Main function using the main thread. What you can do though is to create a new thread and control its apartment model. So what this code does is to create a new thread which is using the STA model and use that to execute MAPIEx. It blocks until the STA thread sending mail completes by calling the Join method on the STA thread and then waits for a keypress to end the console program.

Regards,
Greg

using System;
using System.Threading;
using MAPIEx;

namespace ThreadedMAPI {

    class ApartmentTest {

        static void Main( ) {
            Thread newThread = new Thread( new ThreadStart( SendMAPIMail ) );

            Console.WriteLine( "newThread pre-SetApartmentState ThreadState: {0}, newThread ApartmentState: {1}", 
				newThread.ThreadState, 
				newThread.GetApartmentState( ) );
				
            Console.WriteLine( "CurrentThread pre-SetApartmentState ApartmentState: {0}", Thread.CurrentThread.GetApartmentState( ) );

            newThread.SetApartmentState( ApartmentState.STA );

            Console.WriteLine( "newThread ThreadState: {0}, newThread ApartmentState: {1}, CurrentThread ApartmentState {2}",
				newThread.ThreadState, 
				newThread.GetApartmentState( ), 
				Thread.CurrentThread.GetApartmentState( ) );

            newThread.Start( );
            newThread.Join( );

            Console.WriteLine( "CurrentThread apartmentState after newThread ends {0}", Thread.CurrentThread.GetApartmentState( ) );

            Console.ReadKey( );
        }

        static void SendMAPIMail( ) {

            Console.WriteLine( "SendMAPIMail ApartmentState: {0}", Thread.CurrentThread.GetApartmentState( ) );

            NetMAPI mapi = null;
            MAPIMessage message = null;

            try {
                if( NetMAPI.Init( ) ) {
                    mapi = new NetMAPI( );

                    if( mapi.Login( "ProfileName", false ) ) {
                        if( mapi.OpenMessageStore( ) ) {
                            if( mapi.OpenOutbox( ) ) {
                                message = new MAPIMessage( );
                                if( message.Create( mapi, MAPIMessage.Importance.IMPORTANCE_HIGH ) ) {
                                    message.SetSenderName( "Sender Name" );
                                    message.SetSenderEmail( "SomeOne@SomeWhere.com" );
                                    message.SetSubject( "This is the subject of the message." ) );
                                    message.SetBody( "This is the body of the message." );
                                    message.AddRecipient( "SomeOneElse@SomeWhere.com" );
                                    message.Send( );
                                }
                            }
                        }
                        mapi.Logout( );
                    }
                    NetMAPI.Term( );
                }

            } catch( Exception ex ) {
				Console.WriteLine( "There was an exception. The exception message was: {0}", ex.Message );

            } finally {
                if( null != message ) {
                    message.Dispose( );
                    message = null;
                }
                if( null != mapi ) {
                    mapi.Dispose( );
                    mapi = null;
                }
            }
        }
    }
}


modified on Tuesday, September 2, 2008 3:42 AM

GeneralRe: Using MAPIEx from a C# System.Threading.Timer event Pin
Noel Dillabough1-Sep-08 5:19
Noel Dillabough1-Sep-08 5:19 
GeneralRe: Using MAPIEx from a C# System.Threading.Timer event Pin
GPalmer1-Sep-08 13:27
GPalmer1-Sep-08 13:27 
QuestionContact & Calendar Support in ur Latest Code Pin
manishaa_p29-Aug-08 0:12
manishaa_p29-Aug-08 0:12 
AnswerRe: Contact & Calendar Support in ur Latest Code Pin
Noel Dillabough29-Aug-08 6:42
Noel Dillabough29-Aug-08 6:42 
GeneralCompilation for Windows 2000 Pin
GeertMys28-Aug-08 7:49
GeertMys28-Aug-08 7:49 
GeneralWindows 2000 and MAPI_NO_CACHE [modified] Pin
GeertMys28-Aug-08 7:22
GeertMys28-Aug-08 7:22 
QuestionLatest code download Pin
2355#27-Aug-08 3:16
2355#27-Aug-08 3:16 
QuestionHow to set Sent date. Pin
JeongWS24-Aug-08 15:59
JeongWS24-Aug-08 15:59 
AnswerRe: How to set Sent date. Pin
Noel Dillabough24-Aug-08 18:34
Noel Dillabough24-Aug-08 18:34 
QuestionPOP3 profile spawns System.AccessViolationException on Vista Home Pin
joycekc19-Aug-08 10:43
professionaljoycekc19-Aug-08 10:43 
AnswerRe: POP3 profile spawns System.AccessViolationException on Vista Home Pin
Noel Dillabough20-Aug-08 6:10
Noel Dillabough20-Aug-08 6:10 
GeneralHelped me a lot, great thanks! Pin
popota15-Aug-08 6:09
popota15-Aug-08 6:09 
GeneralRe: Helped me a lot, great thanks! Pin
Noel Dillabough15-Aug-08 6:52
Noel Dillabough15-Aug-08 6:52 
GeneralRe: Helped me a lot, great thanks! Pin
Noel Dillabough15-Aug-08 6:53
Noel Dillabough15-Aug-08 6:53 
GeneralBuild MAPIEx Pin
APTL13-Aug-08 3:47
APTL13-Aug-08 3:47 
GeneralRe: Build MAPIEx Pin
Noel Dillabough13-Aug-08 8:41
Noel Dillabough13-Aug-08 8:41 
GeneralAccess Violation Errors and Managed Code Pin
jk997-Aug-08 3:38
jk997-Aug-08 3:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.