How to Read MSG Files in C++

How to Read MSG Files in C++

Aspose.Email FOSS for C++ lets you open and parse Outlook MSG files with a single function call. mapi_message::from_file() and mapi_message::from_stream() load the full message — subject, body, sender, recipients, and attachments — into an in-memory object with no Outlook or COM dependency.

Step 1 — Set Up the Project

Clone the repository and add it to your CMakeLists.txt:

git clone https://github.com/aspose-email-foss/Aspose.Email-FOSS-for-Cpp.git
add_subdirectory(Aspose.Email-FOSS-for-Cpp)
target_link_libraries(your_target PRIVATE AsposeEmailFoss::AsposeEmailFoss)

Step 2 — Include the Header

#include "aspose/email/foss/msg/mapi_message.hpp"

This single header provides mapi_message and all supporting types in the aspose::email::foss::msg namespace.


Step 3 — Load a Message from a File Path

Use mapi_message::from_file() when you have a std::filesystem::path:

#include <filesystem>
#include <iostream>
#include "aspose/email/foss/msg/mapi_message.hpp"

int main()
{
    const auto message = aspose::email::foss::msg::mapi_message::from_file(
        std::filesystem::path("sample.msg"));

    std::cout << "Subject: " << message.subject() << '\n';
    std::cout << "From:    " << message.sender_name()
              << " <" << message.sender_email_address() << ">\n";
    std::cout << "Body:    " << message.body() << '\n';
}

Step 4 — Load a Message from a Stream

Use mapi_message::from_stream() to parse from any std::istream — useful for processing MSG data received over a network or from an in-memory buffer:

#include <fstream>
#include <iostream>
#include "aspose/email/foss/msg/mapi_message.hpp"

int main()
{
    std::ifstream input("sample.msg", std::ios::binary);
    auto message = aspose::email::foss::msg::mapi_message::from_stream(input);

    std::cout << "Subject: " << message.subject() << '\n';
}

Always open the stream with std::ios::binary to prevent Windows line-ending translation from corrupting the CFB container.


Step 5 — Iterate Recipients

message.recipients() returns a std::vector of recipient objects, each with display_name, email_address, and recipient_type fields:

#include <fstream>
#include <iostream>
#include "aspose/email/foss/msg/mapi_message.hpp"

int main()
{
    std::ifstream input("sample.msg", std::ios::binary);
    auto message = aspose::email::foss::msg::mapi_message::from_stream(input);

    for (std::size_t i = 0; i < message.recipients().size(); ++i)
    {
        const auto& r = message.recipients()[i];
        std::cout << "[" << (i + 1) << "] "
                  << r.display_name << " <" << r.email_address << ">"
                  << " type=" << r.recipient_type << '\n';
    }
}

Step 6 — Iterate Attachments

message.attachments() returns a std::vector of attachment objects. Each attachment exposes filename, mime_type, data (the raw bytes), content_id, and the is_embedded_message() predicate:

#include <fstream>
#include <iostream>
#include "aspose/email/foss/msg/mapi_message.hpp"

int main()
{
    std::ifstream input("sample.msg", std::ios::binary);
    auto message = aspose::email::foss::msg::mapi_message::from_stream(input);

    for (std::size_t i = 0; i < message.attachments().size(); ++i)
    {
        const auto& a = message.attachments()[i];
        std::cout << "[" << (i + 1) << "] "
                  << a.filename << "  mime=" << a.mime_type
                  << "  bytes=" << a.data.size()
                  << "  embedded=" << (a.is_embedded_message() ? "yes" : "no")
                  << '\n';
    }
}

Common Issues and Fixes

Empty body() after loading. Some messages store text only in the HTML body property. Check message.html_body() when message.body() returns an empty string.

Stream opened without std::ios::binary. On Windows, text-mode streams translate CRLF sequences, silently corrupting binary CFB data. Always open MSG files with std::ios::binary.

msg_exception thrown for a valid-looking file. Verify the file is a genuine MSG (OLE Compound File). Files saved by some export tools may be plain text or MIME and cannot be parsed by mapi_message::from_file().

recipients() returns an empty vector. Older MSG files sometimes omit the recipient table but store display recipients in the display_to MAPI property. Access it via message.get_property_value() with common_message_property_id::display_to and property_type_code::ptyp_string.

Large attachment data in memory. mapi_message::from_file() and from_stream() load the entire message including all attachment bytes. For large files, open the underlying container with msg_reader and inspect attachment streams without fully materialising them.

Frequently Asked Questions

What is the difference between from_file() and from_stream()?

Both produce an identical mapi_message object. from_file() accepts a std::filesystem::path and opens the file internally. from_stream() reads from any std::istream you provide — useful when the MSG data is already in memory or arriving over a socket.

Does from_file() keep the file open after loading?

No. The file is opened, fully parsed into memory, and closed before from_file() returns. You can move or delete the source file immediately after the call.

Can I read MSG files created by Outlook on macOS or Linux?

Yes. The MSG format (OLE Compound File + MAPI properties) is portable across operating systems. Any .msg file produced by any version of Outlook can be opened with mapi_message::from_file().

How do I read the HTML body?

Call message.html_body(). If the message was created with only a plain-text body, this returns an empty string. message.body() returns the plain-text body.

Is strict mode the default?

mapi_message::from_file(path, strict) and from_stream(stream, strict) accept an optional strict boolean. The default is false, which tolerates minor structural deviations in real-world MSG files.

See Also