Common Tasks with MAPI

Now that you have looked at all of the basic interfaces for working with the various MAPI objects, let's take a more detailed look at how you can use them to perform specific tasks with e-mail.

Opening a Message Store

  1. Initialize and log into a MAPI session.

  2. Get the pointer to the table of message stores from the MAPI session object.

  3. Search the table based on the display name matching the store you want to open.

  4. Open the message store.0

The following sample shows how to open a message store:

HRESULT hr = S_OK;

///////////////////////////////////////////
// Step 1 - Initialize COM/MAPI, and log on
IMAPISession *pIMapi = NULL;
if(FAILED(CoInitializeEx(NULL, 0)))
   return FALSE;

if(MAPIInitialize(NULL) != S_OK)
   return FALSE;

if(MAPILogonEx(0, NULL, NULL, 0, &pIMapi) != S_OK) {
   MAPIUninitialize();
   return FALSE;
}

//////////////////////////////////////////////////////
// Step 2 - Get the pointer to the Message Store Table
IMAPITable *pIMapiStoresTable = NULL;
hr = pIMapi->GetMsgStoresTable(0, &pIMapiStoresTable);
if(FAILED(hr)) {
   pIMapi->Logoff(0, 0, 0);
   pIMapi->Release();
   pIMapi = NULL;
   MAPIUninitialize();
   return FALSE;
}

////////////////////////////////////////////////////////////
// Step 3 - Query the table for the entry that matches the
// name of the store we are interested in.
IMsgStore *pPop3Store = NULL;
while(1) {
   SRowSet *pRowSet = NULL;

   SizedSPropTagArray(2, tblColumns) = {2,{PR_DISPLAY_NAME,
      PR_ENTRYID}};
   pIMapiStoresTable->SetColumns((LPSPropTagArray)
      &tblColumns, 0);
   hr = pIMapiStoresTable->QueryRows(1, 0, &pRowSet);
   if(FAILED(hr))
      break;

   if(pRowSet->cRows != 1)
      break;


////////////////////////////////////////////////////////////
// Step 4 - Compare the name with "POP3". If it's a
// match, open it
if(_tcscmp(TEXT("POP3"), pRowSet->aRow[0].lpProps[0].
  Value.lpszW) == 0) {ENTRYID *pEntry =
      (ENTRYID *) pRowSet->aRow[0].lpProps[1].Value.bin.lpb;

      ULONG ulStoreBytes =
          pRowSet->aRow[0].lpProps[1].Value.bin.cb;

      pIMapi->OpenMsgStore(NULL, ulStoreBytes, pEntry, NULL,
         NULL, &pPop3Store);
      FreeProws(pRowSet);
      break;
   }

   // Free buffers allocated by MAPI
   FreeProws(pRowSet);
};

// Clean up the store table
pIMapiStoresTable->Release();

// Make sure we opened the store
if(!pPop3Store) {
   pIMapi->Logoff(0, 0, 0);
   pIMapi->Release();
   pIMapi = NULL;

   MAPIUninitialize();
   return FALSE;
}

// Do something now..

Creating a New Message

  1. Open a MAPI message store that will be used to contain the new message. See "Opening a Message Store" for details.

  2. Open the Drafts folder.

  3. Create a new message using the IMAPIFolder::CreateMessage() function.

  4. Add the subject and the message flags to the message using the IMessage::SetProps() function.

  5. Add the message body by using the IMessage::OpenProperty() function to get the IStream interface that you can write the message to.

  6. Create a recipient list.

  7. Call IMessage::SubmitMessage() to move the message from the Drafts folder to the Outbox. The next time the message store is synchronized, the message will be sent.

The following sample shows how to create a new message:

//////////////////////////////////////////
// Step 2 - Open up the Pop3 Drafts folder
LPSPropValue rgprops = NULL;
LPSPropValue lppPropArray = NULL;
ULONG cValues = 0;
IMAPIFolder *pPOPDraftsFolder = NULL;
SizedSPropTagArray(2, rgTags) =
   {2,{PR_CE_IPM_DRAFTS_ENTRYID,PR_OBJECT_TYPE}};

// Now get the Drafts folder
hr = pPop3Store->GetProps((LPSPropTagArray)&rgTags,
  MAPI_UNICODE, &cValues, &rgprops);
if(FAILED(hr))
   return FALSE;

hr = pPop3Store->OpenEntry(rgprops[0].Value.bin.cb,
    (LPENTRYID)rgprops[0].Value.bin.lpb, NULL, MAPI_MODIFY,
    NULL, (LPUNKNOWN*)&pPOPDraftsFolder);
if(FAILED(hr))
   return FALSE;

//////////////////////////////////////////
// Step 3 - Create a new message object
IMessage *pNewMessage = NULL;

hr = pPOPDraftsFolder->CreateMessage(NULL, 0, &pNewMessage);
if(FAILED(hr))
   return FALSE;

//////////////////////////////////////////
// Step 4 - Add a subject and message flags
SPropValue sMsgProps[2];
TCHAR tchSubject[128] = TEXT("\0");

memset(&sMsgProps, 0, sizeof(sMsgProps));

wsprintf(tchSubject, TEXT("Test Message Subject"));

sMsgProps[0].ulPropTag = PR_MESSAGE_FLAGS;
sMsgProps[0].Value.ul = MSGFLAG_UNSENT;
sMsgProps[1].ulPropTag = PR_SUBJECT;
sMsgProps[1].Value.lpszW = tchSubject;

hr = pNewMessage->SetProps(2, sMsgProps, NULL);
if(FAILED(hr)) {
   pNewMessage->Release();
   return FALSE;
}

//////////////////////////////////////////
// Step 5 - Stream the message body
IStream *pStream = NULL;
TCHAR tchBody[255] = TEXT("\0");

wsprintf(tchBody, TEXT("This is a test of the message body"));

hr = pNewMessage->OpenProperty(PR_BODY, NULL, 0,
   MAPI_CREATE|MAPI_MODIFY, (IUnknown **)&pStream);
if(FAILED(hr)) {
   pNewMessage->Release();
   return FALSE;
}

// Copy the body into the stream
DWORD dwLength = (lstrlen(tchBody)+1)*sizeof(TCHAR);
pStream->Write(tchBody, dwLength, NULL);
pStream->Release();

//////////////////////////////////////////
// Step 6 - Create a recipient list
SizedADRLIST(1, msgAdrList);

// Allocate a buffer for the entry
SPropValue rgMsgProps[3];

memset(&rgMsgProps, 0, sizeof(rgMsgProps));

rgMsgProps[0].ulPropTag = PR_ADDRTYPE;
rgMsgProps[0].Value.lpszW = TEXT("SMTP");

rgMsgProps[1].ulPropTag = PR_EMAIL_ADDRESS;
rgMsgProps[1].Value.lpszW =
   TEXT("emailname@emailaddress.com");

rgMsgProps[2].ulPropTag = PR_RECIPIENT_TYPE;
rgMsgProps[2].Value.ul = MAPI_TO;

msgAdrList.cEntries = 1;
msgAdrList.aEntries->cValues = 3;
msgAdrList.aEntries->rgPropVals = rgMsgProps;

// Add the list to the message
hr = pNewMessage->ModifyRecipients(MODRECIP_ADD,
   (LPADRLIST)&msgAdrList);

// Free the buffer
MAPIFreeBuffer((LPADRLIST)&msgAdrList);

//////////////////////////////////////////
// Step 7 - Submit the message
hr = pNewMessage->SubmitMessage(0);
if(FAILED(hr))
   return FALSE;

// Clean up
pNewMessage->Release();
pPOPDraftsFolder->Release();
pPop3Store->Release();

Reading a Message

  1. Open a MAPI message store that contains the message you want to read. See "Opening a Message Store" for details.

  2. Open the Inbox folder.

  3. Get the table of messages in the Inbox folder.

  4. Open the first message by using the IMsgStore::OpenEntry() function with the ENTRYID of the message you want to open.

  5. Open the message and retrieve its properties for display.

To read the contents of a message, you would do the following:

//////////////////////////////////////////
// Step 2 - Open up the Pop3 Inbox folder
LPSPropValue rgprops = NULL;

ULONG cValues = 0;
IMAPIFolder *pPOPInboxFolder = NULL;
SizedSPropTagArray(2, rgTags) =
   {2,{PR_CE_IPM_INBOX_ENTRYID,PR_OBJECT_TYPE}};

// Now get the Inbox folder
hr = pPop3Store->GetProps((LPSPropTagArray)&rgTags,
  MAPI_UNICODE, &cValues, &rgprops);
if(FAILED(hr))
   return FALSE;

hr = pPop3Store->OpenEntry(rgprops[0].Value.bin.cb,
    (LPENTRYID)rgprops[0].Value.bin.lpb, NULL, MAPI_MODIFY,
    NULL, (LPUNKNOWN*)&pPOPInboxFolder);
if(FAILED(hr))
   return FALSE;

////////////////////////////////////////////////////////////
// Step 3 - Get the list of messages in the folder and their
// ENTRY IDs
IMAPITable *pIInboxTable = NULL;
IMessage *pMsg = NULL;
hr = pPOPInboxFolder->GetContentsTable(0, &pIInboxTable);

if(FAILED(hr)) {
   pPOPInboxFolder->Release();
   return FALSE;
}

while(1) {
   SRowSet *pRowSet = NULL;

   // Get the From, Subject and ID fields
   SizedSPropTagArray(3, tblMessages) =
      {3,{PR_SENDER_NAME,PR_SUBJECT,PR_ENTRYID}};
   pIInboxTable->SetColumns((LPSPropTagArray)&tblMessages, 0);
   hr = pIInboxTable->QueryRows(1, 0, &pRowSet);

   if(pRowSet->cRows != 1)
      break;

for(int nVal = 0; nVal < pRowSet->aRow[0].cValues;
      nVal++) {
      TCHAR tchProperties[10] = TEXT("\0");
      DWORD dwType = 0, dwID = 0;

      dwType =
         PROP_TYPE(pRowSet->aRow[0].lpProps[nVal].ulPropTag);
      dwID =
         PROP_ID(pRowSet->aRow[0].lpProps[nVal].ulPropTag);
      wsprintf(tchProperties, TEXT("0x%08x Type:%04x
         ID:%04x\r\n"),
         pRowSet->aRow[0].lpProps[nVal].ulPropTag, dwType,
         dwID);

      // Do something, such as show the messages in a list?
      OutputDebugString(tchProperties);
   }

   /////////////////////////////////////////////////////////
   // Step 4 - Open the message. For the sake of this example,
   // let's open the first message and break out
   hr = pPop3Store->OpenEntry
      (pRowSet->aRow[0].lpProps[2].Value.bin.cb, (LPENTRYID)
      pRowSet->aRow[0].lpProps[2].Value.bin.lpb, NULL,
      MAPI_MODIFY, NULL, (LPUNKNOWN*)&pMsg);

   FreeProws(pRowSet);
   break;
}

////////////////////////////////////////////////////////////
// Step 5 - Do something w/ the message
if(pMsg) {
   // Get the from and subject
   LPSPropValue rgMsgprops = NULL;
   ULONG cMsgValues = 0;
   SizedSPropTagArray(2, rgMsgTags) = {2,{PR_SENDER_NAME,
      PR_SUBJECT}};

   hr = pMsg->GetProps((LPSPropTagArray)&rgMsgTags,
      MAPI_UNICODE, &cMsgValues, &rgMsgprops);

   // Get the body
   IStream *pStream = NULL;
   TCHAR tchBody[255] = TEXT("\0");

   hr = pMsg->OpenProperty(PR_BODY, NULL, 0, 0,
      (IUnknown **)&pStream);
   if(FAILED(hr)) {
      pMsg->Release();
      pIInboxTable->Release();
      pPOPInboxFolder->Release();
      return FALSE;
   }

   // Read from the stream into the tchBody
   hr = pStream->Read(tchBody, 254, NULL);
   pStream->Release();

   // Do something with it:
   TCHAR tchMsg[1024] = TEXT("\0");
   wsprintf(tchMsg, TEXT("From: %s\r\nSubject: %s\r\nMsg:
      %s"), rgMsgprops[0].Value.lpszW,rgMsgprops[1].
      Value.lpszW, tchBody); MessageBox(NULL, tchMsg,
      TEXT("New Message!"), MB_OK|MB_ICONINFORMATION);

   // Msg cleanup
   MAPIFreeBuffer(rgMsgprops);
   pMsg->Release();
}

// Cleanup
pIInboxTable->Release();
pPOPInboxFolder->Release();

Working with Message Attachments

Message attachments are used to send one or more additional "blobs" of data, such as a picture or sound file, along with an e-mail message. Each individual attachment to a message is supported by the IAttach interface.

Although the IAttach interface has no unique methods, it is derived from the IMAPIProp interface. Table 11.22 describes the properties that are used to configure an attachment.

Table 11.22. IAttach Properties

Property Tag

Property Type

Description

PR_ATTACH_DATA_BIN

PT_BINARY

IStream interface that can be used to access the attachment

PR_ATTACH_FILENAME

PT_TSTRING

Display name for the attachment

PR_ATTACH_METHOD

PT_LONG

Must be set to PR_ATTACH_BY_VALUE

PR_ATTACH_NUM

PT_LONG

Number that uniquely identifies the attachment within the message

PR_ATTACH_SIZE

PT_LONG

The size, in bytes, of the attachment and all of the attachment properties

PR_ENTRYID

PT_BINARY

The object's entry identifier

PR_LAST_MODIFICATION_TIME

PT_SYSTIME

The last date and time the object was modified

PR_NULL

PT_NULL

A NULL value

PR_OBJECT_TYPE

PT_LONG

The type of object

PR_PARENT_ENTRYID

PT_BINARY

The parent object's entry identifier

Creating an Attachment
  1. Create a new message that will have an attachment. See the section "Creating a New Message" for details.

  2. After you have completed setting up the message subject, body, and recipient list, use the IMessage::CreateAttach() method to get the interface pointer for a new IAttach object.

  3. Set up the properties for the attachment. You need to configure the PR_ATTACH_METHOD, PR_ATTACH_DATA_BIN, and PR_ATTACH_FILENAME property tags.

  4. Stream in the binary data for the file. This is accomplished by using the IAttach::OpenProperty() method to get the IStream interface for the data property. You can then use the IStream::Write() method to stream the data to the attachment.

  5. Submit the message to the Outbox using IMessage::SubmitMessage(). This will set up the message to be sent the next time the store is synchronized.

The following example creates a message with an attachment:

// This is at the point where our message has been created,
// and we are about to send it. Before we do, let's attach
// a sound file to the message.

////////////////////////////////////////
// Step 2 - Attach a file to the message
IAttach *pAttach = NULL;
ULONG ulAttachNo = 0;
hr = pNewMessage->CreateAttach(NULL, 0, &ulAttachNo,
  &pAttach);

if(FAILED(hr))
   return FALSE;

///////////////////////////////////////////////////
// Step 3 - Set up the properties for the attachment
SPropValue rgAttachProps[3];
TCHAR tchFileName[MAX_PATH] = TEXT("\0");
TCHAR tchFilePath[MAX_PATH] = TEXT("\0");

wsprintf(tchFileName, TEXT("Alarm1.wav"));
wsprintf(tchFilePath, TEXT("\\Windows\\Alarm1.wav"));
memset(&rgAttachProps, 0, sizeof(rgAttachProps));

rgAttachProps[0].ulPropTag = PR_ATTACH_METHOD;
rgAttachProps[0].Value.ul = ATTACH_BY_VALUE;

// Specify that we are attaching binary data. We'll stream
// in the data using IStream below.
rgAttachProps[1].ulPropTag = PR_ATTACH_DATA_BIN;

rgAttachProps[2].ulPropTag = PR_ATTACH_FILENAME;
rgAttachProps[2].Value.lpszW = tchFileName;

hr = pAttach->SetProps(3, rgAttachProps, NULL);

////////////////////////////////////////////////////////////
// Step 4 - Using IStream, let's stream in the attachment data

// from the file
HANDLE hAttachFile = NULL;
IStream *pAttachStream = NULL;

// Get the pointer to the property stream
hr = pAttach->OpenProperty(PR_ATTACH_DATA_BIN, NULL, 0,
   MAPI_CREATE|MAPI_MODIFY, (IUnknown **)&pAttachStream);

// Open up the file
hAttachFile = CreateFile(tchFilePath, GENERIC_READ,
  FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_
     NORMAL, 0);
if(hAttachFile == INVALID_HANDLE_VALUE) {
   pAttach->Release();
   pNewMessage->Release();
   pPOPDraftsFolder->Release();
   pPop3Store->Release();
   return FALSE;
}

// Read from the file, and write it to the stream
DWORD dwRead = 0, dwWritten = 0;
LPVOID lpBuffer = NULL;

lpBuffer = (LPVOID)LocalAlloc(LPTR, 4096);
do {
   ReadFile(hAttachFile, lpBuffer, 4096, &dwRead, NULL);
   if(dwRead > 0) {
      hr = pAttachStream->Write(lpBuffer, dwRead,
         &dwWritten);
   if(FAILED(hr))
      break;
   }
}while(dwRead > 0);

CloseHandle(hAttachFile);
LocalFree(lpBuffer);

pAttachStream->Commit(STGC_DEFAULT);
pAttachStream->Release();

//////////////////////////////////////////
// Step 5 - Submit the message
hr = pNewMessage->SubmitMessage(0);

if(FAILED(hr))
   return FALSE;

// Clean up
pAttach->Release();
pNewMessage->Release();
pPOPDraftsFolder->Release();
pPop3Store->Release();
Opening an Attachment
  1. Open an IMessage interface pointer for the message that contains attachments. See "Reading a Message" for details.

  2. Get the attachment table for the message using IMessage::GetAttachmentTable().

  3. Query the table for the PR_ATTACH_NUM for the attachment you are interested in opening.

  4. Get the properties from the table entry that you will use to open the attachment stream.

  5. Using the IMessage::OpenAttach() method with the attachment number you found in the query, you can get the IAttach interface pointer.

  6. Get the IStream pointer for the PR_ATTACH_DATA_BIN property. In addition, create a new file handle for the output of the data stream.

  7. Read from the stream and output to the file.

The following example shows how to open a message attachment:

////////////////////////////////////////////////////////////
// Step 2 - Already have a message that is opened
// so, let's go ahead and get the message's attachment by
// first getting the attachment table for the message
if(!pMsg)
   return FALSE;

IMAPITable *pAttachTable = NULL;
hr = pMsg->GetAttachmentTable(0, &pAttachTable);

////////////////////////////////////////////////////////////
// Step 3 - Get the properties from the table for the
// attachments for the first attachment. We'll only extract
// the first one in this example.
SRowSet *pAttachRowSet = NULL;
SizedSPropTagArray(3, tblAttachColumns) =
   {3,{PR_ATTACH_NUM,PR_ATTACH_SIZE,PR_ATTACH_FILENAME}};

pAttachTable->SetColumns((LPSPropTagArray)&tblAttachColumns, 0);
hr = pAttachTable->QueryRows(1, 0, &pAttachRowSet);
if(pAttachRowSet->cRows != 1)
   return FALSE;

////////////////////////////////////////////////////////////
// Step 4 - Grab the properties that we'll use
long lAttachNum = 0;
DWORD dwAttachSize = 0;
TCHAR tchAttachName[MAX_PATH] = TEXT("\0");

lAttachNum = pAttachRowSet->aRow[0].lpProps[0].Value.l;
dwAttachSize = pAttachRowSet->aRow[0].lpProps[1].Value.ul;
_tcscpy(tchAttachName, pAttachRowSet->aRow[0].lpProps[2].
  Value.lpszW);

FreeProws(pAttachRowSet);

////////////////////////////////////////////////////////////
// Step 5 - Get the IAttach interface for the attachment
IAttach *pAttach = NULL;

hr = pMsg->OpenAttach(lAttachNum, 0, 0, &pAttach);
if(FAILED(hr))
   return FALSE;

////////////////////////////////////////////////////////////
// Step 6 - Get the IStream interface to stream the PR_ATTACH_
// DATA_BIN property out. Also, set up a file to write to.
IStream *pAttachStream = NULL;
HANDLE hAttachFile = NULL;
TCHAR tchFilePath[MAX_PATH] = TEXT("\0");

wsprintf(tchFilePath, TEXT("\\%s"), tchAttachName);
hAttachFile = CreateFile(tchFilePath, GENERIC_WRITE, 0, NULL,
  CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
hr = pAttach->OpenProperty(PR_ATTACH_DATA_BIN, NULL, 0, 0,
   (IUnknown **)&pAttachStream);

if(hAttachFile == INVALID_HANDLE_VALUE || FAILED(hr)) {
   pAttachStream->Release();
   pAttach->Release();
   pAttachTable->Release();
   pMsg->Release();
   pIInboxTable->Release();
   pPOPInboxFolder->Release();
   return FALSE;
}

////////////////////////////////////////////////////////////
// Step 7 - Read from the stream, write to the file.
LPVOID lpBuffer = NULL;
DWORD dwRead = 0, dwWritten = 0;
lpBuffer = (LPVOID)LocalAlloc(LPTR, 4096);

do {
   memset(lpBuffer, 0, 4096);
   pAttachStream->Read(lpBuffer, 4096, &dwRead);
   if(dwRead > 0)
      WriteFile(hAttachFile, lpBuffer, dwRead, &dwWritten,
   NULL);
} while(dwRead > 0);

CloseHandle(hAttachFile);
LocalFree(lpBuffer);

// Done. Clean up
pAttachStream->Release();
pAttach->Release();
pAttachTable->Release();
pMsg->Release();
pIInboxTable->Release();
pPOPInboxFolder->Release();