Forcing Plain Text With MAPI

We had a customer recently who was sending mails with Outlook's MAPI. They wanted to know how to force the message to be plain text, like it is in Outlook when you select Send Plain Text only in the properties for the recipient:

PlainText

They were using CreateOneOff to create their recipient, and they were passing MAPI_SEND_NO_RICH_INFO. Yet, if the message they were sending contained an attachment, such as a Word document, the content type of the attachment on the received mail was always application/ms-tnef instead of application/msword. In other words, they were still getting a winmail.dat attachment. If they addressed a mail in Outlook with the Send Plain Text only option, sending the same attachment, it came across without the TNEF.

One thing to note right off the bat is that sending rich text (RTF) data and sending TNEF (winmail.dat) are actually two different concepts. RTF data describes the formatting of the text of the message, much in the same way HTML can also describe the formatting of text. TNEF is a mechanism for encapsulating MAPI properties that Exchange and Outlook do not convert natively into MIME format. One such MAPI property commonly encapsulated in TNEF is PR_RTF_COMPRESSED. But TNEF can encapsulate many more properties than just PR_RTF_COMPRESSED, such as properties dealing with certain attachments. So the presence of winmail.dat doesn't necessarily mean rich text is being transmitted. That's what's happening in this case.

If you're trying to avoid TNEF completely, MAPI_SEND_NO_RICH_INFO is a step in the right direction, but doesn't actually tell the content converter not to use TNEF - it just tells it not to use RTF. To avoid TNEF altogether, we need to set some more flags. But which flags? We can look at what CreateOneOff generates to find out. Fortunately, we don't need to reverse engineer the structure - it was recently documented in section 2.2.5.1 of the following Exchange Protocol Documentation: [MS-OXCDATA].pdf

Now - the documentation's in "bytes on the wire - what's a constant?" format, but with a little elbow grease, we can produce the following structure:

 { 
 DWORD dwFlags;
 BYTE ProviderUID[16]; 
 DWORD dwBitmask;
} ONEOFFEIDHEADER, FAR *LPONEOFFEIDHEADER;

which we can map our entry ID to so we can check which flags are set. The following flags are valid (determined by matching the docs up to MAPI's headers):

 #define MAPI_SEND_NO_RICH_INFO          ((ULONG) 0x00010000)
#define ENCODING_PREFERENCE             ((ULONG) 0x00020000)
#define ENCODING_MIME                   ((ULONG) 0x00040000)
#define BODY_ENCODING_HTML              ((ULONG) 0x00080000)
#define BODY_ENCODING_TEXT_AND_HTML     ((ULONG) 0x00100000)
#define MAC_ATTACH_ENCODING_UUENCODE    ((ULONG) 0x00200000)
#define MAC_ATTACH_ENCODING_APPLESINGLE ((ULONG) 0x00400000)
#define MAC_ATTACH_ENCODING_APPLEDOUBLE ((ULONG) 0x00600000)
#define OOP_DONT_LOOKUP                 ((ULONG) 0x10000000)

With this, we can see that setting the Plain Text Only option in Outlook translates to setting the ENCODING_PREFERENCE and ENCODING_MIME flags. Unfortunately, the CreateOneOff function doesn't let you pass those flags. If you try, you'll get MAPI_E_UNKNOWN_FLAGS. We won't let that stop us though - we can use our structure to set the flags as well as read them!

Here's how we can put this all together and get a one-off entry ID which will send without TNEF:

 { 
    DWORD dwFlags; 
    BYTE ProviderUID[16]; 
    DWORD dwBitmask; 
} ONEOFFEIDHEADER, FAR *LPONEOFFEIDHEADER; 

hr = lpAddrBook->CreateOneOff( 
    lpszName, 
    lpszAdrType, 
    lpszAddress, 
    MAPI_SEND_NO_RICH_INFO, 
    &cbEntryID, 
    &lpEntryID); 
if (HR_SUCCEEDED(hr)) 
{ 
    LPONEOFFEIDHEADER lp1Off = (LPONEOFFEIDHEADER) (ENTRYID*) lpEntryID; 
    if ((lp1Off->dwBitmask & MAPI_SEND_NO_RICH_INFO)) 
    { 
        lp1Off->dwBitmask |= (ENCODING_PREFERENCE | ENCODING_MIME); 
    } 
}

[Update: 8/4/2008 5:45 - rewrote the RTF/TNEF paragraph to make it clearer]
[Update: 8/4/2008 9:36 - realized I hadn't proofread the code - fixed it]