Managing Attachments in Outlook Messages

The Creating and Saving Outlook Message (MSG) Files article explained the steps involved in creating and saving messages, including MSG files with attachments. This article delves into the management of Microsoft Outlook attachments using Aspose.Email. It will guide you through accessing and saving attachments from a message file utilizing the attachments property of the MapiMessage class. This property is a collection belonging to the MapiAttachmentCollection class, allowing for efficient handling of attachments.

Check Inline vs Regular Attachments

“Inline” and “Regular” attachments refer to the way they are included in an email message. Regular attachments are files attached in the traditional way. They are typically displayed in a list within the email client and can be downloaded by a recipient and saved to a local storage. Inline attachments, also known as embedded or inline images, are typically used for including images or other media within the body of the email. They are not displayed in a separate list but are instead shown directly within the email’s content, such as in the body of the email. This allows you to include images or other media that are part of the message’s content. The code sample below demonstrates how to determine whether an attachment is inline or regular:

import aspose.email as ae

msg = ae.mapi.MapiMessage.load("message.msg")

for attachment in msg.attachments:
    print(f"{attachment.display_name}:{attachment.is_inline}")

Identify Reference Attachments in MAPI Messages

The Aspose.Email API includes the is_reference property of the MapiAttachment class which enables developers to identify reference attachments in a message. Try the code sample below to see how it works in a python project:

import aspose.email as ae

# Load or create a MapiMessage
msg = ae.mapi.MapiMessage.load("Message_With_Reference_Attachment.msg")

# Iterate through the attachments
for attachment in msg.attachments:
    if attachment.is_reference:
        # Process reference attachment
        pass

Save Attachments from MSG Files

To save attachments from an MSG file:

  1. Iterate through the MapiAttachmentCollection collection and get the individual attachments.
  2. To save the attachments, call the MapiAttachment class Save() method.

The following code snippet shows you how to save attachments to the local disk:

import aspose.email as ae

data_dir = "C://dataDir/"
file_name = "message.msg"

# Create an instance of MapiMessage from file
message = ae.mapi.MapiMessage.load(data_dir + file_name)

# Iterate through the attachments collection
for attachment in message.attachments:
    # Save the individual attachment
    attachment.save(data_dir + attachment.file_name)

TNEF Attachments Handling

Load and Save Attachments in TNEF Format

Aspose.Email provides methods for handling TNEF attachments. These methods allow saving and loading attachments in TNEF format, commonly used in Outlook messages (winmail.dat).

The following code sample demonstrates how to extract, save, and re-add TNEF format email attachments while managing them through memory streams or files:

import io
from aspose.email.mapi import MapiMessage, MapiAttachment

# Load the EML message (which contains a winmail.dat TNEF attachment)
msg = MapiMessage.load("message.eml")

# Save the first attachment as a winmail.dat file
msg.attachments[0].save_to_tnef("winmail.dat")

# Load the TNEF attachment from the file and add it back to the message
from_tnef_attachment = MapiAttachment.load_from_tnef("winmail.dat")
msg.attachments.add(from_tnef_attachment.long_file_name, from_tnef_attachment.binary_data)

# (Optional) If you also want to use a memory stream instead of the file
ms = io.BytesIO()
msg.attachments[0].save_to_tnef(ms)

# Reset the stream position to the beginning
ms.seek(0)

# Load the TNEF attachment directly from the memory stream and add it
from_tnef_attachment_from_stream = MapiAttachment.load_from_tnef(ms)
msg.attachments.add(from_tnef_attachment_from_stream.long_file_name, from_tnef_attachment_from_stream.binary_data)

Retrieve Nested Mail Message Attachments

The Embedded OLE (Object Linking and Embedding) attachments also appear in the attachment collection of the MapiMessage class. The code example below demonstrates how to parse a message file and extract embedded message attachments, subsequently saving them to disk for further use. The MapiMessage class offers a from_properties static method to create a new message from an embedded attachment. The following code snippet shows you how to get nested mail message attachments:

import aspose.email as ae

eml = ae.mapi.MapiMessage.load("my.msg")

for index, attachment in enumerate(eml.attachments):
    # Only the embedded messages carry OLE object data
    if attachment.object_data is None:
        continue

    # Create a MapiMessage object from the individual attachment
    get_attachment = ae.mapi.MapiMessage.from_properties(attachment.object_data.properties)

    # Convert the embedded message to a MailMessage and save it to disk
    mail_message = get_attachment.to_mail_message(ae.mapi.MailConversionOptions())
    mail_message.save(f"NestedMailMessageAttachments_{index}_out.eml", ae.SaveOptions.default_eml)

Remove Attachments

Aspose.Email library provides the functionality to remove attachments from Microsoft Outlook Message (.msg) files. Use the remove_attachments(path) method which requires the path of the message file as its parameter. As a public static method, you can call it directly without needing to instantiate an object, making the process straightforward and efficient.

The following code snippet shows you how to remove attachments from MSG files using Aspose.Email:

import aspose.email as ae

ae.mapi.MapiMessage.remove_attachments("AttachmentsToRemove_out.msg")

You can also call the static destroy_attachments method of the MapiMessage class. It works faster than remove_attachments because the remove_attachments method parses the message file.

import aspose.email as ae

# Destroy attachments in the MapiMessage
ae.mapi.MapiMessage.destroy_attachments("AttachmentsToDestroy_out.msg")

Add MSG Attachments

An Outlook message can contain other Microsoft Outlook messages in attachments either as regular or embedded ones. The MapiAttachmentCollection provides overloaded members of the add method to create Outlook messages with both types of attachments.

Add Reference Attachments to MAPI Messages

Reference attachment typically refers to an attachment that contains a reference or link to an external resource rather than the actual file itself. These references are often used in HTML emails to link to external images or resources hosted on a remote server. Instead of embedding the entire file, a reference attachment includes a URL or reference to the external content.

To add a reference attachment to a MAPI message, use the add method of the MapiAttachmentCollection class and the ReferenceAttachmentOptions class to specify:

  • The content URL (direct link to the file)

  • The provider URL (link to the file’s location or UI view)

  • The attachment provider name (e.g., “GoogleDrive”)

as shown in the code sample below.

import aspose.email as ae

# Load or create a MapiMessage
msg = ae.mapi.MapiMessage()
msg.subject = "Email with Reference Attachment"
msg.body = "This message contains a cloud-based reference attachment."

# Create ReferenceAttachmentOptions
options = ae.mapi.ReferenceAttachmentOptions(
    "https://drive.google.com/file/d/1HJ-M3F2qq1oRrTZ2GZhUdErJNy2CT3DF/",
    "https://drive.google.com/drive/my-drive",
    "GoogleDrive"
)

# Add the reference attachment to the message
msg.attachments.add("Document.pdf", options)

# Save the message to MSG format
msg.save("Message_With_Reference_Attachment.msg")

Embed Messages as Attachments

The following code snippet demonstrates how to embed Outlook MSG files as attachments within another MSG file. An embedded MSG file includes a PR_ATTACH_METHOD property with a value of 5, indicating that it is an attachment of type “message”.

import aspose.email as ae

# Create a new MapiMessage
message = ae.mapi.MapiMessage("from@test.com", "to@test.com", "Subj", "This is a message body")

# Load the attachment message
attach_msg = ae.mapi.MapiMessage.load("Message.msg")

# Add the attachment to the message
message.attachments.add("Weekly report.msg", attach_msg)

# Save the message with the embedded message attachment
message.save("WithEmbeddedMsg_out.msg")

Read Embedded Messages from Attachments

The following code snippet shows you how to read embedded messages from attachments.

import aspose.email as ae

file_name = "path/to/file.msg"

# Load the MapiMessage from file
message = ae.mapi.MapiMessage.load(file_name)

# Check if the first attachment is an Outlook message
if message.attachments[0].object_data.is_outlook_message:
    # Get the embedded message as MapiMessage
    embedded_message = message.attachments[0].object_data.to_mapi_message()
    # Perform further operations with the embedded message
    # ...

Insert and Replace Attachments

Aspose.Email API offers the functionality to insert attachments at a specific index within the parent message. Additionally, it provides the ability to replace the contents of an existing attachment with a different message attachment. Below are code snippets demonstrating how to perform attachment insertion and replacement.

Insert Attachments at Specific Locations

Aspose.Email API enables you to insert a MSG attachment into a parent MSG using the Insert method of the MapiAttachmentCollection. This method is used with the signature MapiAttachmentCollection Insert(int index, string name, MapiMessage msg). Below is a code snippet illustrating how to insert an attachment at a specific location:

import aspose.email as ae
from io import BytesIO

file_name = "path/to/file.msg"

# Load the MapiMessage from file
message = ae.mapi.MapiMessage.load(file_name)

# Save the attachment to a memory stream
memory_stream = BytesIO()
message.attachments[2].save(memory_stream)

# Load the attachment from the memory stream
get_data = ae.mapi.MapiMessage.load(memory_stream)

# Insert the loaded attachment at index 1
message.attachments.insert(1, "new 11", get_data)

Replace Attachment Contents

You can use the replace method to update the contents of an embedded attachment with new data. However, note that this method cannot be used to insert an attachment with PR_ATTACH_NUM = 4 into a collection that holds only 2 attachments. Below is a code snippet demonstrating how to replace attachment contents:

import aspose.email as ae
from io import BytesIO
file_name = "path/to/file.msg"

# Load the MapiMessage from file
message = ae.mapi.MapiMessage.load(file_name)

# Save the attachment to a memory stream
memory_stream = BytesIO()
message.attachments[2].save(memory_stream)

# Load the attachment from the memory stream
get_data = ae.mapi.MapiMessage.load(memory_stream)

# Replace the attachment at index 1 with the loaded attachment
message.attachments.replace(1, "new 1", get_data)

Rename Attachments in MapiMessages

With Aspose.Email, you can easily modify the display names of attachments in email messages loaded from a file. The following code sample demonstrates how to achieve this:

import aspose.email as ae

msg = ae.mapi.MapiMessage.load("message.msg")

msg.attachments[0].display_name = "New display name 1"
msg.attachments[1].display_name = "New display name 2"

Extract Attachments from RTF-Formatted MSG Files

For the messages formatted as RTF, the code below differentiates and extracts the attachments that are either inline or appear as an icon in the message body. The information is stored in the OLE object data of the attachment, which is available through the object_data property of MapiAttachment:

  • the \x03ObjInfo property tells whether the object is displayed inline or as an icon;
  • the Package property holds the raw content of the embedded file.
from aspose.email.mapi import MapiMessage


def is_attachment_inline(attachment):
    if attachment.object_data is None:
        return False
    # `values` is a property of MapiPropertyCollection, not a method
    for prop in attachment.object_data.properties.values:
        if prop.name == "\x03ObjInfo":
            odt_persist1 = int.from_bytes(prop.data[:2], "little")
            return (odt_persist1 & (1 << 6)) == 0
    return False


def save_attachment(attachment, file_name):
    for prop in attachment.object_data.properties.values:
        if prop.name == "Package":
            with open(file_name, "wb") as f:
                f.write(prop.data)


msg = MapiMessage.load(data_dir + "MSG file with RTF Formatting.msg")

for index, attachment in enumerate(msg.attachments):
    if is_attachment_inline(attachment):
        save_attachment(attachment, data_dir + f"attachment_{index}")

Extract Embedded OLE Objects from oledata.mso

Sometimes embedded OLE data is represented as an oledata.mso attachment in a MapiAttachment and needs to be extracted manually. oledata.mso files are in the Microsoft Compound Document File (MCDF) format, and support for such files is beyond the scope of Aspose.Email. However, Aspose.Email can be used together with a compound-file library to read their contents and save them to disk.

Aspose.Email provides the InlineAttachmentExtractor class whose enumerate_mso_package method enumerates the MSO packages contained in the binary data of oledata.mso. The returned dictionary maps the item name to the raw bytes of the compound file, which can then be passed to a compound-file reading library such as olefile.

When the message body type is HTML (not RTF) and there are OLE objects in the message, the PR_ATTACH_DATA_OBJ property is absent. In this case the information about the OLE objects is contained in oledata.mso.

import io
from aspose.email.mapi import MapiMessage, InlineAttachmentExtractor

msg = MapiMessage.load(data_dir + "double.msg")

for mapi_attachment in msg.attachments:
    if mapi_attachment.long_file_name == "oledata.mso":
        oledata = InlineAttachmentExtractor.enumerate_mso_package(
            io.BytesIO(mapi_attachment.binary_data))

        for index, (name, content) in enumerate(oledata.items()):
            # `content` is a compound file: read the CONTENTS stream with a library
            # such as olefile and write it to disk
            with open(data_dir + f"{index}.bin", "wb") as f:
                f.write(content)