Click here to Skip to main content
Click here to Skip to main content

Reading and Writing Messages in Outlook Express

By , 27 Mar 2006
Rate this:
Please Sign up or sign in to vote.
Prize winner in Competition "MFC/C++ Nov 2004"

Sample Image - Outlook_Express_Messages.jpg

Table of contents

Introduction

The application has the following features:

  1. List local folders.
  2. Create / rename / delete local folder.
  3. List messages of a local folder.
  4. Get message properties.
  5. Get message source.
  6. Create / copy / move / delete messages.
  7. Mark messages as Read or Unread.
  8. List body properties or headers.
  9. Get / set property values.
  10. Navigate body structure.
  11. Get / set body content.
  12. Insert bodies.
  13. List / add / remove attachments.

Using this Code

This code was written to provide an initial example of the IStoreFolder / IStoreNamespace classes. Then, an example of IMimeMessage / IMimeMessageTree / IMimeBody / IMimePropertySet was added to the application. The idea of this article is to document, with a complete example, all these interfaces to show how Outlook Express storage could be accessed.

In the initial dialog, all the local folders of the main identity are listed to let the user modify them. In the message dialog, you will see all the messages of the selected folder. Identifying the message source and other operations can be done here. In the 'Bodies' dialog, you will be able to view the message structure and modify it.

Points of Interest

List local folders:

// add all the folders to the list box recursively
void CDemoDlg::AddFolders(STOREFOLDERID dwFolderId)
{
    FOLDERPROPS props;
    HENUMSTORE hEnum;
    int nIndex;

    hEnum = NULL;

    // set the size of the structure
    // or the function return error
    props.cbSize = sizeof(FOLDERPROPS);

    HRESULT hr = 
      m_pStoreNamespace->GetFirstSubFolder(dwFolderId, 
      &props, &hEnum);

    while(SUCCEEDED(hr) && hr != 
               S_FALSE && hEnum != NULL) {
        nIndex = m_listFolder.AddString(props.szName);

        if(nIndex != LB_ERR && nIndex != LB_ERRSPACE) {
            // set the folder id as the data of the item
            m_listFolder.SetItemData(nIndex, props.dwFolderId);

            // add children of this folder too
            AddFolders(props.dwFolderId);
        }

        hr = m_pStoreNamespace->GetNextSubFolder(hEnum, &props);
    }

    // close the enum
    if(hEnum) {
        m_pStoreNamespace->GetSubFolderClose(hEnum);
    }
}

List messages of a folder:

//List messages of folder and add 
//all 'Subject' and 'From' to the list box
MESSAGEPROPS msgProps;
HENUMSTORE hEnumMsg;
CString item;
int nIndex;

hEnumMsg = NULL;

// set the size of the structure
// or the function return error
msgProps.cbSize = sizeof(MESSAGEPROPS);

// as we want the subject and other
// staff we get all the properties.
// you can use MSGPROPS_FAST as first parameter
// to get only a few properties of the message.
HRESULT hr = m_pStoreFolder->GetFirstMessage(0,
                                             0,
                                             MESSAGEID_FIRST,
                                             &msgProps,
                                             &hEnumMsg);

while(SUCCEEDED(hr) && hr != S_FALSE) {
    item = msgProps.pszDisplayFrom;
    item += _T("      ");
    item += msgProps.pszNormalSubject;

    // message subject and from is displayed in the list box.
    // data of each item is the message id.
    nIndex = m_listMsg.AddString(item);

    if(nIndex != LB_ERR && nIndex != LB_ERRSPACE) {
        m_listMsg.SetItemData(nIndex, msgProps.dwMessageId);
    }

    // free the message properties
    // as they are allocated by IStoreFolder.
    m_pStoreFolder->FreeMessageProps(&msgProps);

    hr = m_pStoreFolder->GetNextMessage(hEnumMsg, 
                                   0, &msgProps);
}

// close the enum
if(hEnumMsg) {
    m_pStoreFolder->GetMessageClose(hEnumMsg);
}

Display the source of a message:

// this function displays the source
// of the selected message in the list box
void CMsgDlg::OnView()
{
    ULONG ulReaded = 0;
    int nIndex;
    STOREFOLDERID dwSelMsg;
    HRESULT hr;
    IStream *pTextStream;
    char buffer[4096];

    // Get selected folder id
    nIndex = m_listMsg.GetCurSel();
    if(nIndex == LB_ERR) {
        MessageBox(_T("Select a message first."), 
                   _T("Demo Error"));
        return;
    }

    dwSelMsg = m_listMsg.GetItemData(nIndex);

    // create a IStream from the message
    hr = m_pStoreFolder->OpenMessage(dwSelMsg, 
         IID_IStream, (VOID **) &pTextStream);
    if(FAILED(hr)) {
        MessageBox(_T("Error opening message."), 
                   _T("Demo Error"));
        return;
    }

    CMsgSrcDlg msgSrcDlg;

    // read all the message
    do {
        hr = pTextStream->Read(buffer, 
             sizeof(buffer)-1, &ulReaded);

        if(FAILED(hr)) {
            MessageBox(_T("Error reading message."), 
                       _T("Demo Error"));
        }
        else {
            buffer[ulReaded] = 0;

            msgSrcDlg.AddMessageSource(buffer);
        }
    } while(SUCCEEDED(hr) && ulReaded != 0);

    if(SUCCEEDED(hr)) {
        // display message
        msgSrcDlg.DoModal();
    }

    pTextStream->Release();
}

Create a message in a folder:

IStream *newMail = NULL;
MESSAGEID msgId;
HRESULT hr;
ULONG len;
CString msgSource;

// Set msgSource to contain
// the source of the new message
...

// Create the IStream to write the new message
// this function returns the id of the new message
hr = m_pFolder->CreateStream(0, 0, &newMail, &msgId);
if(FAILED(hr)) {
    MessageBox(_T("Cannot Create Stream."), 
               _T("Demo Error"));
    return;
}

// write message source in the IStream
hr = newMail->Write((const char *) msgSource, 
     msgSource.GetLength(), &len);
if(FAILED(hr)) {
    MessageBox(_T("Cannot Write message."), 
               _T("Demo Error"));
    newMail->Release();
    return;
}

// Commit the IStream in the folder
// and use the returned msgId
hr = m_pFolder->CommitStream(0, 0, 0, 
                  newMail, msgId, NULL);
if(FAILED(hr)) {
    MessageBox(_T("Cannot Commit Stream."), 
               _T("Demo Error"));
    newMail->Release();
    return;
}

// release the IStream
newMail->Release();

List body properties:

// add property names to the combo box
// first get IMimeMessage interface
// using IStoreFolder and the message id.
hr = pFolder->OpenMessage(msgId,
                          IID_IMimeMessage,
                          (LPVOID*) &m_pMimeMsg);
if(FAILED(hr)) {
    OutputDebugString("CMessageTreeDlg::"
             "SetMessage: OpenMessage.\n");
    return;
}

// get root body of the message.
hr = m_pMimeMsg->GetBody(IBL_ROOT, 0, &m_hCurBody);
if(FAILED(hr)) {
    OutputDebugString("OEMessage::SetMessage:"
                      " Cannot get root body.\n");
    return;
}

...

// bind the body to the IMimePropertySet interface.
hr = m_pMimeMsg->BindToObject(m_hCurBody,
     IID_IMimePropertySet, (LPVOID *) &m_pPropertySet);
if(FAILED(hr)) {
    OutputDebugString("OEMessage::UpdateBodyInfo:"
           " BindToObject IID_IMimePropertySet.\n");
    return;
}

...

IMimeEnumProperties *pEnum = NULL;
ENUMPROPERTY eProp = {0};
ULONG cFetched;
HRESULT hr;

m_propNames.ResetContent();

// enum properties of the body.
hr = m_pPropertySet->EnumProps(0, &pEnum);
if(FAILED(hr)) {
    OutputDebugString("OEMessage::"
          "FillCombo: EnumProps.\n");
    return;
}

hr = pEnum->Next(1, &eProp, &cFetched);

while(SUCCEEDED(hr) && hr != S_FALSE) {
    m_propNames.AddString(eProp.pszName);

    hr = m_pAllocator->FreeEnumPropertyArray(1,
                                    &eProp, FALSE);
    if(FAILED(hr)) {
        OutputDebugString("OEMessage::FillCombo:"
                          " FreeEnumPropertyArray.\n");
    }

    hr = pEnum->Next(1, &eProp, &cFetched);
}

if(pEnum) {
    pEnum->Release();
}

List attachments:

ULONG attachCount, i, j;
HBODY *bodyAttachs = NULL;
HRESULT hr;
IMimeBody *pMimeBody;
LPSTR display;
int nItem;

m_attachs.ResetContent();

hr = m_pMimeMsg->GetAttachments(&attachCount,
                                   &bodyAttachs);
if(FAILED(hr)) {
    MessageBox(_T("Cannot get attachments:")
       _T(" GetAttachments."), _T("Demo Error"), MB_OK);
    return;
}

// keep only bodies of type IBT_ATTACHMENT.
for(i=0; i<attachCount;) {
    hr = m_pMimeMsg->IsBodyType(bodyAttachs[i],
                                  IBT_ATTACHMENT);
    if(hr != S_OK) {
        for(j=i+1; j<attachCount; j++) {
            bodyAttachs[j-1] = bodyAttachs[j];
        }

        attachCount--;
    }
    else {
        // for the attachments, get display
        // name of the body to add to the listbox.
        hr = m_pMimeMsg->BindToObject(bodyAttachs[i],
                                      IID_IMimeBody,
                                      (LPVOID *) &pMimeBody);
        if(SUCCEEDED(hr)) {
            hr = pMimeBody->GetDisplayName(&display);
            if(SUCCEEDED(hr)) {
                nItem = m_attachs.AddString(display);
                m_attachs.SetItemData(nItem,
                          (DWORD) bodyAttachs[i]);

                CoTaskMemFree(display);
            }
        }

        i++;
    }
}

if(bodyAttachs) {
    CoTaskMemFree(bodyAttachs);
}

Get body content:

while(1) { // just to save code!
    // bind body handle to a IMimeBody interface.
    hr = m_pMimeMsg->BindToObject(m_hCurBody,
                                  IID_IMimeBody,
                                  (LPVOID *) &pMimeBody);
    if(FAILED(hr)) {
        OutputDebugString("CMessageTreeDlg::"
             "UpdateBodyInfo: BindToObject\n");
        break;
    }

    ...

    encType = IET_BINARY;
    m_isTextBody = FALSE;

    m_cntType = GetContentType(m_hCurBody);

    // if the body is a 'text' treat as a text.
    // Otherwise, read it as a buffer char
    // by char.
    if(m_cntType.Find(_T("text")) == 0) {
        encType = IET_UNICODE;
        m_isTextBody = TRUE;
    }

    ...

    m_bodyContent = _T("");

    // Get body as a stream
    hr = pMimeBody->GetData(IET_UNICODE,
                            &pBodyStream);
    if(FAILED(hr)) {
        OutputDebugString("OEMessage::GetBodyText: GetData\n");
        break;
    }

    // if it is a text when we read it it comes unicode.
    if(encType == IET_UNICODE) {
        // for text bodies
        do {
            // Read the IStream into our buffer
            hr = pBodyStream->Read(lpszwBody,
                                   sizeof(lpszwBody)-sizeof(WCHAR),
                                   &ulRead);
            if(FAILED(hr)) {
                OutputDebugString("OEMessage::GetBodyText: Read\n");
            }
            else if(ulRead != 0) {
                // Null terminate it
                lpszwBody[ulRead/2] = '\0';
                m_bodyContent += (WCHAR *) lpszwBody;
            }
        } while(ulRead != 0);
    }
    else {
        do {
            // Read the IStream into our buffer.
            // It can be binary so it could
            // be displayed truncated.
            hr = pBodyStream->Read(lpszBody,
                                   sizeof(lpszBody)-sizeof(char),
                                   &ulRead);
            if(FAILED(hr)) {
                OutputDebugString("OEMessage::GetBodyText: Read\n");
            }
            else if(ulRead != 0) {
                // Null terminate it
                lpszBody[ulRead] = '\0';
                m_bodyContent += lpszBody;
            }
        } while(ulRead != 0);
    }

    pBodyStream->Release();

    break;
}

if(pMimeBody) {
    pMimeBody->Release();
}

Set body content:

HRESULT hr;
ULONG ulLength, ulWritten;
BSTR bstr = NULL;
IStream *pStream = NULL;
IMimeBody *pMimeBody = NULL;
PROPVARIANT propValue;

UpdateData(TRUE);

while(1) {
    // Create a new stream to write in the new body
    hr = CreateStreamOnHGlobal(NULL,
                               TRUE,
                               &pStream);
    if(FAILED(hr)) {
        MessageBox(_T("Cannot set content:" )
            _T(" CreateStreamOnHGlobal."),
            _T("Demo Error"), MB_OK);
        break;
    }

    // compute the new body length + the
    // zero that terminates the string
    ulLength = m_bodyContent.GetLength() + 1;

    // there are better ways
    // to do it but this is the easiest
    bstr = m_bodyContent.AllocSysString();

    // write in the new body
    hr = pStream->Write((LPWSTR) bstr,
                        ulLength * sizeof(WCHAR),
                        &ulWritten);
    if(FAILED(hr)) {
        MessageBox(_T("Cannot set content: Write."),
                   _T("Demo Error"), MB_OK);
        break;
    }

    // Commit the stream
    hr = pStream->Commit(STGC_DEFAULT);
    if(FAILED(hr)) {
        MessageBox(_T("Cannot set content: Commit."),
                   _T("Demo Error"), MB_OK);
        break;
    }

    // bind body handle to IMimeBody interface
    hr = m_pMimeMsg->BindToObject(m_hCurBody,
                                  IID_IMimeBody,
                                  (LPVOID *) &pMimeBody);
    if(FAILED(hr)) {
        MessageBox(_T("Cannot set content:")
                _T(" Commit."), _T("Demo Error"), MB_OK);
        break;
    }

    CString priCon, secCon;

    propValue.vt = VT_LPSTR;

    // get content-type property to save the body
    hr = m_pMimeMsg->GetBodyProp(m_hCurBody,
         PIDTOSTR(PID_HDR_CNTTYPE), 0, &propValue);
    if(FAILED(hr) || hr == S_FALSE) {
        MessageBox(_T("Cannot set content:")
             _T(" GetBodyProp."), _T("Demo Error"), MB_OK);
        break;
    }

    // this property has the format
    // of 'primaryType/secondaryType'
    char *sep = strchr(propValue.pszVal, '/');

    if(sep == NULL) {
        MessageBox(_T("Cannot set content:")
             _T("Content Type error."),
             _T("Demo Error"), MB_OK);
        PropVariantClear(&propValue);
        break;
    }

    secCon = sep+1;
    *sep = 0;
    priCon = propValue.pszVal;

    PropVariantClear(&propValue);

    // save the data in this new stream
    // into the body using
    // the save conent-type it had before
    hr = pMimeBody->SetData(IET_UNICODE,
                            priCon,
                            secCon,
                            IID_IStream,
                            pStream);
    if(FAILED(hr)) {
        MessageBox(_T("Cannot set content: SetData."),
                   _T("Demo Error"), MB_OK);
        break;
    }

    break;
}

if(bstr) {
    ::SysFreeString(bstr);
}
if(pMimeBody) {
    pMimeBody->Release();
}
if(pStream) {
    pStream->Release();
}

License

You can use this freely, leaving the copyright notice at the top of the files.

History

  • 28-Dec-2004 - First released: IStoreFolder / IStoreNamespace.
  • 25-Mar-2006 - Update: IMimeMessage / IMimeMessageTree / IMimePropertySet / IMimeBody.

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

About the Author

Pablo Yabo
Technical Lead http://www.nektra.com
Argentina Argentina
Pablo Yabo is a Software Developer since he was young, specialized in system internals.
In 2003 years ago founded with Sebastian Wain a Company named Nektra specialized in Outlook Express and Outlook Plugin Development.
Now there is a new Windows Live Mail API 2011 / 2009 that works on all the platforms Windows 7, Vista and XP
Follow on   Twitter

Comments and Discussions

 
QuestionHow can I manipulate contacts in outlook express 6 PinmemberVishalIndia3-Sep-09 20:41 
AnswerRe: How can I manipulate contacts in outlook express 6 PinmemberVishalIndia4-Sep-09 1:48 
AnswerRe: How can I manipulate contacts in outlook express 6 PinmemberPablo Yabo4-Sep-09 3:27 
QuestionHow to detect Switch Identity Programatically? PinmemberJitendra Shirolkar31-Aug-09 22:25 
Questiondeleting msg from windows mail PinmemberMember 31829027-Jul-09 21:58 
AnswerRe: deleting msg from windows mail PinmemberPablo Yabo8-Jul-09 2:37 
QuestionRe: deleting msg from windows mail [modified] PinmemberMember 31829028-Jul-09 20:36 
GeneralError with OpenMessage function PinmemberAlain favre12-Jun-09 5:34 
GeneralPassword protected identity PinmemberNico Cuppen4-Jun-09 0:37 
GeneralRe: Password protected identity PinmemberPablo Yabo4-Jun-09 3:25 
GeneralVS2008 Pinmemberdjaus9-Mar-09 19:53 
GeneralRe: VS2008 PinmemberPablo Yabo27-Apr-09 6:25 
GeneralWindows Mail access Pinmemberphil359565485210-Feb-09 18:30 
Pablo,
do you know how to access the windows Live mail or Windows Mail of Vista, messages and properties. Messages are ascii files but the properties of the messages such as read etc are not documented that I can tell.
GeneralRe: Windows Mail access PinmemberPablo Yabo27-Apr-09 6:24 
GeneralI must be missing something-please advise Pinmemberphil359565485210-Feb-09 17:54 
GeneralRe: I must be missing something-please advise PinmemberPablo Yabo27-Apr-09 6:28 
QuestionProblem when the application is launched from Window Service PinmemberShailk6-Feb-09 21:07 
AnswerRe: Problem when the application is launched from Window Service PinmemberPablo Yabo27-Apr-09 6:31 
QuestionBlock Sender List Pinmemberpakistan2-Feb-09 22:55 
AnswerRe: Block Sender List PinmemberPablo Yabo27-Apr-09 6:34 
Generalcreate a .dbx file at a specified location. Pinmemberravimalika29-Jan-09 22:12 
QuestionCan I get the domain of a message from which it was downloaded? Pinmemberjoel_12341-Oct-08 11:43 
GeneralHooking message store events Pinmembergnxfiles21-May-08 11:55 
GeneralChanges are not saved in reports in Windows Mail Pinmemberv_klad8-Apr-08 11:52 
GeneralProblems with IMimeMessage::SetProp and IMimeMessage::SetBodyProp PinmemberVishwananda Hemadri9-Mar-08 23:24 
QuestionCan you tell me what's wrong in the sample of MS? Pinmembercrystalicetpmc5-Mar-08 0:35 
AnswerRe: Can you tell me what's wrong in the sample of MS? PinmemberPablo Yabo5-Mar-08 1:46 
GeneralRe: Can you tell me what's wrong in the sample of MS? Pinmembercrystalicetpmc5-Mar-08 3:47 
GeneralRe: Can you tell me what's wrong in the sample of MS? PinmemberPablo Yabo5-Mar-08 5:48 
GeneralRe: Can you tell me what's wrong in the sample of MS? Pinmembercrystalicetpmc5-Mar-08 8:22 
GeneralRe: Can you tell me what's wrong in the sample of MS? Pinmembercrystalicetpmc6-Mar-08 4:24 
QuestionHow to link at C# project Pinmemberap8211-Oct-07 23:07 
AnswerRe: How to link at C# project PinmemberPablo Yabo4-Feb-08 1:28 
Questionthis demo can't modify the content of a mail in oe or winodws mail(vista) Pinmemberfangqing11-Sep-07 15:28 
AnswerRe: this demo can't modify the content of a mail in oe or winodws mail(vista) PinmemberKlirik10-Dec-07 5:45 
QuestionModifying .. [modified] PinmemberHStrix20-Aug-07 2:38 
QuestionMail Access In Service Mode Pinmembersurez12-Aug-07 21:04 
QuestionHow get the access to IMAP folders? [modified] PinmemberDieter_Kr20-Jul-07 1:37 
GeneralWindows Mail: IStoreNamespace/IStoreFolder RegisterNotification Pinmemberbask198218-Jun-07 16:43 
GeneralBulk save attachments and replace with link Pinmemberbaworth18-May-07 17:14 
QuestionIs there a way to have such functionality in C# Pinmemberaleccc2-May-07 3:41 
AnswerRe: Is there a way to have such functionality in C# PinmemberPablo Yabo2-May-07 3:53 
GeneralCreate Folder PinmemberMember #292037814-Feb-07 10:18 
GeneralI want to save mail attachments PinmemberV.A24-Dec-06 23:33 
Questionhow can read the deleted mail from outlok express Pinmembernaveen padiyar10-Dec-06 18:46 
QuestionCan I read attachments in-memory and parse it to the string PinmemberBParsi4-Dec-06 16:56 
AnswerRe: Can I read attachments in-memory and parse it to the string PinmemberBParsi4-Dec-06 16:57 
QuestionHow to use IMimeMessage interface to read an EML file? Pinmemberpashya_comp17-Oct-06 18:17 
GeneralIMimeMessageW Pinmemberherr_cristi12-Sep-06 4:32 
GeneralRe: IMimeMessageW Pinmemberratass200216-Oct-06 19:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140421.2 | Last Updated 27 Mar 2006
Article Copyright 2004 by Pablo Yabo
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid