Skip to main content

eml_parser

Documentation Status PyPI PyPI pyversions

Turn any e-mail into structured, indicator-rich JSON, even the broken and the malicious ones.

eml_parser is a Python library for parsing .eml files. It returns everything you would otherwise dig out of a message by hand: headers, the mail route, bodies, attachments and the indicators of compromise (IOCs) hiding in them. It is built at GOVCERT.LU for incident response, phishing triage, threat intelligence and mail analysis pipelines, where messages are rarely well formed and are sometimes crafted to break parsers.

Why eml_parser?

  • Indicators, not just fields. URLs, domains, IP addresses, e-mail addresses and remote file shares are extracted from the text, the HTML (including href, src, form actions, frames and scripts), and from attachments which carry links (SVG, scripts, shortcuts, calendar invitations, ...).
  • Phishing tricks are spotted. Obfuscated IPs (http://3232235777/, http://0xC0A80101/), UNC and WebDAV paths (\\host\share, file://, smb://) and disguised attachments (invoice.pdf.exe, right-to-left override characters, executables posing as documents) are reported.
  • Sender authentication at a glance. SPF, DKIM, DMARC and ARC results from Authentication-Results and Received-SPF are collected in header.authentication.
  • Robust against broken mail. Missing or wrong multipart boundaries, bad charsets, invalid dates, garbage in front of the headers and other defects do not make the content disappear; defects are reported instead.
  • Hardened for hostile input. Parsing time stays linear for pathological headers, deeply nested structures and huge bodies, and crafted headers can not crash the parser.
  • Deterministic results. The same message gives the same output on every run, with unique indicators in order of first appearance.
  • Safe to share. By default URLs, domains, IPs and addresses are reported as SHA-256 hashes; plain values are opt-in with include_raw_body=True.
  • Fast. Built on the linear-time regex engine, and creating a parser is cheap enough to do it for every message.

Installation

pip install eml_parser[filemagic]

Python 3.11 or newer is required.

⚠️ Note: The filemagic extra installs file-magic to determine the mime types of attachments. python-magic is a drop-in replacement, so if either of the two is installed, you are fine. Only if neither is available no mime types are determined. If you don't want to / cannot use the extra, install via:

pip install eml_parser

Known Issues

OSX users

Make sure to install libmagic, else eml_parser will not work.

Quick start

import eml_parser

ep = eml_parser.EmlParser()

with open('sample.eml', 'rb') as fhdl:
    parsed_eml = ep.decode_email_bytes(fhdl.read())

print(parsed_eml['header']['subject'])
print(parsed_eml['header'].get('authentication'))  # only present if the message has such headers

decode_email('sample.eml') reads a file by path, decode_email_bytes() takes the raw message. The result is a plain dict; eml_parser.decode.export_to_json() turns it into a JSON string (dates, hashes and attachment data included).

The parser can be tuned with a few options, for example:

ep = eml_parser.EmlParser(
    include_raw_body=True,      # report plain URLs, domains, IPs, ... instead of hashes, and keep the body text
    include_attachment_data=True,  # keep the (base64 encoded) content of the attachments
    parse_attachments=True,     # hash and inspect attachments
    domain_force_tld=True,      # only accept domains with a valid TLD (public suffix list)
    ip_force_routable=True,     # ignore private and reserved IP addresses
    email_force_tld=True,       # only accept e-mail addresses with a TLD
    pconf={'whiteip': ['192.168.1.1'], 'whitefor': ['postmaster@example.com']},
)

See the API documentation for all options. Ready-to-run scripts are in examples/.

What you get

Fields are only present when there is something to report (for example authentication, defect and anomaly).

Where Field Content
header from, to, cc, subject, date Decoded addresses and subject, date as an ISO 8601 string
received The hops of the mail route: from, by, with, for, date, plus the raw line in src
received_domain, received_ip, received_foremail Domains, IPs and recipients found along the route
authentication SPF, DKIM, DMARC, ARC, ... results, e.g. {"spf": ["pass"]}
header All raw header fields, in order of appearance
defect Structural problems found in the message, e.g. NoBoundaryInMultipartDefect
body[] content_type, hash, boundary One entry per text part, with the SHA-256 of its content
uri, uri_noscheme, unc, email, domain, ip Indicators (with include_raw_body=True)
uri_hash, uri_noscheme_hash, unc_hash, email_hash, domain_hash, ip_hash The same indicators as SHA-256 (default)
attachment[] filename, extension, size Name, extension and size
hash MD5, SHA-1, SHA-256 and SHA-512
mime_type, mime_type_short As detected by libmagic
anomaly Tricks to disguise the type, see below

Attachment anomalies

anomaly lists filename-bidi-control, filename-control-chars, filename-double-extension, filename-trailing-dot-or-space, filename-padding and extension-content-mismatch when they apply.

Example

This is the (abridged) result for a small phishing message with failing authentication, an obfuscated IP in a link, a network share and a double-extension executable (samples/sample_phishing.eml; the raw headers and the content headers of the parts are left out):

{
  "attachment": [
    {
      "filename": "invoice.pdf.exe",
      "size": 256,
      "extension": "exe",
      "hash": {
        "md5": "00a2126b03824e2eaa5cc87ca2cbda1d",
        "sha1": "8836fcadc9faa02c011534401b5112f4cdf0b5d4",
        "sha256": "eef52d1656582cb18a4f2deba3aed5256f754e45998dc718845b013bccb44186",
        "sha512": "621d591638d18f6b7c44d14dae8953f5cc8ad7952e8235b10d9c886a4fe7b07fb1c4302bbaca1d5e0076772800db757c040359204b51bd528ed6d86611a4dd38"
      },
      "anomaly": [
        "filename-double-extension"
      ],
      "mime_type": "PE Unknown PE signature 0 (Control Panel Item), for MS Windows",
      "mime_type_short": "application/vnd.microsoft.portable-executable"
    }
  ],
  "body": [
    {
      "uri_hash": [
        "35ccde66a6e0749bb26440355987c3bf73b351c3d0e7cb5a3cc21c648ccb73d8",
        "9d7dc809de40ea7fb9fe30883a2dfc2351b90b9bc08316d7a39bcf3d3282de41"
      ],
      "unc_hash": [
        "201e3c9d007c6c6255482794cac37a235dc8083554f06b65e2c96d56e4aaf2e9"
      ],
      "domain_hash": [
        "25349124a554706674aebacad6d82390be22a3d44d2939b5805a41b022a33d53",
        "c9a1f844464610c0a2d7489e46e76cb8b51478a0a307818fbf9f677aa26522c7"
      ],
      "ip_hash": [
        "c5eb5a4cc76a5cdb16e79864b9ccd26c3553f0c396d0a21bafb7be71c1efcd8c"
      ],
      "content_type": "text/html",
      "hash": "aac2579a09161cb15583d1d628b11613a14a69e1589f2e72ab907cba1c6d7395",
      "boundary": "BOUND"
    }
  ],
  "header": {
    "subject": "Invoice 4711 - action required",
    "from": "billing@pay-secure.example",
    "to": [
      "victim@example.org"
    ],
    "date": "2026-10-05T07:12:40+00:00",
    "received": [
      {
        "src": "from mail.badhost.example (mail.badhost.example [203.0.113.7]) by mx.example.org (postfix) with esmtps id 4f2a31 for <victim@example.org>; mon, 05 oct 2026 09:12:44 +0200",
        "from": [
          "203.0.113.7",
          "mail.badhost.example"
        ],
        "by": [
          "mx.example.org"
        ],
        "with": "esmtps id 4f2a31",
        "for": [
          "victim@example.org"
        ],
        "date": "2026-10-05T07:12:44+00:00"
      }
    ],
    "received_domain": [
      "mx.example.org",
      "mail.badhost.example"
    ],
    "received_ip": [
      "203.0.113.7"
    ],
    "received_foremail": [
      "victim@example.org"
    ],
    "authentication": {
      "spf": [
        "fail"
      ],
      "dkim": [
        "none"
      ],
      "dmarc": [
        "fail"
      ]
    }
  }
}

With include_raw_body=True the indicators of the body are reported in clear text instead of hashes. Note that the obfuscated IP in the link is resolved and the share host is added to the domains:

{
  "uri": [
    "http://3232235777/login",
    "https://pay-secure.example/collect"
  ],
  "unc": [
    "\\\\files.evil.example\\share\\invoice.lnk"
  ],
  "domain": [
    "pay-secure.example",
    "files.evil.example"
  ],
  "ip": [
    "192.168.1.1"
  ]
}

Contributing

Please feel free to send me your comments / pull requests.

For the changelog, please see CHANGELOG.md.

Metadata

Release files for eml-parser 4.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for eml-parser 4.2.0
File Size Uploaded
eml_parser-4.2.0.tar.gz 76.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for eml-parser 4.2.0
File Interpreter ABI Platform
eml_parser-4.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 128.0 kB

Release files / eml_parser-4.2.0.tar.gz

Download URL eml_parser-4.2.0.tar.gz
Size 76.3 kB
Tags Source
SHA-256 checksum
How to use checksums
4cd3b074918a4f17a45f207ac7ba97c2651605f1ab1eb5c5dea587fed5a3106a
BLAKE2b-256 checksum
How to use checksums
3fa0f7ea578a15efac9825028fd65c72b3f3b5467e2fb14786e3a57e3472f6eb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / eml_parser-4.2.0-py3-none-any.whl

Download URL eml_parser-4.2.0-py3-none-any.whl
Size 51.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5d711d22ad4274dd647c7f9d0349258ea9417aaea2c8df69e282823fec0afebc
BLAKE2b-256 checksum
How to use checksums
1bd7a8b42484870526d3444aca0fbf40ae974bf98c9dd424548d64bac9f34324
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

4.3.0

2 release files

4.2.1

2 release files

This release

4.2.0 This release

2 release files

4.1.0

2 release files

4.0.0

2 release files

3.0.6

2 release files

3.0.5

2 release files

3.0.4

1 release file

3.0.3

1 release file

3.0.2

1 release file

3.0.1

1 release file

3.0.0

1 release file

2.0.1

1 release file

2.0.0

2 release files

1.17.4

2 release files

1.17.3

2 release files

1.17.2

2 release files

1.17.1

2 release files

1.17.0

1 release file

1.16.0

1 release file

1.15.0

1 release file

1.14.8

1 release file

1.14.7

1 release file

1.14.6

1 release file

1.14.5

1 release file

1.14.4

1 release file

1.14.3

1 release file

1.14.2

1 release file

1.14.1

1 release file

1.14.0

1 release file

1.13.0

1 release file

1.12.0

1 release file

1.11.7

1 release file

1.11.6

2 release files

1.11.5

2 release files

1.11.4

2 release files

1.11.2

2 release files

1.11.1

2 release files

1.11

2 release files

1.10

1 release file

1.9

1 release file

1.8

2 release files

1.7

2 release files

1.6

2 release files

1.5

2 release files

1.4

2 release files

1.3

2 release files

1.1

1 release file

1.0

1 release file

0.9

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page