Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Another simple MAPI class

0.00/5 (No votes)
27 Feb 2004 1  
Adding MAPI functionality to your application.

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
    };

//  Attributes

    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();

//  Operations

    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");            //  Set recipient name (me)

         mail.To("someoneelse@somewhereelse.com");  //  Second recipient

         mail.Cc("cc@cc.com");                      //  CC recipient

         mail.From("user@somewhere.com");           //  Identify sender (not strictly 

                                                    //  necessary since

                                                    //  MAPI will fill this in for you)

         mail.Subject("Test Email");                //  Subject of this email

         mail.Attach("somefilename");               //  Attaching a file

         mail.Attach("someotherfile", "different_name_for_recipient");
                                                    // Attach another file but give it

                                                    // a different name inside the 

                                                    // email itself

 
         // Put text of message in body

         mail.Text("Body text for this email");     //  Set body text

         mail.Send();                               //  Now send the mail! 

    }
    else
    {
        // Do something appropriate to the error...

    }
    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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here