Introduction
Many applications can usefully include the ability to send email. Any MFC SDI or MDI application, for instance, can easily send the underlying CDocument
as an attachment to an email by simply adding a message map entry. The MFC implementation does, however, throw up an email window and expects user interaction. This class grew out of my need for an application to be able to send email without the user having to interact with the email transport subsystem.
Background
You may have seen this code before. It was first published on
CodeGuru[
^] in 1999. (As an aside, I was surprised to see just how many sites host this code, having copied it from CodeGuru. Yeah, I do a little ego surfing now and then :) )
The real origin of this code was the implementation of email in MFC. I did a cut and paste of the MFC MAPI code and modified it to do what I needed. From that grew this class. There are still pieces of the original MFC code in the class as you'll see from the comments I copied.
Important Environmental Assumptions
The class relies on having a MAPI transport available and correctly configured. It's been tested, over the years, with Microsoft Outlook and Microsoft Outlook Express. If your system contains a correctly configured copy of either of those programs the class should work like a charm.
But that statement hides a multitude of complications. The most important complication is that Outlook Express (I don't have enough Outlook experience to take the risk of being specific about Outlook) needs to be configured on a per-user basis. If I log on to my computer (the one I'm writing this on) as myself Outlook Express comes up already configured to use my POP3 incoming and SMTP outgoing accounts.
If I create a new user account on this machine and that user account launches Outlook Express it will be launching the exact same binary as my account does. But Outlook Express references the user specific registry hive (HKEY_CURRENT_USER
) to get its list of configured email accounts. A new user hasn't set those accounts so Outlook Express will be unable to send or receive mail for this new user account even though it works perfectly fine for my main account. (I suspect it's actually via configuration files that are specified in the registry. For the purpose of this section it doesn't really matter where the data is stored, merely that it's stored on a per-user basis (and to make it even more complicated, on a profile basis within each user account)).
With interactive log on sessions this usually doesn't bite much. It's pretty easy to notice that for some reason the application isn't sending email, note that this user hasn't configured Outlook Express, configure it and the problem is solved. (And in one sentence I've shrugged off a world of remote support problems.) Where it can bite is when you try to use the class in a service.
Services typically start when the machine is booted and continue to run until shutdown. They specifically don't require a logon session to run. So the question is, if they don't require a logon session, who are they running as? The most usual answer to this question is that they are running as the LocalSystem
user. This is a special, highly privileged account over which even the Administrator account has little control. You can't log on as LocalSystem
and therein lies the rub. If you can't log on as LocalSystem
you can't easily configure Outlook Express for that account.
Now read the next sentence carefully. The class will not work in a service that runs as LocalSystem
. Go back and read it again. If you want to use the class in a service you must run the service using an account which can log on and configure the mail transport system. Obviously, you must have logged on to that account at least once to configure email. (See below).
The class
Now we've got the caveats (and solutions to most problems) out of the way, let's look at the class. Here's the definition.
class CIMapi
{
public:
CIMapi();
~CIMapi();
enum errorCodes
{
IMAPI_SUCCESS = 0,
IMAPI_LOADFAILED,
IMAPI_INVALIDDLL,
IMAPI_FAILTO,
IMAPI_FAILCC,
IMAPI_FAILATTACH
};
void Subject(LPCTSTR subject);
void Text(LPCTSTR text) { m_text = text; }
UINT Error();
void From(LPCTSTR from) { m_from.lpszName = (LPTSTR) from; }
static BOOL HasEmail();
BOOL To(LPCTSTR recip);
BOOL Attach(LPCTSTR path, LPCTSTR name = NULL);
BOOL Send(ULONG flags = 0);
private:
BOOL AllocNewTo();
MapiMessage m_message;
MapiRecipDesc m_from;
UINT m_error;
CString m_text;
ULONG (PASCAL *m_lpfnSendMail)(ULONG, ULONG, MapiMessage*, FLAGS, ULONG);
static HINSTANCE m_hInstMail;
static BOOL m_isMailAvail;
};
Typical usage would be something like this.
void CBugReport::OnOK()
{
CIMapi mail;
if (mail.Error() == IMAPI_SUCCESS)
{
mail.To("ultramaroon@cox.net");
mail.To("someoneelse@somewhereelse.com");
mail.Cc("cc@cc.com");
mail.From("user@somewhere.com");
mail.Subject("Test Email");
mail.Attach("somefilename");
mail.Attach("someotherfile", "different_name_for_recipient");
mail.Text("Body text for this email");
mail.Send();
}
else
{
}
CDialog::OnOK();
}
Well that looks pretty simple. Create a
CIMapi
instance, call some functions that set things like the recipient, the subject, append some attachments and send the email.
CIMapi internals
The constructor zeroes some internal structures and then attempts to load
MAPI32.DLL
. If it fails to load the DLL it sets an internal error variable to
IMAPI_LOADFAILED
and returns. If it succeeds in loading the DLL it searches for the
MAPISendMail
entry point in the DLL. If it finds this entry point it assumes it has a valid DLL, otherwise it sets the internal error variable to
IMAPI_INVALIDDLL
and returns.
You can avoid the overhead of loading MAPI32.DLL
if all you want to know is whether MAPI is available by calling the static member function HasEmail()
which checks the registry for the existence of a specific key and checks that MAPI32.DLL
is somewhere on your path.
You'll see lots of calls to malloc()
, realloc()
and free()
sprinkled through the code. The MAPI implementation goes back a long way and uses pointers to arrays of structures. Handling these using new
and delete
is a pain when you need to append a new structure to the array.
Using the class in a service
Firstly, reread
Important Environmental Assumptions. Now include the source files in your project and
#define SERVER
for the project. The symbol doesn't change anything in the header file but it does suppress some UI related code in the class implementation. Frankly, I don't recommend trying to use this class in a service. I've found the whole subject is just too flaky and vaguely documented. You're on your own if you choose to go ahead and try using the class inside a service.
So why use MAPI instead of SMTP?
In general, if your mail transport eventually delegates to SMTP (for example, using Outlook Express), I'd recommend using an SMTP library in your code. That said, there are still many organisations using MAPI based email systems. I've also found that many SMTP libraries are a pain to use and indeed, in my own code, I prefer to use this class even when dealing with SMTP, as long as I can guarantee Outlook Express is available. Of course, this preference may simply be inertial :)
History
28 February 2004 - Initial CodeProject release.