Share via


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]

Comments

  • Anonymous
    August 04, 2008
    ---snip--- MIME doesn't have a native way to encapsulate RTF data, so we use TNEF to encapsulate it ---snap--- Sorry, WHAT?! Just use text/rtf or a multipart/mixed containing the text/rtf. Sigh ...

  • Anonymous
    August 04, 2008
    Zool - Maybe text/rtf could be used, but I couldn't even find a standard covering it. However, that's beside the point - Outlook and Exchange use TNEF to encode MAPI properties that they don't convert directly to MIME headers, and that includes PR_RTF_COMPRESSED.

  • Anonymous
    October 10, 2008
    Is there any way to get at the flags when using CDO 1.21?  I can't find a way to eliminate TNEF when the CDO-created message has an attached file.  Any suggestions?

  • Anonymous
    October 11, 2008
    The comment has been removed

  • Anonymous
    October 11, 2008
    O.K. I found a solution.  I experimented with Simple MAPI VBA code and found that if you set the recipient's Name property to address@company.com and use MAPIResolveName, the Address property is set to SMTP:address@company.com and there is no TNEF block in an email with an attachment.  But if you set the Name property to a display name and set Address to SMTP:address@company.com and don't use MAPIResolveName you will get the TNEF and winmail.dat. There is a similar solution in CDO.  This excerpt gets a TNEF block: Set objRecipient = objMessage.Recipients.Add objRecipient.Name = "John Doe" objRecipient.Address = "SMTP:address@company.com" objRecipient.Resolve False But this CDO code does not: Set objRecipient = objMessage.Recipients.Add objRecipient.Name = "address@company.com" objRecipient.Resolve True I lose the display name in the outgoing email but no longer have a TNEF block.  So, using MAPIResolveName and its equivalent in CDO is the solution.

  • Anonymous
    October 13, 2008
    Have you tried encapsulating your address in [square brackets?] I seem to remember for several microsoft products that this forces resolution to a different method. I know that in exchange i have to wrap X500 address this way [/o=org/ou=site/cn=recipients/cn=user], otherwise resolution will fail. Perhaps try some combination like this: [address@domain.com]  - no smtp moniker [SMTP:address@domain.com] SMTP:[address@domain.com] Let us know your results. -Eriq

  • Anonymous
    October 13, 2008
    The brackets did not help but I can get the display name back by setting it after calling CDO's Resolve method: Set objRecipient = objMessage.Recipients.Add objRecipient.Name = "address@company.com" objRecipient.Resolve True objRecipient.Name = "My Name <address@company.com>" That works and I've eliminated the TNEF block.