Archive-name: sending-mail-from-code-with-microsoft
Last-modified: 2000/jan/05
Version: 1.0.8

FAQ: Sending Mail From Code with Microsoft Systems

Author: Daniel J. Mitchell djmitchella@yahoo.com
Date: 2000/jan/05

This document attempts to cover some of the more frequently asked questions about sending mail using the Microsoft mail APIs.


Table of Contents:

1. Welcome
1.1 Disclaimer
1.2 Copyright
1.3 What's New?
1.4 Where can I find this FAQ?
1.5 What's with all the KB article numbers?
2. Introduction.
2.1 Which ways are there to send mail from my application?
2.1.1 Simple MAPI.
2.1.2 CMC.
2.1.3 MAPI ActiveX control.
2.1.4 Extended MAPI.
2.1.5 CDO. (Active Messaging/OLE Messaging).
2.1.6 Outlook Automation.
2.2 Which should I use?
3. Questions
4. General version issues.
4.0 What exactly is MAPI, anyway?
4.1 Can I use MAPI/CDO with Outlook Express?
4.2 How do I install Windows Messaging?
4.3 Where can I get CDO?
4.4 How do I use CDO from C++?
4.5 How do I use CDO from Java?
4.6 Which mail programs support CDO?
4.7 How do I get the CDO HTML rendering library to run? I've installed Outlook, but still no joy.
5. Outlook.
5.1 How do I use Outlook from my C++ code?
5.2 Can I run two Outlooks at the same time pointing at different mailboxes?
5.3 When my Outlook code is finished, Outlook doesn't unload. Why?
5.4 How can I send mail containing RTF from Outlook?
5.5 Can I drive Outlook Express from code?
5.6 What's going on with my default mail system? How do I make it Outlook/stop it being Outlook/fix weird reply behavior?
6. Mail format.
6.1 How can I send mail containing RTF from CDO?
6.2 How can I send mail containing HTML?
7. Profiles and identity.
7.1 How can I send mail without the "Choose Profile" dialog appearing?
7.2 How do I send mail without knowing the name of a profile?
7.3 How do I find the name of the default profile?
7.4 What do I do if there is no default profile?
7.5 How do I build a profile?
7.6 Why does my MAPI application fail when I try and run it as an NT service?
7.7 How can I find the mailbox for a particular NT account? (and vice versa).
7.8 How can I loop over all the mailboxes on an Exchange Server and process them?
7.9 How do I send mail with a different ReplyTo value than the sender?
7.10 How do I find the SMTP address for someone? I keep getting the X.400 address for them.
8. Missing features/bugs.
8.1 How can I send a task request from CDO/MAPI?
8.2 How can I read calendar information from MAPI?
8.3 Okay, how do I find the calendar folder at all from MAPI?
8.4 How can I read someone else's calendar? I have permissions, but I can't read the messages as AppointmentItems.
8.5 How do I find all appointments/meetings between given dates?
8.6 Why does my code leak memory?
8.7 How do I access custom properties in CDO? I get MAPI_E_NOT_FOUND when I try and read them.
8.8 Why do my custom properties disappear from recurring meetings?
8.9 Why does my sent mail stay in the Outbox?
8.10 When I read an attachment with the VB MAPI controls, I can only get the 8.3 version of the filename?
8.11 Why do I get 2147483647 messages when I try and count the messages in a folder?
8.12 Why does my advise sink miss the first event that passes it?
8.13 Why can't I read calendar entries from _my_ calendar folder?
8.14 Why do I get MAPI_E_NOT_FOUND trying to read/write properties on messages?
8.15 Why is it so complicated to do simple things in MAPI? Isn't there an easier way?
8.16 I found the ID for a property on messages -- but now I'm on a different server, the ID's changed. What gives?
8.17 My MAPI application fails when I try and run it from an MTA thread and/or an ATL (D)COM server.
8.18 What's with the HRESULTS I get in my C++ CDO errors? They don't make any sense?
8.19 Why can't I open the messages I get in notifications to my Outlook extension?
8.20 Why do I get MAPI_E_NO_SUPPORT trying to open a calendar folder?
8.21 What happens when I try mixing multi-threaded code and MAPI?
8.22 Why do I get CdoE_NO_SUPPORT when trying to log in?
8.23 Why don't I get calendar items in my event script?

A. Other sources of information.
B. Credits.
C. Delphi extra bits.


1. Welcome

This document has been written to attempt to answer some of the more frequently asked questions about sending mail using Microsoft's APIs and programming tools. Many questions come up time and time again on the assorted newsgroups dedicated to this, and hopefully this document should save people needing to ask the same questions, and provide a standard answer to them.

Note that it does _not_ cover questions about Exchange or Outlook other than as much as is needed to cover mail-enabling applications. If you are having problems with your Out Of Office Wizard, for instance, this is not the FAQ you want. (I don't think such a FAQ exists yet, in fact, though I may be wrong.)

It is also not meant to be a replacement for the online documentation for the mail APIs, or a beginners' guide. It does answer some common basic questions, but if you want sample code, then go to the documentation.

1.1 Disclaimer

Disclaimer: ``This FAQ is presented with no warranties or guarantees of ANY KIND including correctness or fitness for any particular purpose. The author of this document has attempted to verify correctness of the data contained herein; however, slip-ups can and do happen. If you use this data, you do so at your own risk.''

Disclaimer 2: ``This FAQ is in no way authorised or endorsed by Microsoft, and should not be taken to speak for or on behalf of Microsoft. All references to Microsoft products acknowledge the trademarks owned by Microsoft.''

1.2 Copyright

Copyright 1999,2000 by Daniel J. Mitchell. All rights reserved worldwide. Permission is granted to copy this document for free distribution so long as it remains intact and unmodified. For other arrangements, contact the author/maintainer via email: djmitchella@yahoo.com

1.3 What's New?

1.4 Where can I find this FAQ?

The current version of this FAQ is posted every two weeks or so; it is also mirrored on the WWW at http://www.geocities.com/djmitchella/mailfaq.txt or http://go.to/mailfaq if you want a shorter URL but more advertising junk.

1.5 What's with all the KB article numbers?

A lot of these answers simply refer you to KB articles; sometimes I've put in a direct URL, sometimes I just give the number. In the latter case, go to http://support.microsoft.com/support/search/c.asp?FR=0 and do "search by article number" to find the article in question. At some stage I'll convert them all into links, probably at the same time as I make a real HTML version of this. Apologies for any inconvenience.

Alternatively, substituting in the obvious way in this link: http://support.microsoft.com/support/kb/articles/q123/4/56.asp will work with the current organisation of MS's website.

At some point I'm intending to put in the keywords you could use to find these KB articles yourself; there's a knack to finding things in the KB, but a lot of these questions are answered in there -- hopefully giving guidelines to how to find things should mean that more questions can be solved without needing outside assistance. That's for later..


2. Introduction.

Microsoft provides a large number of possible ways to send mail from your application. Some of these are easier to use than others; some provide more features; some provide a more low-level and powerful interface. Depending on what you want to do with your application, the methods you use may vary.

2.1 Which ways are there to send mail from my application?

Note that this only covers Microsoft products. There are a number of third-party mail libraries, which are not covered here. Note also that not all of the methods listed below are available from all programming languages. Note also that it may be possible to shoehorn some APIs into some languages given a lot of knowledge of the language in question. Oh, and this only covers Visual Basic, C, and C++ as possible languages -- I don't have any experience with other languages. Also, when I say "C++" it generally means that they are a set of COM objects -- you can use these from C, it's just (a lot) more work.

Delphi information below courtesy of Paul Qualls.

This isn't intended to document the APIs in question, just to list them -- use the MSDN documentation for more. Where possible, I've provided the location in the MSDN helpfiles, because some of this stuff can be tricky to find. You can probably find it more rapidly by going to the index and searching for the last entry in the hierarchy given.

One other thing to note is that this doesn't cover sending mail using direct SMTP -- that's another thing entirely.

2.1.1 Simple MAPI.

Very limited featureset -- send mail, read messages from inbox only, pop up standard addressbook dialog. Usable from C/C++ from mapi.h, usable from VB by declaring the functions manually. Usable from Delphi by including MAPI in the Uses Clause. Documented: MSDN under Platform SDK|Messaging and Collaboration|Guide|Programming with Simple MAPI.

2.1.2 CMC.

Similar functionality to Simple MAPI; this is just another interface created (I assume) to match up with some standard or another.

2.1.3 MAPI ActiveX control.

Visual version of simple MAPI for VB: use by project|components|Microsoft MAPI control. Not the same interface, but roughly the same level of functionality. Documentation: Visual Studio 6.0 Documentation|Visual Basic Documentation|Using Visual Basic|Component Tools Guide|Using the ActiveX controls|Using the MAPI Control. Also usable from C++ by inserting the control as with any ActiveX control. Usable from any Delphi form similarly.

2.1.4 Extended MAPI.

C++ only, as far as I know. This is the lowest level interface available, and is the interface that everything else eventually uses. As such, you have the ability to do anything that you can do with any other API -- but that makes some assumptions about documentation of message formats that aren't necessarily true.

Not particularly easy to use -- once you get the hang of it, it's a reasonably consistent set of interfaces. Until you do get the hang of it, though, it's a steep learning curve. Provides features that you can't get at with any of the other APIs, so you may have to use this.

Also usable in Delphi: you need translated header files available at http://sunsite.icm.edu.pl/delphi/ftp/d30free/mapid10.zip to be able to reference the interfaces, though. (I don't use Delphi, so I don't know how accurate these files are; I've had reports that there are problems with them; to be precise, any constants in the original header files ending in B (for instance 0x123409AB) have the final digit removed in the corresponding .pas file (so this would become 0x123409A). This is presumably fixable manually, I guess. Many thanks to Ivan Borissov Kojuharov for this information.)

2.1.5 CDO. (Active Messaging/OLE Messaging).

This is a more powerful set of APIs that lives on top of MAPI. Easier to use than MAPI, and provides a broader set of objects to wrap up some of the undocumented formats of MAPI. Usable from VB, C++, Delphi (with CreateComObject('Mapi.Session') ), etc. Lacks a lot of the low-level features of MAPI, and has some obscure holes in the implementation. Still much much easier to use than Extended MAPI, though. Current version is 1.2.1 -- v1.1 was called "Active Messaging", v1.0 was called "OLE messaging". Documented in Platform SDK|Messaging and Collaboration|CDO.

Here's a table of which versions of CDO do what, and where you can get them (thanks to Siegfried Weber for this list).

  • CDO 1.x for MAPI enabled applications (CDO.DLL)
    Comes with Outlook 9x/2K, Exchange server + client & OWA No native support for MHTML, SMTP, NNTP, POP3, IMAP4, LDAP etc.

  • CDO 1.2 for Windows NT Server (CDONTS.DLL)
    Comes with Windows NT 4 server option pack, IIS SMTP service only. Only support for SMTP and MHTML, no MAPI or OLEDB support - runs ONLY on NT server with IIS4 and SMTP service installed

  • CDO for Windows 2000 AKA CDO 2.0 (CDOSYS.DLL)
    Comes with Win2K Prof. Server, Adv. Server Not MAPI support at all, only SMTP and NNTP

  • CDO for Exchange 2000 - AKA CDO 3.0 (CDOEX.DLL)
    Comes with Exch2K only. No client release as of yet No MAPI support at all, only OLEDB to access Exch2K Webstores etc. While Win2K comes with a CDONTS.DLL it is only provided for backwards compatibility. Same applies for Exch2K which still delivers CDO.DLL and CDOHTML.DLL (and a working MAPI subsystem) to support the "old" MAPI based CDO stuff.

2.1.6 Outlook Automation.

Use Outlook itself through automation. This has a different featureset to CDO; it fills in some gaps, and opens up others. Easy to use from VB, more painful to use from C++ because of strangenesses in the type library. Usable from Delphi with early/late binding; samples in appendix C. Documented in a difficult-to-find help file reputedly somewhere on one of the Outlook install CDs -- look in vbaoutl.hlp in your Office directory for more on this.

2.2 Which should I use?

As with most questions of this nature, the best way to answer it is to work out what you need to do; some features are only present in one of the APIs above. If your particular requirement is not listed below, then a more general set of recommendations follows.

If you need to send task requests, you have to use Outlook. (see also 8.1).

If you need to send RTF messages, you have to use MAPI or CDO. (see also 5.4/6.1).

If you need to process calendar entries, you have to use CDO or Outlook. (see also 8.2).

If you need to log into a user that doesn't have a profile created on the system, you have to use MAPI or CDO. (see also 7.1 et al)

If you don't have one of those specific requirements, you can use any of the APIs. If all you want to do is pop up the standard "File|Send Mail" dialog, or just want to send plaintext mail messages, Simple MAPI will do what you want just fine. If you want to do something more complex, CDO is probably your best bet.


3. Questions

These aren't meant to cover all possible questions; they're just a list of questions that have come up frequently in the past.

If your question is not answered below, please try and find the answer yourself before asking. Every single one of the URLs below was found by simply searching the resources mentioned at the end of this FAQ for keywords found in the question.. Asking questions that are trivially answered by searching the KB for (for instance) "NT service" does not give a good first impression.


4. General version issues.

4.0 What exactly is MAPI, anyway?

MAPI is the Messaging API -- it's the underlying system that all the other mail APIs use one way or another. This is the code that lives in mapi32.dll. All the other APIs wrap MAPI in some way or another.

4.1 Can I use MAPI/CDO with Outlook Express?

(Windows 98 installs Outlook Express by default). No, you can't -- you have to install Windows Messaging. See Q3.2. Note that if you install Outlook Express, it will overwrite your MAPI settings and you may have to reinstall Windows Messaging. Alternatively, if you already had MAPI installed, search for the file mapi32.oe, and rename that back to mapi32.dll; that should work. (but might have weird consequences for Outlook Express, so test this first on your system). Theoretically, Outlook Express supports Simple MAPI, but in practise this tends not to work.

4.2 How do I install Windows Messaging?

If you have Windows 95, they should be installable from the Add/Remove programs dialog. If you have Windows 98, you can either run the file wms.exe from the Windows 98 CD, or you can try ftp://ftp.microsoft.com/softlib/MSLFILES/exupdusa.exe which should also work. Also see http://www.slipstick.com/exchange/win98-wminstall.htm for more info.

4.3 Where can I get CDO?

Download it from http://microsoft.com/exchange/55/downloads/CDO.htm.

4.4 How do I use CDO from C++?

Basically, put

  #import 
  using namespace MAPI;
at the top of your code, make sure you call CoInitialize(NULL), and go from there -- the object model's the same as in VB, but you're using -> instead of . to access methods/properties.

For samples, see KB articles 171098, 171429, 173550, 195545, and 178480. Also see 179233 for a glitch to be aware of.

4.5 How do I use CDO from Java?

(I've not tried this myself, but I came across this article while poking around): KB 216723 has fairly detailed instructions on doing this with Visual J++. If you're using a non-Microsoft Java, you may have more difficulty getting at the type library information -- your particular environment may have extra help. I know nothing about this myself, sadly).

4.6 Which mail systems support CDO?

Currently, the list of mail programs that definitely support CDO are (these all install the Extended MAPI system, in other words): Exchange 5.0 Client, Outlook 97, Outlook 98 (only in corporate/workgroup mode), Outlook 2000 (only in corporate/workgroup mode).

Exchange 4.0 client may work, but there are a lot of bugfixes not in the version of MAPI that comes with that.

4.7 How do I get the CDO HTML rendering library to run? I've installed Outlook, but still no joy.

You can only use the CDO rendering library on a machine with Outlook Web Access installed.


5. Outlook.

5.1 How do I use Outlook from my C++ code?

Firstly, see KB 199870.

If you're using Outlook 97, and just try "#import msoutl8.tlb", you'll get an enormous number of errors -- something is wrong with either #import, or the type library information. To workaround this, use the following code: (thanks to James B. at microsoft for this).

(you'll need to change the #define OUTL8 and paths to the DLLs appropriately for your system or it probably won't work too well).

	// For using the Outloook Object Model
	#define IMPPROPS rename_namespace("OL")
 
	// Define this according to the OL version you are compiling under 
	#define OUTL8 // #define OUTL8 or OUTL85 or OUTL9
	#if defined(OUTL9) // Outlook 2000
	#import "C:\Program Files\Microsoft Office\Office\mso9.dll" IMPPROPS
	#import "C:\Program Files\Microsoft Office\Office\msoutl9.olb" IMPPROPS
	#elif defined(OUTL85) // Outlook 98
	#import "C:\Program Files\Microsoft Office\Office\mso97.dll" IMPPROPS
	#import "C:\Program Files\Microsoft Office\Office\msoutl85.olb" IMPPROPS
	#elif defined(OUTL8) // Outlook 97
	#import "stdole2.tlb" IMPPROPS include("IPicture") \
	 exclude("Font", "IUnknown", "IDispatch", "IEnumVARIANT", "IFont") 
	#import "fm20.dll" IMPPROPS include("IFont") \
	  exclude("OLE_COLOR", "OLE_HANDLE") rename("Pages", "FMPages") 
	#pragma warning(disable: 4146)
	#import "D:\Program Files\Microsoft Office\Office\mso97.dll" IMPPROPS \
	rename("DocumentProperties", "DocProps") 
	#pragma warning(default: 4146)
	#import "D:\Program Files\Microsoft Office\Office\msoutl8.olb" IMPPROPS \
	 rename("_IMailItem", "_MailItem")
	#endif
5.2 Can I run two Outlooks at the same time pointing at different mailboxes?

It doesn't seem to be possible to do this -- even setting the NewSession parameter to Outlook to be "true" doesn't get around this. The second Outlook always hooks up to the first running session.

5.3 When my Outlook code is finished, Outlook doesn't unload. Why?

This only seems to happen from C++ -- sometimes you'll get reference leaks, smart pointers and all. It's not clear exactly where the reference leaks come in, but you can usually get rid of them by putting an extra manual call to Release() on the underlying object. Generally, the best way to find where to do this is the usual one of cutting out code until Outlook unloads, then put it back in until you find the source of the leak.

5.4 How can I send mail containing RTF from Outlook?

This seems to be impossible, despite the fact you can do it from "real" Outlook. The only solution anyone has come up with thus far is to configure Outlook to use Word as it's email composer, and automate the instance of Word that appears somehow. I haven't tried this.

5.5 Can I drive Outlook Express from code?

No, it doesn't expose any official interfaces for you to use. See KB 216281 for more on this.

5.6 What's going on with my default mail system? How do I make it Outlook/stop it being Outlook/fix weird reply behavior?

See KB Q181056, Q180429, & Q175862 (more Outlook-related, but they still relate to setting MAPI as your default mail system in case it's not that any more).


6. Mail format.

6.1 How can I send mail containing RTF from CDO?

Use the DLL provided by Microsoft at KB Q172038 .

6.2 How can I send mail containing HTML?

In the same way as you would send RTF -- use the DLL above, or use the Extended MAPI RTF interface, but with HTML text instead of RTF text. (I haven't tested this myself, because we use Outlook 97 here). You'll have to put "\fromhtml1" and a newline in at the start of your HTML text, as well. See KB article 216344 for more on this.


7. Profiles and identity.

7.1 How can I send mail without the "Choose Profile" dialog appearing?

This varies with the API you're using, but in general, look up the documentation for the relevant "Logon" function, and pass in the name of a mail profile, and set any showDialog flags to false.

7.2 How do I send mail without knowing the name of a profile?

You have to tell the system to use the default profile. This is possible using Extended MAPI -- set the MAPI_DEFAULT_PROFILE flag in the flags argument to MapiLogonEx. It's not directly possible using any other API; you can sneak this into CDO if you're using C++, using code like:

	IMAPISession * lpSession;
	MapiLogonEx(...); // log into default profile
	SessionPtr pCDOSession;
	pCDOSession->MAPIOBJECT = lpSession;
which will give you a CDO session pointing at the MAPI session.

7.3 How do I find the name of the default profile?

Using Extended MAPI, look up the IProfAdmin interface. Using anything else, you're pretty much out of luck. The information _is_ in the registry, but this isn't documented so you may not want to trust this: Profile key is DefaultProfile, directory is:

  • Win95/98: HKCU\Software\Microsoft\Windows Messaging Subsystem\Profiles
  • WinNT: HKCU\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles
Also see KB 171422 for a VB implementation of the last.

7.4 What do I do if there is no default profile?

This can be the case -- just because someone only has one profile set up, that's no guarantee that it's the only one. In this case, you have to use IProfAdmin and set that to be the default. If they have multiple profiles and no default, there's no obviously correct solution other than popping up UI and making them choose. Alternatively, you can use dynamic profiles. (see 7.5)

7.5 How do I build a profile?

If you want to build a permanent profile, you have to use the IProfAdmin interfaces in Extended MAPI. There are a number of external programs that will do this -- newprof.exe which comes with Outlook 97 is the Microsoft version of this. See KB 145905 for more on this. There's also profgen, available at ftp://ftp.microsoft.com/bussys/exchange/exchange-unsup-ed/

If you want a profile to be created based on the mailbox name and server location, you can use CDO; see the documentation for the ProfileInfo parameter to the Logon method of a Session object.

If you want to log into your Exchange Server anonymously, then assuming the server is set up to allow that, see KB 195662 for how to do this, and more information on the ProfileInfo parameter.

See KB 173550 for information on doing this from C++.

If you need to do this with Extended MAPI, see KB 170225, and the entry titled "Creating and Configuring a Profile" in MSDN.

Other MSDN entries worth looking at are titled "Creating a profile with custom code", and "Creating a profile through MAPI".

7.6 Why does my MAPI application fail when I try and run it as an NT service?

Because profiles only exist for the user that your code is running as, and services tend to run as the system account. See KB 177851 for how to build a VB NT service that'll send mail, KB 185139 for explanation of getting MAPI_E_INVALID_PARAMETER from logging on inside a service with very old MAPI dlls, and KB 197820 for a nice overview of using MAPI from services.

7.7 How can I find the mailbox for a particular NT account (and vice versa)?

This isn't as simple as it might seem -- the problem is that the name of the mailbox isn't stored directly in the GAL. However, code along the following lines will do what you want (yes, it's not a pretty solution, but it's the only way..)

  1. Open the GAL.
  2. For each entry in the GAL:
  3. Open the PR_EMS_AB_ASSOC_NT_ACCOUNT (or CdoPR_EMS_AB_ASSOC_NT_ACCOUNT) field.
  4. The value of this is a binary SID, Security Identifier. You can then translate this to an NT account and domain pairing using the NT-only function LookupAccountName. You may need some VB/Delphi/whatever magic to call this function in those languages; whatever you usually do to call Win32 functions will work again here. (I don't know what this is, I should point out).
  5. You now have a name and domain. If this name and domain matches the name you're looking for, add the mailbox name to a list.
  6. Keep looping until you're done with the GAL.
  7. You now have a list of mailboxes. This list can contain:
    • 0 entries. No mailbox is owned by this account.
    • 1 entry. Done!
    • > 1 entries. This account owns more than one mailbox -- now you'll need to find some way of resolving which one is the correct one. (example: the system admin probably owns both postmaster@company.com and JoeAdmin@company.com -- there's no guaranteed way to tell which is correct).

That'll convert from domain\user to mailbox. One obvious optimisation is that you can call LookupAccountSID on the user, and then just compare SIDs rather than doing a lookup for each entry. To go the other way around is a bit easier, because a mailbox only has one owner, so you just have to open the GAL, open the relevant entry, get the SID, and call LookupAccountSID on that entry.

Things to be aware of: if you do a LookupAccountName("userfoo"), and there's a _local_ user "userfoo" on the machine you do the lookup on, you'll get the SID for the _local_ user. This will almost certainly not be the SID that's in the GAL, because the GAL contains users logged into domains -- make sure you do a lookup on "domainfoo\userfoo". This still isn't perfect; sometimes one user logs in from multiple domains and domainone\user and domaintwo\user both have access to J.User's mailbox but it's owned by domainone\user -- in this case you won't get a match, so beware of this possible gotcha.

See KB 244670 for VB code to do part of this lot.

7.8 How can I loop over all the mailboxes on an Exchange Server and process them?

Updated: this now lives at Q200160 See KB 198752 (loops finding mailbox size for all mailboxes; MAPI/C++) KB 194627 (just loops opening all mailboxes; MAPI/C++) and KB 203019 (loops over mailboxes using CDO)

7.9 How do I send mail with a different ReplyTo value than the sender?

See KB 181408 and 195842 for methods to do this from VB and C++ respectively.

7.10 How do I find the SMTP address for someone? I keep getting the X.400 address for them.

Two ways: firstly, open the PR_EMS_AB_PROXY_ADDRESSES property on a recipient and loop over them searching for the entry beginning with "SMTP:". This lets you get at all possible addresses for someone, which may be useful.

Secondly, use (thanks to Siegfried Weber for this code):

	Public Const CdoPR_EMAIL = &H39FE001E
	Set objAddressEntry = objMessage.Sender
	strAddressEntryID = objAddressEntry.ID
	strEMailAddress = _
		objSession.GetAddressEntry(strAddressEntryID).Fields(CdoPR_EMAIL)

8. Missing features/bugs.

Note that this is just a list of the most commonly found problems -- it is not by any means a complete list of bugs/misfeatures. That can be approximated by searching the KB for "BUG:", but even that still doesn't list all the strangenesses that will occur from time to time.

8.1 How can I send a task request from CDO/MAPI?

You can't. The format of a task request is undocumented, and the last word from anyone at Microsoft was that the Outlook development team were not willing to give out any documentation. You _can_ send a plain task object -- set the message's Type to "IPM.Task" and it'll arrive as a task but without the accept/deny buttons. Task requests seem to arrive as two separate messages (one in the inbox, one in the tasks folder) linked in some manner; trying to simply set the type to "IPM.TaskRequest" results in Outlook crashing when it opens the messages. CDOLive has a partial list of the other properties that affect tasks. (see appendix)

You can send task requests from Outlook, however.

8.2 How can I read calendar information from MAPI?

This is also undocumented -- use CDO if at all possible. Alternatively, CDOLive has some documentation on the format of a calendar message. (see appendix).

8.3 Okay, how do I find the calendar folder at all from MAPI?

See KB 171670

8.4 How can I read someone else's calendar? I have permissions, but I can't read the messages as AppointmentItems. What about calendar entries in public folders?

(solution at end of this lot)

The documentation for AppointmentItems states that you can only read messages as AppointmentItems from a folder that you have got by using Folder f = session.GetDefaultFolder(CdoDefaultFolderCalendar). This is a restriction of CDO imposed for some unknown reason, and basically means you can only open calendar items for the primary user of the session.

If you can read someone else's mailbox, you can navigate to the calendar by looking for "Calendar" as the folder title or a folder with PR_CONTAINER_CLASS = "IPF.Appointment". Once there, you still need to somehow decode the calendar items -- using the documentation from CDOLive (see appendix) you can get at a lot of the information; recurrences aren't documented there, but it is possible to reverse-engineer the information. See http://www.deja.com/[ST_rn=ps]/getdoc.xp?AN=422943739&fmt=text for code I wrote to do this for recurrences. Note that the properties I use in that sample probably won't work on your system; you'll have to do named property stuff to get the actual values. Apologies for the inconvenience; but then again you'd have to use named properties anyway, so this is just more practise at finding the names for a given thing.. Also see KB 196508 for the KB article about this.

This also applies to calendar entries in public folders, only more so -- because a public folder is never the default calendar folder for someone, you can never read calendar entries from this directly.

An ingenious workaround (Thanks to Siegfried Weber for this tip) is to read the info on the Exchange Server and mailbox for the person in question from the GAL, use that to log into a dynamic profile pointing at that user, and then, because you're logged into that person's mailbox directly, you can use GetDefaultFolder. This will work, but will hit problems with either memory leaks (if you log in and out, see Q8.6) or just having an awful lot of open sessions. That said, it's by far the easiest way to get this stuff working.

8.5 How do I find all appointments/meetings between given dates?

Here's a code fragment to do this:

  ' s is a session that's been logged in.
  Dim f As MAPI.Folder
  Set f = s.GetDefaultFolder(CdoDefaultFolderCalendar)
  Dim ms As MAPI.Messages
  Set ms = f.Messages
  Dim fi As MAPI.MessageFilter
  Set fi = ms.Filter
  With fi
    Dim fs As MAPI.Fields
    Set fs = fi.Fields
    With fs
      Dim f1 As MAPI.Field
      Set f1 = .Add(CdoPR_START_DATE, #11/6/1998#)
      Set f1 = .Add(CdoPR_END_DATE, #11/2/1998#)
    End With
 End With
 ' now loop over `ms' using getfirst/getnext/whatever.
The important thing to note here is that the start and end date are _reversed_ from what you might expect. This is a CDO glitch. See KB Q178508 for an MS sample that does this. Also see KB 192404 for more about why things are backwards.

8.6 Why does my code leak memory?

There's a leak in the login/out code in MAPI (and thus CDO): See KB 215463 (leak in login code), 171427 (leak in CDO1.1 (1.2?)) folder handling code), 188958 (general survey of ways your MAPI code may not be freeing things up).

If you're using CDO or Outlook by using #import, see KB 231872 -- there's a leak in the code #import generates.

Workarounds for this:

  1. Don't log in and out any more than you have to -- log in at the start of your program, and cache the session object so you stay logged in.
  2. Make sure your program finishes every so often; when it exits, the leaked memory goes away with it.
These aren't _good_ workarounds, but they're all there is..

8.7 How do I access custom properties in CDO? I get MAPI_E_NOT_FOUND when I try and read them.

There's a peculiarity in the way CDO decodes the propsetIDs; see KB 195656.

8.8 Why do my custom properties disappear from recurring meetings?

Because instances of recurring meetings aren't "real" messages -- CDO creates fake instances internally and stores them as attachments on the base meeting.

The properties don't actually disappear -- it's just that when you open one of these instances with CDO, CDO, for some peculiar reason, gives you a message that you can't get at these properties with.

Workaround as follows:

  1. Open instance of meeting.
  2. Get the Parent of the meeting's RecurrencePattern; this is the "base" message.
  3. Loop over the table of attachments in the base meeting searching for an attachment where property 0x7ffb0000 | PT_FILETIME contains the same time as the start of the meeting instance (no timezone issues to worry about here).
  4. Read the PR_ATTACHMENT_INDEX property from this row, and then do:
         hr = pMsgMAPI->OpenAttach(index, &IID_IMAPIProp, MAPI_BEST_ACCESS,
                                   (IAttach**)&pAttach1);                      
         LPMESSAGE pAttachMsg;
         hr = pAttach1->OpenProperty(PR_ATTACH_DATA_OBJ,&IID_IMessage,0,0,
                               (LPUNKNOWN*)&pAttachMsg);
    
    or use CDO, and code like:
         Set objAttachment = objMessage.Attachments.Items(1)
         Set objEmbeddedMsg = objAttachment.Source
    
  5. Use MAPI named property stuff to read your property from pAttachMsg.
You can do this with CDO, too; thanks to Siegfried Weber for the attachment.Source info. (I've not tried this myself, but it should be clear enough how to make this work).

See http://www.geocities.com/djmitchella/recurringmeetings1.txt for a large explanation of how I came up with this particular solution, and why other possible workarounds won't work. (there are a _lot_ of bugs that you hit along the way..)

8.9 Why does my sent mail stay in the Outbox?

Three possiblities: if the mail arrives, then you need to set the PR_DELETE_AFTER_SUBMIT flag (assuming you're using extended MAPI). If you're using simple MAPI, you have to set the ForceDownload flag to true when you log in. If you're using CDO, you may need to use the DeliverNow method on the Session object -- this forces delivery of all currently pending messages.

8.10 When I read an attachment with the VB MAPI controls, I can only get the 8.3 version of the filename?

This is a limitation on those controls; you will have to use CDO to avoid it. To read the attachment in the first place, you need to use the AttachmentPathName property of the message; the attachment is stored in a temporary directory when you open the message.

8.11 Why do I get 2147483647 messages when I try and count the messages in a folder?

Because the Count property can lie to you. If you look at the documentation, it says that it may return &HFFFFFFF as the count of messages if it decides to not count things properly, and that's the number above.

8.12 Why does my advise sink miss the first event that passes it?

Because you have to "wake up" advise sinks by doing some sort of RPC event (bug). Opening a new folder and doing GetContentsTable() on the folder will do this. Also, see KB 190413 for the MS explanation of this.

8.13 Why can't I read calendar entries from _my_ calendar folder?

See KB Q196258 (version clash with CDO 1.1 being installed mistakenly with Outlook) and 194806 (now vanished, annoyingly, but it might come back; talks about the message class being wrong on occasions).

8.14 Why do I get MAPI_E_NOT_FOUND trying to read/write properties on messages?

See KB 179639 (survey of possible problems with CDO code giving this error) and 183094 (specifically with properties)

8.15 Why is it so complicated to do simple things in MAPI? Isn't there an easier way?

A lot of things may seem very complicated; reading a single property involves building a one-element array of Proptags, calling GetProps on the message, etc. There are a large set of helper functions provided for you (for instance HrGetOneProp will do the single-property read) -- try looking through the list in the documentation under Platform SDK|Messaging and Collaboration|Messaging API|Reference|MAPI Functions. The Exchange SDK also provides a lot of useful helper functions; look under Platform SDK|Messaging and Collaboration|Microsoft Exchange Server| Microsoft Exchange Server Programmers Guide|Reference|Functions|Function Descriptions. (for instance, a one-step function to get the GAL).

Also, as mentioned before, CDO provides much nicer wrappers for a lot of MAPI stuff; the helper functions tend to make raw MAPI code shorter, but not necessarily easier.

8.16 I found the ID for a property on messages -- but now I'm on a different server, the ID's changed. What gives?

A lot of the more obscure undocumented properties that make up messages use named properties -- these are properties accessed by passing a name and propsetID to the Exchange Server in question; the server then converts these into an ID and uses that ID to store them. If you move to another server, the ID corresponding to a particular name may change. To find the name for a particular ID, your easiest route is to use MDBView (see appendix A), open the message, find the property you're interested in in the property dialog, and use the NamesToIDs button. One trick to be aware of is that you'll probably get something along the lines of "*lppPropNames.Kind.IID = 34070" -- this means that the name is actually "0x8516" (ie a string, 0, x, hex representation of the number). Quite why things wind up this way is a mystery, but this is the way it goes.

To read from a named property using CDO, you need code like: Fields fs = message.Fields Field f = fs.Item("0x8516", propsetid) where the propsetID is got from the value from MDBView. See Q8.7, though, because CDO is a bit glitchy here.

Alternatively, look at http://www.cdolive.com/cdo10.htm where there's a big list of the names and propsetIDs for most of the properties you're every likely to need, and save yourself a lot of effort.

8.17 My MAPI application fails when I try and run it from an MTA thread and/or an ATL (D)COM server.

Threading in MAPI seems to have some issues -- in particular, when you call MAPIInitialize, it calls CoInitialize(NULL). If you've already called CoInitializeEx() with MTA threading, the MAPI call to plain CoInitialize will hit problems. This can happen with the boilerplate code that VC++ builds for ATL (D)COM servers if you select the wrong option in the wizards. (thanks to Didier Clabaut for this info).

Also, see KB 239785 for possible interaction problems with Outlook 97; fix is to upgrade to Outlook '98. (For reference, I haven't hit this problem myself and I'm in the situation this article describes.. I may just be lucky, though.)

8.18 What's with the HRESULTS I get in my C++ CDO errors? They don't make any sense?

The code that #import generates for CDO has a bug where the HRESULTs in the exceptions thrown are messed up -- see KB 235361 for a table of results, and search that table for the middle column matching hrFake where hrFake is calculated from the _com_error e as follows: HRESULT hrFake = e.WCode(); hrFake = hrFake & 0xffff; (I'm not sure what the +1000 stuff they talk about there is, to be honest; doing things this way works fine).

8.19 Why can't I open the messages I get in notifications to my Outlook extension?

(I don't know anything about this code, I'm just putting it in here because this is a reasonably common problem. Thanks to James B. at Microsoft for the source here; I'm just quoting directly from him.)

    --startquote--
LPMDB lpStore = NULL; LPMESSAGE lpMessage = NULL; LPADRBOOK lpAddrBook = NULL; LPSPropValue lpProps = NULL; lpeecb->GetObject(&lpStore, (LPMAPIPROP*) &lpMessage); HrGetOneProp((LPMAPIPROP) lpMessage, PR_ENTRYID, &lpProps); LPSTR lpszEID = (LPSTR) new char[(lpProps->Value.bin.cb*2)+1]; HexFromBin(lpProps->Value.bin.lpb, lpProps->Value.bin.cb, lpszEID); // Open the item inside the Outlook Object Model LPOUTLEXTCALLBACK lpoecb = NULL; try { _NameSpacePtr spMAPI = m_lpCEE->m_spApplication->GetNamespace("MAPI"); _MailItemPtr spMyItem = spMAPI->GetItemFromID(lpszEID); delete[] lpszEID; // Do work with the item here } catch (_com_error) { ::MessageBox(NULL, "Unable to get the MailItem in OnDelivery", "Error", MB_OK); }
I get the application pointer in the Install method under the TASK context, and I save it. You can find out how to do that by searching the MSDN for GetOfficeCharacter and going to the bottom of the one article that will come up.
--endquote--

8.20 Why do I get MAPI_E_NO_SUPPORT trying to open a calendar folder?

This is probably related to Outlook '98 -- see KB 194077.

8.21 What happens when I try mixing multi-threaded code and MAPI?

See KB 239096 (CDO glitch where it calls CoInitialize(NULL)) and 239853 (same thing wrt MAPI) for more information on this.

8.22 Why do I get CdoE_NO_SUPPORT when trying to log in?

See KB 244547 -- some versions of CDO.DLL are incorrectly labelled as being for both Win9x and NT, when they shouldn't be.

8.23 Why don't I get calendar items in my event script?

See KB 243387 -- which just says "yes, it does this" and doesn't give a workaround or suchlike.. See Q8.4 for more on this sort of thing.


Appendix: Other sources of information.

A.1 Microsoft KB.

The first place you should look is the Microsoft Knowledge base: http://support.microsoft.com/servicedesks/msdn/search/default.htm will get you to the search page for this. Select "Messaging SDK" in the first combobox, and do a keyword search, or search by KB article.

A.2 MSDN documentation.

If you don't have this locally, then http://msdn.microsoft.com/library/ will get you to the top of the documentation tree.

Also see http://support.microsoft.com/support/MessagingSDK/FAQ/cdofaq.asp for a (brief) Microsoft FAQ on CDO.

A.3 DejaNews

http://www.dejanews.com, Power Search, and search for messages in the groups listed next.

A.4 Usenet

The current recommended newsgroup is microsoft.public.win32.programmer.messaging -- this is available if you point your newsreader at msnews.microsoft.com, in case your local news feed is missing the group.

When searching DejaNews, you could also try microsoft.public.win32.programmer.mapi, which was the original group for discussion of this sort of thing.

The microsoft.public.exchange hierarchy also tends to have mail questions asked there a lot, so that would be worth trying too; specifically, microsoft.public.exchange.applications is occasionally good, as is microsoft.public.exchange.clients.

Other, obsolete (as far as I know) groups this is posted to: microsoft.public.messaging.misc microsoft.public.platformsdk.messaging

A.5 MAPI Mailing list.

The archives of this and a joining interface are at http://peach.ease.lsoft.com/archives/mapi-l.html

A.6 Other web sites.

CDOLive: http://www.cdolive.com CDOLive property lists: http://www.cdolive.com/cdo10.htm (contains a list of the majority of the undocumented properties for Outlook calendar/task/ note/contact/etc items)

http://www.cdolive.com/kb.htm has an enormous list of relevant KB articles -- this is a good place to start looking if you don't want to search the entire KB for the info you're after.

Slipstick Solutions outlook info: http://www.slipstick.com (this contains a _huge_ amount of generally useful information on utilities and plugins that may already do what you want).

Exchange FAQ (very server-oriented, but might be useful): http://www.swinc.com/resource/exch_faq.htm

Old MAPI FAQ: http://www.darkweb.com/~beng/exchange/mdevfaq.htm

http://212.17.127.87/MAPIResource has some samples of MAPI advise sinks.

MAPI samples can theoretically be found in the MAPI hierarchy on MSDN, as a starting point -- in practise, the files referred to there aren't obviously locateable (does anyone know where they are?) A better place to go is ftp://ftp.microsoft.com/developr/MAPI/Samples/MFCAPPS/MFCAPPS.ZIP -- this contains samples and a _lot_ of handy helper functions and stuff that will make your life immensely easier once you start using them. Really -- if you're doing Extended MAPI development, the functions in the Common subdirectory are invaluable timesavers. Also, this is where you find MDBView, which you'll find amazingly handy as soon as you start doing anything fiddly. This is the most essential tool in any MAPI developer's toolbox, I promise.

If you just want a very simple MAPI sample project, KB article 239795 contains enough code to get going with; I'd still advise using the mfcapps sample just because it contains MDBView..

A.7 Books.

The primary source on MAPI programming is "Inside MAPI", by Irving de la Cruz and Les Thaler, Microsoft Press. It is now out of print, but you may be able to find a second-hand copy. If you want to do low-level MAPI stuff, your life will be _much_ easier if you can find one -- it's not necessary, but it's worth keeping an eye out for.

The "MAPI, SAPI, and TAPI developers guide" you'll find by searching amazon.com for "MAPI" is pretty brief -- it's possibly better than nothing, but the documentation is better than it.

If you want information on CDO, then there's a fair few books out there; "Professional CDO Programming", from Wrox Press, is a good starting point, though I should point out that I co-authored this, so am probably not the most impartial judge of quality.

These are the only ones I've personally read -- if you search Amazon or your favorite online bookstore for "Outlook" or "CDO" in the title, you should find a lot of other books to pick from.


B. Credits.

Thanks to: James B. for the code sample given above, and for being the source of information at Microsoft.

Siegfried Weber for CDOLive.

The Slipstick Systems folks for their excellent resources.

Too many other people to mention for questions answered on newsgroups in times past, though I really must thank Eric June at Intuitive Data for fielding a lot of basic questions a lot of times..


C. Delphi extra bits.

Here's a couple of samples of doing Outlook Automation in Delphi; I don't know Delphi myself -- these are from Paul Qualls, just pasted directly in here exactly as sent, to avoid any risks of me breaking things by summarising.

1. Early binding using Variants.

  uses comobj;

  procedure CreateAnOutlookEmail;
  var
    OA , 
    NS , 
    MI , // MailItem
    FO ,
    MyUserProperty : variant ;
  begin

    OA := CreateOleObject('Outlook.Application') ;
    NS := OA.GetNameSpace('MAPI') ;


    // IDispatch(MI) := FO.Items.Add('IPM.Note');

    MI := OA.CreateItem(0) ;

    MI.To := 'user@domain.com' ;
    MI.Subject := 'Outlook EMail from Delphi' ;
    MI.Body := 'Bla bla bla' ;
    //  uncomment the next line if you need to add an attachment
      MI.Attachments.Add('c:\foo.txt', emptyparam, emptyparam, emptyparam) ; 
    MI.Send ;
  end;
2. Late Binding. A little more complex.

In Delphi, Import the typelib from Outlook by choosing Project>>Import Type Library then choose the Outlook library. Mine is "Microsoft Outlook 98 Object Model (Outlook 8.0) " After the import, you can now use "Outlook_TLB" in your uses clause to Access EVERYthing in the Outlook object model. Note that it also imports Office and MSForms typelibs for you to because Outlook's object model has dependencies on them. Now you are ready to use the following code. This method is better than variants because it gives you type checking and code completion.

  procedure SendMoreEmailFromDelphi;
  var
    OA : _DApplication ;
    NS : NameSpace ;
    MI : MailItem ;
  begin
    OA := CoApplication.Create ;
    NS := OA.GetNamespace('MAPI') ;
    NS.Logon('myProfile' , 'myPassword' , false , false ) ;
    MI := OA.CreateItem(olMailItem) as _DMailItem;
    MI.Recipients.Add('user@domain.com') ;
    MI.Subject := 'Mail From Delphi' ;
    MI.Body := 'Here is the body of the message' ;
    MI.Send ;
  end;