Detecting and Processing Bounced Emails in Python

This article describes how to detect, analyze and process bounce messages (delivery failure notifications) with Aspose.Email for Python via .NET.

The aspose.email.bounce module has the classes needed to detect and analyze bounce messages. The main features include:

  • Detecting whether an email message is a bounce message
  • Extracting the bounce details (recipient, status code, reason, action)
  • Accessing the original message from a bounce notification
  • Handling different types of Delivery Status Notification (DSN) actions

Core Classes and Methods

Class/Method Description
BounceResult Contains the results of the bounce detection
DSNAction Enumeration representing the Delivery Status Notification actions
MailMessage.check_bounced() Method to check if a message is a bounce
MapiMessage.check_bounced() Method to check if an Outlook message is a bounce

Processing Bounced Emails

It is very common that a message sent to a recipient bounces for some reason, an invalid recipient address, for example. The check_bounced method of the MailMessage class returns a BounceResult object that provides detailed information about the recipient, the action taken and the reason for the notification.

from aspose.email import MailMessage

mail = MailMessage.load(data_dir + "message.eml")
result = mail.check_bounced()

print("IsBounced: " + str(result.is_bounced))
print("Action: " + str(result.action))
print("Recipient: " + str(result.recipient))
print("Reason: " + str(result.reason))
print("Status: " + str(result.status))

Checking if a Message is a Bounce

To determine whether an email message is a bounce message, use the check_bounced() method. It automatically detects bounce messages and extracts the relevant information.

from aspose.email import MailMessage

# Load a message
message = MailMessage.load(data_dir + "message.eml")

# Check if it is a bounce
result = message.check_bounced()

if result.is_bounced:
    print("This is a bounce message!")

Extracting Bounce Details

Once a bounce is detected, you can extract the detailed information about the delivery failure:

  • recipient: the original recipient address
  • status: the DSN status code (5.1.1, 4.2.2, and so on)
  • reason: a human-readable diagnostic code
  • action: the delivery action performed (failed, delayed, and so on)
from aspose.email import MailMessage

message = MailMessage.load(data_dir + "message.eml")
result = message.check_bounced()

if result.is_bounced:
    print(f"Failed to: {result.recipient}")
    print(f"Status: {result.status}")
    print(f"Reason: {result.reason}")
    print(f"Action: {result.action}")

Accessing the Original Message

Bounce notifications may include the original message that was returned. It is available through the original_message property of BounceResult:

from aspose.email import MailMessage

message = MailMessage.load(data_dir + "message.eml")
result = message.check_bounced()

if result.original_message is not None:
    original = result.original_message
    print(f"Original subject: {original.subject}")
    print(f"Original from: {original.from_address.address}")

Handling Different DSN Actions

The DSNAction enumeration represents different delivery status notification actions. Each action requires a different response:

Action Description Recommended action
FAILED Delivery permanently failed Remove the recipient from the list
DELAYED Delivery delayed Retry later
DELIVERED Successfully delivered No action needed
EXPANDED Expanded to multiple recipients Monitor for failures
RELAYED Relayed to a non-DSN system No action needed
NONE No action specified Investigate further
from aspose.email import MailMessage
from aspose.email.bounce import DSNAction

message = MailMessage.load(data_dir + "message.eml")
result = message.check_bounced()

if result.action == DSNAction.FAILED:
    print("Delivery permanently failed")
    # Remove from the mailing list
elif result.action == DSNAction.DELAYED:
    print("Delivery delayed")
    # Retry later
elif result.action == DSNAction.DELIVERED:
    print("Successfully delivered")

Processing Multiple Bounce Messages

To process multiple bounce messages from a directory:

import os
from aspose.email import MailMessage

bounces_dir = data_dir + "bounces"

for file_name in os.listdir(bounces_dir):
    if not file_name.endswith(".eml"):
        continue

    message = MailMessage.load(os.path.join(bounces_dir, file_name))
    result = message.check_bounced()

    if result.is_bounced:
        print(f"Bounce for: {result.recipient}")

DSN Status Codes

Bounce status codes follow the DSN (Delivery Status Notification) format defined in RFC 3463.

Permanent Failures (Start with “5.”)

Status code Description
5.1.1 Recipient not found (address not recognized)
5.1.2 Domain not found (DNS lookup failed)
5.2.1 Mailbox disabled (recipient account inactive)
5.2.2 Mailbox full (storage limit exceeded)
5.3.0 Message too large
5.4.0 Routing error

Temporary Failures (Start with “4.”)

Status code Description
4.2.1 Mailbox temporarily disabled
4.2.2 Mailbox full
4.4.1 Connection timeout
4.4.2 Timeout waiting for connection
4.5.0 Temporary server error

Common Bounce Handling Patterns

Removing Invalid Recipients from a Mailing List

When a bounce has the FAILED action, the recipient address should be removed from your mailing list:

from aspose.email import MailMessage
from aspose.email.bounce import DSNAction

message = MailMessage.load(data_dir + "message.eml")
result = message.check_bounced()

if result.is_bounced and result.action == DSNAction.FAILED:
    failed_recipient = result.recipient
    # Remove failed_recipient from your mailing list

Retrying Temporary Failures

For temporary failures (the DELAYED action), add the recipient to a retry queue:

from aspose.email import MailMessage
from aspose.email.bounce import DSNAction

message = MailMessage.load(data_dir + "message.eml")
result = message.check_bounced()

if result.action == DSNAction.DELAYED:
    # Add result.recipient to the retry queue
    pass

Extracting and Saving the Original Message

When the original message is included in the bounce notification, you may want to save it:

import uuid
from aspose.email import MailMessage, SaveOptions

message = MailMessage.load(data_dir + "message.eml")
result = message.check_bounced()

if result.original_message is not None:
    original_file_name = f"original_{uuid.uuid4()}.eml"
    result.original_message.save(data_dir + original_file_name, SaveOptions.default_eml)

Working with MapiMessage (Outlook MSG Files)

Bounce detection also works with Outlook MSG files through MapiMessage:

from aspose.email.mapi import MapiMessage

msg = MapiMessage.load(data_dir + "bounce.msg")
result = msg.check_bounced()

if result.is_bounced:
    print(f"Bounce detected: {result.recipient}")

Troubleshooting

Empty Recipient Field

Some bounce messages do not populate the recipient field. In such cases, check the X-Failed-Recipients header:

from aspose.email import MailMessage

message = MailMessage.load(data_dir + "message.eml")
result = message.check_bounced()

if not result.recipient:
    failed_recipients = message.headers.get("X-Failed-Recipients")
    if failed_recipients:
        # Process the failed recipients taken from the header
        print(failed_recipients)

Missing Original Message

Many bounce notifications do not include the original message. Always check whether it is available:

from aspose.email import MailMessage

message = MailMessage.load(data_dir + "message.eml")
result = message.check_bounced()

if result.original_message is None:
    print("Original message not included in the bounce notification")
    # Extract the recipient from the headers or other sources

BounceResult Properties

Property Type Description
is_bounced bool True if the message is a bounce message
action DSNAction The delivery action performed
recipient str The original recipient email address
status str The DSN status code (5.1.1, for example)
reason str A human-readable diagnostic reason
original_message MailMessage The original message, if it is included in the bounce

Best Practices

  1. Regular processing: process bounce messages regularly to keep your mailing lists clean.
  2. Log everything: maintain logs for compliance and troubleshooting.
  3. Respect temporary failures: implement retry logic for the DELAYED bounces.
  4. Immediate removal: remove recipients immediately for the FAILED bounces.
  5. Verify headers: check the X-Failed-Recipients header when recipient is empty.
  6. Use a dedicated folder: in IMAP, move the bounces to a dedicated folder for easier processing.