Using MAPI to Create Outlook 2007 Items

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

Summary: Learn how to use the Microsoft Exchange Server Protocols documentation to create a contact item in Microsoft Office Outlook 2007 using the Messaging Application Programming Interface (MAPI). (16 printed pages)

Randy Byrne, Senior Program Manager, Outlook Product Team, Microsoft Corporation

Stephen Griffin, Escalation Engineer, Developer Support Messaging, Microsoft Corporation

June 2008

Applies to: Microsoft Office 2007 Outlook

Contents

  • Overview of Outlook Extensibility

  • MAPI and Outlook Items

  • Exchange Server Protocols Documentation Used to Create an Outlook Contact

  • Installation Instructions

  • Running MFCMAPI

  • Mapping Property Names and Types

  • Using MAPI to Create a Contact

  • Conclusion

  • Additional Resources

Download MFCMAPI.

Download the CreateOutlookItemsAddin project.

Overview of Outlook Extensibility

Until the publication of the Microsoft Exchange Server Protocols, the properties required to create a Microsoft Office Outlook contact item had not been documented. This article shows you how to use the Exchange Server Protocols documentation to create a contact item using the Messaging Application Programming Interface (MAPI).

Microsoft Office Outlook 2007 provides a Component Object Model (COM) type library to enable developers to customize Outlook. The common name for this type library is the Outlook object model. Most Outlook customizations are built by using either native or managed add-ins that use the Outlook object model. An Outlook add-in must implement the IDTExtensibility2 interface. This interface provides events that enable your DLL or assembly to load in the Outlook process. After the add-in is loaded, native add-ins communicate with the Outlook object model directly through COM, while managed add-ins use COM Interop and communicate with the object model through the primary interop assembly (PIA) wrapper.

Both the Outlook object model and the add-in architecture are strategic for Outlook extensibility. However, creating an Outlook add-in is not appropriate for all scenarios. The Messaging Application Programming Interface provides an alternative extensibility model for scenarios that require store, address book, or transport providers. To implement a provider, you use MAPI and a native development tool such as Microsoft C++. Other solutions require low-level data access through MAPI to properties and structures that are not exposed in the Outlook object model. For example, the following scenarios lend themselves to using MAPI rather than an add-in that is built on the Outlook object model:

  • Creating a synchronization application that syncs contact items between a phone device and the default Contacts folder in multiple versions of Outlook. Using the Outlook object model and an add-in is problematic because all calls to the object model run on the main foreground thread in Outlook. For synchronization scenarios that must not block the Outlook user interface (UI), reading and writing items via MAPI is recommended. When you use MAPI instead of the Outlook object model, you should consider doing synchronization work on a background thread.

  • Creating a Windows service application that writes appointment data to the default Calendar folder for a specific Outlook profile. Use of the Outlook object model is not supported in a Windows service application. The object model can display UI, which is one of the reasons that use of the object model is not supported in a service application.

  • Creating a native component that exposes functionality that is not available in the Outlook object model. Although the Outlook object model has been improved significantly in Outlook 2007, the object model does not accommodate some advanced scenarios. Also, the use of MAPI is not supported in managed code. There are circumstances when a developer wraps MAPI code in a native component that can be remotely invoked from managed or native code. For more information about supported APIs, see the Microsoft Knowledge Base article, The support guidelines for client-side messaging development.

In most scenarios, Microsoft recommends using the Outlook object model and an add-in component for Outlook customization. The object model encapsulates the Outlook business logic and guarantees that this business logic is followed when creating or modifying items. However, there are scenarios that require the use of MAPI instead of the Outlook object model. MAPI is a low-level API used to access data in stores and address book containers. Let’s consider how you would accomplish the first scenario listed earlier, namely creating an Outlook contact using MAPI.

MAPI and Outlook Items

MAPI is essentially Outlook item agnostic. Outlook items such as e-mail messages, appointments, contacts, or task items do not represent separate types in the world of MAPI. An item is represented by the IMessage interface, which is generic to all Outlook item types. The purpose of this article is to show you how to create a first-class Outlook contact item by using MAPI instead of the Outlook object model.

When developers try to create or modify an Outlook item via MAPI, they have confronted the problem that not all Outlook item properties and data structures are documented. In the upcoming version of the Outlook 2007 MAPI Reference, both named and MAPI properties will be documented for the developer community. In the interim, this article shows you how to use the Microsoft Exchange Server Protocols Documentation to gain an understanding of the properties required to create a complete Outlook item using MAPI.

The Microsoft Exchange Server Protocols documentation provides detailed technical specifications for Microsoft proprietary protocols (including extensions to industry-standard or other published protocols) that are implemented and used in the Microsoft Exchange Server system (specifically the Exchange Server 2007 Service Pack 1 release) to interoperate or communicate with other products. There are over sixty documents in the documentation set that consist of thousands of pages of detailed technical specifications for the over-the-wire protocols that enable Exchange Server to communicate with clients such as the Microsoft Office Outlook 2007 Service Pack 1 release.

Exchange Server Protocols Documentation Used to Create an Outlook Contact

Let's focus on the specific documents that will help you accomplish the task of creating an Outlook contact item using MAPI. Because the scope of this exercise is narrow, you should familiarize yourself with the following documents.

Table 1. Exchange Server Protocols Documentation

Protocol

Description

[MS-OXCDATA]: Data Structures Protocol Specification

Defines the common basic structures that are used in remote operations.

[MS-OXOCNTC]: Contact Object Protocol Specification

Specifies the properties and operations that are permissible on contacts and personal distribution lists.

[MS-OXPROPS]: Office Exchange Protocols Master Property List Specification

Contains the definition of each property that is used in the objects that are described by [MS-OXO]-prefixed documents.

Microsoft documents the protocol requirements for a specific item type in an object protocol specification. An object protocol specification document is named [MS-OXOTTTT], where TTTT represents the type of object described in the document. Common object protocol specifications are as follows.

Table 2. Common object protocol specifications

Protocol

Description

[MS-OXOCAL]: Appointment and Meeting Object Protocol Specification

Specifies the properties and operations for appointment, meeting request, and response messages.

[MS-OXOCNTC]: Contact Object Protocol Specification

Specifies the properties and operations that are permissible on contacts and personal distribution lists.

[MS-OXOMSG]: E-mail Object Protocol Specification

Specifies the properties and operations that are permissible on e-mail messages.

[MS-OXOTASK]: Task-Related Objects Protocol Specification

Specifies the properties and operations that are permissible for task, task request, and response messages.

In each of the object protocol specification documents, consult Section 4, "Protocol Examples," for a step-by-step presentation of the properties required to create the example item in Outlook or Exchange Server. Be aware that the protocol examples assume that you are writing a protocol client to communicate with an Exchange server or a protocol server to communicate with a client such as Microsoft Office Outlook 2007. The protocol documentation does not discuss MAPI as the means to create protocol objects. In this article, you will learn how to translate between protocol documents and MAPI types and properties.

Installation Instructions

To obtain the sample code used in this article, follow these steps.

To install the sample code used in this article

  1. Download the current version of the MFCMAPI executable to a folder on your system.

  2. Extract all the files in Mfcmapi.exe.[version].zip to an empty folder on your hard drive.

  3. Download the current version of the CreateOutlookItemsAddin project.

  4. Extract all the files in CreateOutlookItemsAddin.zip to the folder where you extracted MFCMAPI in Step 2.

  5. Copy Mfcmapi.exe from the folder used in Step 2 to the build directory for the CreateOutlookItemsAddin project (\CreateOutlookItemsAddin\Debug)

  6. Open the CreateOutlookItemsAddin project in Visual Studio to examine the source code.

Running MFCMAPI

The following steps assume that you have downloaded the current version of MFCMAPI executable and the CreateOutlookItemsAddin project. This section will guide you to the menu item that enables you to create a contact using MFCMAPI and the CreateOutlookItemsAddin project.

  1. Start Mfcmapi.exe in the CreateOutlookItemsAddin\Debug folder that is created when you follow the installation instructions.

  2. Click OK to dismiss the MFCMAPI splash screen.

  3. On the Session menu, select Logon and Display Store Table.

  4. A profile dialog box appears. Select the correct profile, and then click OK. Otherwise MFCMAPI logs on to a MAPI session.

  5. Double-click Mailbox - [User Name] in the store table list view.

  6. In the folder tree view, expand the Root - Mailbox node.

  7. In the folder tree view, expand the IPM_SUBTREE node.

  8. In the folder tree view, double-click the Contacts folder under IPM_SUBTREE.

  9. On the Addins menu, select Add Contact to open the Add Contact dialog box.

  10. Click OK to create the sample contact shown in Figure 1 in your default Contacts folder.

Figure 1. Sample contact item created using MFCMAPI

Sample contact item created using MFCMAPI

Mapping Property Names and Types

The sample code that accompanies this article shows you how to create an Outlook contact item using MFCMAPI and the CreateOutlookItemsAddin project. MFCMAPI is a reference application for MAPI developers, and full source for MFCMAPI is available on CodePlex. MFCMAPI supports an add-in model so that extensions to the application can ship as a separate DLL component. In this case, the download that accompanies this article provides the CreateOutlookItemsAddin project for MFCMAPI. Discussion of the add-in architecture used by MFCMAPI is beyond the scope of this technical article.

Before diving into the sample code, it's important to understand how a MAPI developer can use the protocols documentation. MAPI properties are defined in MAPI Properties in the Outlook 2007 MAPI Reference and in the Mapitags.h file that is included in the Windows SDK. However, named properties used by Outlook are not documented in the MAPI Programmer’s Reference. A forthcoming version of the Outlook 2007 MAPI Reference will document all named properties used by Office Outlook. In the meantime, you must map canonical property names and property types defined in the protocol documentation to MAPI property sets, property identifiers, property tags, and property types. This mapping is straightforward when you understand that the protocol documentation uses its own identifiers for property name and property types.

Let's start with the types defined in the [MS-OXCDATA] document. Table 3 illustrates the mapping for commonly used property types. For a complete mapping of all property types, consult [MS-OXCDATA].

Table 3. Property type mapping to MAPI property value types

Canonical property type

Property type value

Property type specification

MAPI property type

PtypInteger16

0x0002

2 bytes, a 16-bit integer

PT_SHORT, PT_I2

PtypInteger32

0x0003

4 bytes, a 32-bit integer

PT_LONG, PT_I4

PtypFloating32

0x0004

4 bytes, a 32-bit floating point number

PT_FLOAT, PT_R4

PtypFloating64

0x0005

8 bytes, a 64-bit floating point number

PT_DOUBLE, PT_R8

PtypCurrency

0x0006

8 bytes, a 64-bit signed, scaled integer representation of a decimal currency value, with 4 places to the right of the decimal point

PT_CURRENCY

PtypFloatingTime

0x0007

8 bytes, a 64-bit floating point number in which the whole number part represents the number of days since December 30, 1899, and the fractional part represents the fraction of a day since midnight

PT_APPTIME

PtypErrorCode

0x000A

4 bytes, a 32-bit integer encoding error information

PT_ERROR

PtypBoolean

0x000B

1 byte, restricted to 1 or 0

PT_BOOLEAN

PtypInteger64

0x0014

8 bytes, a 64-bit integer

PT_LONGLONG, PT_I8

PtypString

0x001F

Variable size, a string of Unicode characters in UTF-16LE encoding with terminating null character (2 bytes of 0)

PT_UNICODE

PtypString8

0x001E

Variable size, a string of multi-byte characters in externally specified encoding with terminating null character (single 0 byte)

PT_STRING8

PtypTime

0x0040

8 bytes, a 64-bit integer representing the number of 100-nanosecond intervals since January 1, 1601

PT_SYSTIME

PtypGuid

0x0048

16 bytes, a GUID with Data1, Data2, and Data3 fields in little-endian format

PT_CLSID

PtypBinary

0x0102

Variable size, a COUNT followed by that many bytes

PT_BINARY

PtypMultipleInteger32

0x1003

Variable size, a COUNT followed by that many PtypInteger32 values

PT_MV_LONG, PT_MV_I4

PtypMultipleString

0x101F

Variable size, a COUNT followed by that many PtypString values

PT_MV_UNICODE

PtypMultipleString8

0x101E

Variable size, a COUNT followed by that many PtypString8 values

PT_MV_STRING8

PtypMultipleTime

0x1040

Variable size, a COUNT followed by that many PtypTime values

PT_MV_SYSTIME

PtypMultipleGuid

0x1048

Variable size, a COUNT followed by that many PtypGuid values

PT_MV_CLSID

PtypMultipleBinary

0x1102

Variable size, a COUNT followed by that many PtypBinary values

PT_MV_BINARY

PtypObject or PtypEmbeddedTable

0x000D

The property value is a COM object

PT_OBJECT

Property name mapping is also clearly defined in the [MS-OXPROPS] document. The two most important canonical naming conventions in the protocols documentation are PidTag<Name> and PidLid<Name>. The value of name is usually not the same when comparing the canonical property name to its MAPI property name. For example, the canonical name for the Subject property is PidTagSubject, and the MAPI property name is PR_SUBJECT. Table 4 shows the naming conventions and how they map to MAPI property naming conventions.

Table 4. Property name mapping to MAPI names

Canonical name

Description

MAPI property name

PidTag<Name>

A property defined by MAPI and specified by a property tag. All properties in MAPI have a property tag that is a 32-bit number with bits 16 through 31 defining a unique property identifier, and bits 0 through 15 defining the property type. The property identifier for properties defined by MAPI must fall in the range of 0x001 and 0x7FF.

PR_<Name>

PidLid<Name>

A named property that uses a 32-bit numeric value as its name. The property is defined by this numeric name and a property set that is specified by a GUID. The property identifier for all named properties must fall in the range of 0x8000 and 0x8FF.

A named property with ulKind = MNID_ID and Kind.lID = a valid property set GUID.

PidName<Name>

A named property that uses a string as its name . The property is defined by this string name and a property set GUID.

A named property with ulKind = MNID_STRING and Kind.lID = a valid property set GUID.

Using MAPI to Create a Contact

Given the mapping of property names and types between the Exchange Server Protocols documentation and MAPI, the next step is to determine which properties are required to create a contact item. The exact list of properties depends on the properties that you want to implement in your solution. For the example discussed in this article, the properties are defined in Contacts.cpp. Note that you must also specify a property set for the named properties associated with a specific item type. In this case, the property set is PSETID_Address, which is used for contact items.

// PSETID_Address GUID {00062004-0000-0000-c000-000000000046}
// Propset used for contact items
DEFINE_OLEGUID(PSETID_Address, MAKELONG(0x2000+(0x04),0x0006),0,0);

#define PidLidFileUnderList                0x8026
#define PidLidAutoLog                      0x8025
#define PidLidAddressBookProviderEmailList 0x8028
#define PidLidAddressBookProviderArrayType 0x8029
#define PidLidFileUnder                    0x8005
#define PidLidFileUnderId                  0x8006
#define PidLidContactCharSet               0x8023
#define PidLidEmail1DisplayName            0x8080
#define PidLidEmail1AddressType            0x8082
#define PidLidEmail1EmailAddress           0x8083
#define PidLidEmail1OriginalDisplayName    0x8084
#define PidLidEmail1OriginalEntryID        0x8085
#define PidLidWorkAddressStreet            0x8045
#define PidLidWorkAddressCity              0x8046
#define PidLidWorkAddressState             0x8047
#define PidLidWorkAddressPostalCode        0x8048
#define PidLidWorkAddressCountry           0x8049
#define PidLidWorkAddressCountryCode       0x80DB
#define PidLidPostalAddressId              0x8022
#define PidLidAddressCountryCode           0x80DD
#define PidLidContactItemData              0x8007
#define PidLidHtml                         0x802B

After you define the named properties, the next step is to construct an array that is passed to IMAPIProp::GetIDsFromNames. IMAPIProp::GetIDsFromNames performs for each named property in the array a conversion from the name (numeric name or string name) to the property tag. The aulContactProps array contains the named properties that are to be converted by the GetIDsFromNames method.

// The array is the list of named properties to be set.
ULONG aulContactProps[] = {
    PidLidFileUnderList,
    PidLidAutoLog,
    PidLidAddressBookProviderEmailList,
    PidLidAddressBookProviderArrayType,
    PidLidFileUnder,
    PidLidFileUnderId,
    PidLidContactCharSet,
    PidLidEmail1DisplayName,
    PidLidEmail1AddressType,
    PidLidEmail1EmailAddress,
    PidLidEmail1OriginalDisplayName,
    PidLidEmail1OriginalEntryID,
    PidLidWorkAddressStreet,
    PidLidWorkAddressCity,
    PidLidWorkAddressState,
    PidLidWorkAddressPostalCode,
    PidLidWorkAddressCountry,
    PidLidWorkAddressCountryCode,
    PidLidPostalAddressId,
    PidLidAddressCountryCode,
    PidLidContactItemData,
    PidLidHtml,
};

The GetIDsFromNames method is called in the AddContact method in Contacts.cpp. AddContact takes numerous parameters from the Add Contact dialog box that is displayed when the user selects Add Contact on the Addins menu in MFCMAPI. The DisplayAddContactDialog method in Contacts.cpp displays the dialog box and passes values from the dialog box to the AddContact method. The DisplayAddContactDialog method does not relate directly to creating a contact item using MAPI, so it is not listed here. The AddContact method is listed below. Note that the first parameter passed to the AddContact method is a pointer to an IMAPIFolder interface. Given lpFolder that represents an IMAPIFolder interface, the code calls IMAPIFolder::CreateMessage. The CreateMessage method returns a success code and a pointer to a pointer to an IMessage interface. Most of the AddContact function code handles the work of property setting in preparation for IMAPIProp::SetProps. If the SetProps call succeeds, IMAPIProp::SaveChanges commits the changes to the store and creates a new contact item.

HRESULT AddContact(LPMAPIFOLDER lpFolder,
            LPWSTR szFullName, 
            LPWSTR szGivenName, 
            LPWSTR szMiddleName, 
            LPWSTR szSurName, 
            LPWSTR szInitials, 
            LPWSTR szFileUnder, 
            LPWSTR szEmailDisplayName, 
            LPWSTR szEmailAddressType, 
            LPWSTR szEmailAddress, 
            LPWSTR szEmailOriginalDisplayName, 
            LPWSTR szCompany, 
            LPWSTR szBusinessStreet, 
            LPWSTR szBusinessCity, 
            LPWSTR szBusinessState, 
            LPWSTR szBusinessPostalCode, 
            LPWSTR szBusinessCountry, 
            LPWSTR szBusinessCountryCode, 
            LPWSTR szBusinessPhone, 
            LPWSTR szURL, 
            LPWSTR szNotes 
            )
{
    if (!lpFolder) return MAPI_E_INVALID_PARAMETER;
    HRESULT hRes = S_OK;
    LPMESSAGE lpMessage = 0;

    // Create a message and set its properties.
    hRes = lpFolder->CreateMessage(0,
        0,
        &lpMessage);
    if (SUCCEEDED(hRes))
    {
        MAPINAMEID  rgnmid[ulContactProps];
        LPMAPINAMEID rgpnmid[ulContactProps];
        LPSPropTagArray lpNamedPropTags = NULL;

        ULONG i = 0;
        for (i = 0 ; i < ulContactProps ; i++)
        {
            rgnmid[i].lpguid = (LPGUID)&PSETID_Address;
            rgnmid[i].ulKind = MNID_ID;
            rgnmid[i].Kind.lID = aulContactProps[i];
            rgpnmid[i] = &rgnmid[i];
        }

        hRes = lpFolder->GetIDsFromNames(
            ulContactProps, 
            (LPMAPINAMEID*) &rgpnmid, 
            NULL, 
            &lpNamedPropTags);
        if (SUCCEEDED(hRes) && lpNamedPropTags)
        {
            // Since we know in advance which props 
            // we'll be setting, we can statically
            // declare most of the structures involved 
            // and save expensive MAPIAllocateBuffer calls.
            // For brevity, code to set most spvProps
            // has been removed. For the complete listing, see
            // AddContact in contacts.cpp.

            spvProps[p_PR_DISPLAY_NAME_PREFIX_W].ulPropTag =
                PR_DISPLAY_NAME_PREFIX_W;
            spvProps[p_PR_SURNAME_W].ulPropTag =
                PR_SURNAME_W;
            spvProps[p_PR_MIDDLE_NAME_W].ulPropTag =
                PR_MIDDLE_NAME_W;
            spvProps[p_PR_GIVEN_NAME_W].ulPropTag =
                PR_GIVEN_NAME_W;
            spvProps[p_PR_GENERATION_W].ulPropTag =
                PR_GENERATION_W;
            spvProps[p_PR_INITIALS_W].ulPropTag =
                PR_INITIALS_W;
            spvProps[p_PR_COMPANY_NAME_W].ulPropTag =
                PR_COMPANY_NAME_W;
            spvProps[
                p_PR_BUSINESS_HOME_PAGE_W].ulPropTag =
                PR_BUSINESS_HOME_PAGE_W;
            spvProps[
                p_PR_BUSINESS_TELEPHONE_NUMBER_W].ulPropTag =
                PR_BUSINESS_TELEPHONE_NUMBER_W;
            spvProps[p_PR_POSTAL_ADDRESS_W].ulPropTag =
                PR_POSTAL_ADDRESS_W;
            spvProps[p_PR_STREET_ADDRESS_W].ulPropTag =
                PR_STREET_ADDRESS_W;
            spvProps[p_PR_LOCALITY_W].ulPropTag =
                PR_LOCALITY_W;
            spvProps[p_PR_STATE_OR_PROVINCE_W].ulPropTag =
                PR_STATE_OR_PROVINCE_W;
            spvProps[p_PR_POSTAL_CODE_W].ulPropTag =
                PR_POSTAL_CODE_W;
            spvProps[p_PR_COUNTRY_W].ulPropTag =
                PR_COUNTRY_W;
            spvProps[p_PR_DISPLAY_NAME_W].ulPropTag =
                PR_DISPLAY_NAME_W;
            spvProps[p_PR_MESSAGE_CLASS_W].ulPropTag =
                PR_MESSAGE_CLASS_W;
            spvProps[p_PR_ICON_INDEX].ulPropTag =
                PR_ICON_INDEX;
            spvProps[p_PR_SUBJECT_PREFIX_W].ulPropTag =
                PR_SUBJECT_PREFIX_W;
            spvProps[p_PR_SUBJECT_W].ulPropTag =
                PR_SUBJECT_W;
            spvProps[p_PR_BODY_W].ulPropTag = 
                PR_BODY_W;

            spvProps[p_PidLidFileUnderList].
                Value.MVl.cValues =
                5;
            LONG lFileUnder[5];
            // Surname, Given Middle
            lFileUnder[0] = 0x00008017; 
            // Given Middle Surname Generation
            lFileUnder[1] = 0x00008037; 
            // Company Name (PR_COMPANY_NAME)
            lFileUnder[2] = 0x00003a16; 
            // Surname, Given Middle\r\nCompany Name
            lFileUnder[3] = 0x00008019;
            // Company Name\r\nSurname, Given Middle
            lFileUnder[4] = 0x00008018; 
            spvProps[p_PidLidFileUnderList].
                Value.MVl.lpl =
                lFileUnder;

            // Do not journal.
            spvProps[p_PidLidAutoLog].Value.b = false; 

            spvProps[p_PidLidAddressBookProviderEmailList].
                Value.MVl.cValues = 1;
            LONG lAddressBookProviderEmail[1];

             // Email1 is defined.
            lAddressBookProviderEmail[0] = 0x00000000;
            spvProps[p_PidLidAddressBookProviderEmailList].
                Value.MVl.lpl = 
                lAddressBookProviderEmail;

            // Email1 is defined.
            spvProps[p_PidLidAddressBookProviderArrayType].
                Value.l =
                0x0000001; 
            
            spvProps[p_PidLidFileUnder].Value.lpszW =
                szFileUnder;
            // Surname, Given Middle
            spvProps[p_PidLidFileUnderId].Value.l =
                0x8017; 
            // Generic value denoting 
            // "western" character set.
            spvProps[p_PidLidContactCharSet].Value.l = 
                0x00000100; 
            spvProps[p_PidLidEmail1DisplayName].
                Value.lpszW = 
                szEmailDisplayName;
            spvProps[p_PidLidEmail1AddressType].
                Value.lpszW = 
                szEmailAddressType;
            spvProps[p_PidLidEmail1EmailAddress].
                Value.lpszW = 
                szEmailAddress;
            spvProps[p_PidLidEmail1OriginalDisplayName].
                Value.lpszW = 
                szEmailOriginalDisplayName;

            spvProps[p_PidLidWorkAddressStreet].
                Value.lpszW = 
                szBusinessStreet;
            spvProps[p_PidLidWorkAddressCity].
                Value.lpszW = 
                szBusinessCity;
            spvProps[p_PidLidWorkAddressState].
                Value.lpszW = 
                szBusinessState;
            spvProps[p_PidLidWorkAddressPostalCode].
                Value.lpszW = 
                szBusinessPostalCode;
            spvProps[p_PidLidWorkAddressCountry].
                Value.lpszW = 
                szBusinessCountry;
            spvProps[p_PidLidWorkAddressCountryCode].
                Value.lpszW = 
                szBusinessCountryCode;

            // Work address is mailing address.
            spvProps[p_PidLidPostalAddressId].Value.l = 2; 
            spvProps[p_PR_STREET_ADDRESS_W].Value.lpszW = 
                szBusinessStreet;
            spvProps[p_PR_LOCALITY_W].Value.lpszW = 
                szBusinessCity;
            spvProps[p_PR_STATE_OR_PROVINCE_W].Value.lpszW = 
                szBusinessState;
            spvProps[p_PR_POSTAL_CODE_W].Value.lpszW = 
                szBusinessPostalCode;
            spvProps[p_PR_COUNTRY_W].Value.lpszW = 
                szBusinessCountry;
            spvProps[p_PidLidAddressCountryCode].Value.lpszW = 
                szBusinessCountryCode;

            spvProps[p_PidLidContactItemData].
                Value.MVl.cValues = 6;
            LONG lContactItemData[6];
            // Display work address
            lContactItemData[0] = 0x00000002; 
            // Display Email1
            // (PidLidEmail1DisplayName)
            lContactItemData[1] = 0x00008080; 
            // Display Business Telephone 
            // (PR_BUSINESS_TELEPHONE_NUMBER)
            lContactItemData[2] = 0x3A08; 
            // Display Home Telephone 
            // (PR_HOME_TELEPHONE_NUMBER)
            lContactItemData[3] = 0x3A09; 
            // Display Business Fax 
            // (PR_BUSINESS_FAX_NUMBER)
            lContactItemData[4] = 0x3A24; 
            // Display Mobile Telephone 
            // (PR_MOBILE_TELEPHONE_NUMBER)
            lContactItemData[5] = 0x3A1C; 
            spvProps[p_PidLidContactItemData].
                Value.MVl.lpl = 
                lContactItemData;

            // These two must be the same.
            spvProps[p_PidLidHtml].Value.lpszW = szURL;
            spvProps[p_PR_BUSINESS_HOME_PAGE_W].
                Value.lpszW = szURL;

            spvProps[p_PR_DISPLAY_NAME_PREFIX_W].
                Value.lpszW = L"";
            spvProps[p_PR_SURNAME_W].
                Value.lpszW = szSurName;
            spvProps[p_PR_MIDDLE_NAME_W].
                Value.lpszW = szMiddleName;
            spvProps[p_PR_GIVEN_NAME_W].
                Value.lpszW = szGivenName;
            spvProps[p_PR_GENERATION_W].
                Value.lpszW = L"";
            spvProps[p_PR_INITIALS_W].
                Value.lpszW = szInitials;
            spvProps[p_PR_COMPANY_NAME_W].
                Value.lpszW = szCompany;
            spvProps[p_PR_BUSINESS_TELEPHONE_NUMBER_W].
                Value.lpszW =
                szBusinessPhone;
            spvProps[p_PR_DISPLAY_NAME_W].
                Value.lpszW = szFullName;
            spvProps[p_PR_MESSAGE_CLASS_W].
                Value.lpszW = L"IPM.Contact";
            spvProps[p_PR_ICON_INDEX].
                Value.l = 512; // Contact icon
            spvProps[p_PR_SUBJECT_PREFIX_W].
                Value.lpszW = L"";
            spvProps[p_PR_SUBJECT_W].
                Value.lpszW = szFullName;
            spvProps[p_PR_BODY_W].
                Value.lpszW = szNotes;

            // Call BuildOneOff to create
            // one-off SMTP address.
            hRes = BuildOneOff(
                szEmailDisplayName, 
                szEmailAddressType, 
                szEmailAddress,
                &spvProps[p_PidLidEmail1OriginalEntryID].
                Value.bin.cb,
                &spvProps[p_PidLidEmail1OriginalEntryID].
                Value.bin.lpb);
            if (SUCCEEDED(hRes))
            {
                // Call SetProps on Message.
                hRes = lpMessage->
                    SetProps(NUM_PROPS, spvProps, NULL);
                if (SUCCEEDED(hRes))
                {
                    // If SetProps succeeds,
                    // call SaveChanges.
                    hRes = lpMessage->
                        SaveChanges(FORCE_SAVE);
                }
            }
            if (spvProps[p_PidLidEmail1OriginalEntryID].
                Value.bin.lpb)
                delete[]    spvProps[p_PidLidEmail1OriginalEntryID].
                Value.bin.lpb;
        }
        // Release memory.
        MAPIFreeBuffer(lpNamedPropTags);
    }
    return hRes;
}

Conclusion

Using the Exchange Server Protocols documentation and MAPI code, you can create first-class Outlook items without relying on the Outlook object model. As you can see from the previous code example, there is a great deal of property-level code that you must write to map named properties to property identifiers. Some property values must be written in a certain sequence for the item to be constructed correctly. Although certain scenarios make a compelling case for MAPI, the Outlook object model encapsulates the business logic of the Outlook items and reduces the amount of code that you have to write to do the job. To confirm this point, take a look at the following listing that uses the Outlook object and Microsoft Visual C# to create the sample contact discussed in this article.

private void AddContact()
{
    try
    {
        Outlook.ContactItem oContact =
            Application.CreateItem(
            Outlook.OlItemType.olContactItem)
            as Outlook.ContactItem;
        oContact.FirstName = "Jacqueline";
        oContact.LastName = "Haddad";
        oContact.Initials = "J.H.";
        oContact.CompanyName = "Microsoft";
        oContact.Email1Address = "someone@example.com";
        oContact.Email1AddressType = "SMTP";
        oContact.Email1DisplayName =
            "Jacqueline Haddad (someone@example.com)";
        oContact.BusinessAddressStreet = "1 Microsoft Way";
        oContact.BusinessAddressCity = "Redmond";
        oContact.BusinessAddressState = "WA";
        oContact.BusinessAddressPostalCode = "95802";
        oContact.BusinessAddressCountry = "USA";
        oContact.BusinessTelephoneNumber = "800-555-1212";
        oContact.WebPage = "http://www.codeplex.com/mfcmapi";
        oContact.Body = "This is a sample note.";
        oContact.Save();
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex.Message);
    }
}

The choice between using the Outlook object model or MAPI should be dictated by your specific scenario. For most cases, use of the Outlook object model is preferred. If you are writing managed code, using the object model and primary interop assemblies is mandatory. If you have a special situation in which having your code run on the Outlook UI thread would cause issues for the user, the object model's interface does not give you access to, or control of, the information you need, or you need a higher performance interface to the data and are willing to handle the added complexities of coding to the MAPI interfaces, you should consider using the Exchange Server Protocols documentation in conjunction with MAPI to address your solution requirements.

Additional Resources

For more information, see the following resources: