Skip to main content

Python Library to parse forwarded email messages

Project description

email-forward-parser-py

email-forward-parser extracts the original message details from forwarded email content. It can work with plain forwarded bodies or full .eml messages, including common MIME encodings and attached .eml files.

The package is useful when you receive emails forwarded by users or mailboxes and need to recover the original sender, recipients, subject, sent date, and body.

Features

  • Detects forwarded messages from subject prefixes such as Fwd: and FW:.
  • Parses forwarded bodies from Gmail, Apple Mail, Outlook/Office 365, Thunderbird, Yahoo Mail, HubSpot, Missive, IONOS, and several localized clients.
  • Extracts original metadata: From, To, Cc, Subject, Date, and body.
  • Parses full .eml messages with MIME decoding.
  • Handles quoted-printable/base64 text bodies and attached message/rfc822 or .eml attachments.
  • Rebuilds an .eml string for the original message when a forwarded email is detected.
  • Ships with a 100% statement coverage gate.

Installation

python -m pip install email-forward-parser

Python 3.10 or newer is required.

The distribution name is email-forward-parser, but the import package is emailforwardparser:

from emailforwardparser.forward_parser import get_forwarded_metadata
from emailforwardparser.client import EmailParserClient

Quick Start

Use get_forwarded_metadata() when you already have the email body text.

from emailforwardparser.forward_parser import get_forwarded_metadata

body = """Hi team

---------- Forwarded message ---------
From: Jane Doe <jane@example.com>
Date: Mon, 1 Jan 2024 at 12:00 PM
Subject: Original subject
To: Bob <bob@example.com>
Cc: Copy <copy@example.com>

Original body.
"""

result = get_forwarded_metadata(body, "Fwd: Original subject")

print(result.forwarded)             # True
print(result.message)               # Hi team
print(result.email.subject)         # Original subject
print(result.email.from_.name)      # Jane Doe
print(result.email.from_.address)   # jane@example.com
print(result.email.to[0].address)   # bob@example.com
print(result.email.cc[0].address)   # copy@example.com
print(result.email.body)            # Original body.

Use EmailParserClient when you have a full .eml message.

from emailforwardparser.client import EmailParserClient

client = EmailParserClient()

with open("forwarded.eml", encoding="utf8") as file:
    raw_eml = file.read()

metadata = client.get_original_metadata(raw_eml)

if metadata.forwarded:
    print(metadata.email.from_.address)
    print(metadata.email.subject)
    print(metadata.email.body)

API

get_forwarded_metadata(body, subject=None)

Parses a forwarded body string and returns a ForwardMetadata dataclass.

from emailforwardparser.forward_parser import get_forwarded_metadata

result = get_forwarded_metadata(body, subject)

subject is optional. Passing it helps the parser detect forwards that have a forwarded subject prefix but no separator line in the body.

ForwardMetadata

@dataclass
class ForwardMetadata:
    forwarded: bool
    message: str
    email: OriginalMetadata
  • forwarded: True when forwarded content was detected.
  • message: the note written by the person who forwarded the email, before the forwarded block.
  • email: metadata extracted from the original email.

OriginalMetadata

@dataclass
class OriginalMetadata:
    date: str
    subject: str
    body: str
    from_: MailboxResult
    to: list[MailboxResult]
    cc: list[MailboxResult]

The sender field is named from_ because from is a Python keyword.

MailboxResult

@dataclass
class MailboxResult:
    name: str
    address: str

When a mailbox line has no valid email address, address is empty and the raw value is kept in name.

EmailParserClient

EmailParserClient is the high-level wrapper for full .eml messages.

from emailforwardparser.client import EmailParserClient

client = EmailParserClient()

Methods:

  • get_original_metadata(email: str) -> ForwardMetadata Parses a raw .eml string and returns metadata for the original email. If the message is not forwarded, it returns metadata for the message itself.
  • get_original_metadata_from_file(file_path: str) -> ForwardMetadata Reads a .eml file and returns the same metadata object.
  • get_original_eml(email: str) -> dict Returns a dictionary with:
    • forward: whether the input was detected as forwarded.
    • Send-To: the first address from the wrapper message's From header.
    • eml: a raw .eml string for the original message when forwarded, or the input/attached message when not forwarded.
  • get_original_eml_from_file(file_path: str) -> dict Reads a .eml file and returns the same dictionary.

Examples

Parse an Apple Mail Forward Without a Subject

from emailforwardparser.forward_parser import get_forwarded_metadata

body = """Personal note

Begin forwarded message:

From: Jane Doe <jane@example.com>
Date: Mon, 1 Jan 2024 at 12:00 PM
Subject: Original subject
To: Bob <bob@example.com>

Original body.
"""

result = get_forwarded_metadata(body)

assert result.forwarded is True
assert result.message == "Personal note"
assert result.email.subject == "Original subject"
assert result.email.from_.address == "jane@example.com"

Parse a Quoted-Printable .eml

from emailforwardparser.client import EmailParserClient

raw_eml = """From: Forwarder <forwarder@example.com>
To: parser@example.com
Subject: Fwd: Original subject
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable

Hi=0A=0A---------- Forwarded message ---------=0AFrom: Jane Doe <jane@example.com>=
=0ADate: Mon, 1 Jan 2024 at 12:00 PM=0ASubject: Original subject=0ATo: Bob =
<bob@example.com>=0A=0AOriginal caf=C3=A9 body.=0A
"""

metadata = EmailParserClient().get_original_metadata(raw_eml)

assert metadata.forwarded is True
assert metadata.email.body == "Original café body."

Rebuild the Original .eml

from email.message import EmailMessage

from emailforwardparser.client import EmailParserClient

message = EmailMessage()
message["From"] = "Forwarder <forwarder@example.com>"
message["To"] = "parser@example.com"
message["Subject"] = "Fwd: Original subject"
message.set_content("""Hi

---------- Forwarded message ---------
From: Jane Doe <jane@example.com>
Date: Mon, 1 Jan 2024 at 12:00 PM
Subject: Original subject
To: Bob <bob@example.com>

Original body.
""")

data = EmailParserClient().get_original_eml(message.as_string())

assert data["forward"] is True
print(data["eml"])

Read From a File

from emailforwardparser.client import EmailParserClient

client = EmailParserClient()

metadata = client.get_original_metadata_from_file("forwarded.eml")
data = client.get_original_eml_from_file("forwarded.eml")

print(metadata.email.subject)
print(data["eml"])

Non-Forwarded Messages

If a message is not forwarded, forwarded is False. With the client wrapper, the returned email metadata describes the message itself.

from emailforwardparser.client import EmailParserClient

metadata = EmailParserClient().get_original_metadata(raw_eml)

if not metadata.forwarded:
    print("This was not a forwarded message")
    print(metadata.email.subject)

Behavior Notes

  • Parsing is regex-based. It is designed for common forwarded-message formats, not every possible custom email template.
  • Missing fields are returned as empty strings or empty lists.
  • Multiple To and Cc recipients are returned in order.
  • The parser normalizes common email encodings and non-breaking spaces.
  • The client prefers the first non-attachment text/plain body part when reading multipart .eml messages.
  • Attached .eml messages are returned directly when present.

Development

Install the development and test tools:

python -m pip install -e ".[dev,test]"

Run the full local quality gate:

python -m isort --check-only emailforwardparser tests
python -m black --check emailforwardparser tests
python -m ruff check emailforwardparser tests
python -m mypy
python -m pytest

pytest is configured to require 100% statement coverage.

Release

Build and verify the distribution:

python -m pip install --upgrade build twine
python -m pytest
rm -rf dist build
python -m build
python -m twine check dist/*

Upload to PyPI:

python -m twine upload dist/*

License and Credits

This project is distributed under the Apache License, Version 2.0. See LICENSE for the full license text.

The project was originally created by Garrett Marking. This fork/package includes additional maintenance, packaging, typing, test coverage, and parser/client fixes by Felipe Hertzer.

Apache 2.0 allows redistribution and modification, including publishing to PyPI, as long as the license conditions are followed. In practice for this package:

  • Keep the Apache 2.0 license text with redistributions.
  • Keep existing attribution notices from the source distribution.
  • Keep the NOTICE file with source and binary distributions.
  • Make it clear when files have been modified.

For the canonical license terms, refer to the Apache Software Foundation's Apache License 2.0 page.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

email_forward_parser-0.1.3.tar.gz (24.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

email_forward_parser-0.1.3-py3-none-any.whl (25.1 kB view details)

Uploaded Python 3

File details

Details for the file email_forward_parser-0.1.3.tar.gz.

File metadata

  • Download URL: email_forward_parser-0.1.3.tar.gz
  • Upload date:
  • Size: 24.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for email_forward_parser-0.1.3.tar.gz
Algorithm Hash digest
SHA256 4f1358292887db29714182b837d8f4a571cca9994c71f5ec23e89bc0deec4edd
MD5 74a68645451e4536cd8873c6f5714235
BLAKE2b-256 93345bd712f0419937a1f2cff826fa742dd095ac3049a6b6ab86739addeb1703

See more details on using hashes here.

Provenance

The following attestation bundles were made for email_forward_parser-0.1.3.tar.gz:

Publisher: workflow.yml on felipehertzer/email-forward-parser-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file email_forward_parser-0.1.3-py3-none-any.whl.

File metadata

File hashes

Hashes for email_forward_parser-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 c23a5e60ab9c4eb63bce99198396145dbfd090237f0bbb704b113b1a6be9d385
MD5 8275f6e841cafd72114e4dcac6850403
BLAKE2b-256 510dc71adb1fe8ab37e6e408658633b3352fb99f096b8ceedc55f8e44e84be35

See more details on using hashes here.

Provenance

The following attestation bundles were made for email_forward_parser-0.1.3-py3-none-any.whl:

Publisher: workflow.yml on felipehertzer/email-forward-parser-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page