# extract_msg pulls email bodies and attachments out of Outlook .msg files

> extract_msg is a GPL-3.0 Python package and command line tool that reads Microsoft's Outlook .msg format directly, exposing headers, bodies and attachments to scripts and to a wide set of command line options. It supports Python 3.8 and above, and its own README is currently being rewritten in favour of the built in help text.

**TeamMsgExtractor/msg-extractor** — Extracts emails and attachments saved in Microsoft Outlook's .msg files

- Repository: https://github.com/TeamMsgExtractor/msg-extractor
- Stars: 855 · Forks: 185
- Language: Python
- License: GPL-3.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/teammsgextractor-msg-extractor

## Reading the OLE2 container directly, without Outlook

The problem this project solves is narrow and real. Outlook saves a single message, with its headers, its body in one or more formats, and every attachment, into a .msg file. That file is not a plain text format. It is an OLE2 container, also called Structured Storage, Compound File Binary Format or Compound Document File Format, which the README names in full because the format name is the first thing you have to understand before touching the code. The library uses Philippe Lagadec's Python module for reading Microsoft OLE2 files, which is the underlying format of Outlook's .msg files. The consequence for an integrator is that you do not need Outlook, a Windows machine or a COM automation stack to get at the contents. You point the library at a file, and you get structured access to the data. The extraction targets are the key email fields, from, to, cc, date, subject and body, plus the attachments, and the package automates pulling exactly those out.

## The command line writes a dated folder per message

Using it as a script is a single command:

```bash
python -m extract_msg example.msg
```

The result is a new folder named after the date, time and subject of the message, for example 2013-07-24_0915 Example, containing the email itself along with the attachments. The folder per message convention is the part worth understanding, because it is what makes the tool usable in bulk. A directory tree of dated, subject-named folders is something you can index, diff, sync to a document store or feed to another system without writing any glue code. The output location is configurable with --out, and the naming can be switched with --use-filename, which sets whether the name of each output is based on the msg filename instead of the message metadata. For scripting against a fixed target, --out-name sets the name used for the file output, and it cannot be used when saving more than one file. There is also a --zip option, which takes a path for saving to a zip file, for workflows that want one archive per batch rather than a directory tree.

## Options for body format, fallbacks and conflicting flags

The option list is long, and the README says to refer to the program's help dialog while the README itself is being redone. The help output is the current source of truth:

```bash
usage: extract_msg [-h] [--use-content-id] [--json] [--file-logging] [-v] [--log LOG] [--config CONFIGPATH] [--out OUTPATH] [--use-filename] [--dump-stdout] [--html] [--pdf] [--wk-path WKPATH] [--wk-options [WKOPTIONS ...]]
```

Several options form a decision about how the body is rendered. --html, --pdf, --raw and --rtf each select an output type and error out if that type is not possible for the message. --allow-fallback tells the program to fall back to a different save type when the selected one is not possible, and --ignore-rtfde ignores errors thrown while trying to save, which the help text says is useful for letting a fallback continue after an exception. --prepared-html prepares HTML output for embedded attachments, with --charset to set the character set used in the added tag, defaulting to utf-8. --use-content-id, with the short form --cid, saves attachments by their Content ID when they have one, which is what you need when working with an HTML body that references inline images. Several flags interact and the help text is explicit about it: --no-folders stores everything in the location given by --out, requires --attachments-only and is incompatible with --out-name, and --glob, also spelled --wildcard, interprets all paths as having wildcards and is incompatible with --out-name.

## Embedding it in your own script

The library half of the project is used in two steps. Import the module:

```python
import extract_msg
```

Then open the file:

```python
msg = extract_msg.openMsg("path/to/msg/file.msg")
```

The README also documents a second path for callers that hold the message as a binary string rather than a file, since openMsg accepts a msg binary string in place of a path, which matters when the bytes arrive over a network or out of a database. The package registers a console script entry point named extract_msg, mapped to extract_msg.__main__:main, so an installed copy gives you both the module invocation and a plain extract_msg command. The setup metadata declares python_requires >= 3.8, and the README states the library currently supports Python 3.8 and above. The two entry points are worth distinguishing when you integrate: the module import is the stable surface for code, while the command line is a wide and evolving surface that the maintainers are still documenting.

## Dependencies: a pinned OLE2 reader and an RTF engine

The dependency list is short and deliberately commented, and it shows where the hard parts live. olefile is pinned to 0.47, and the file explains that modules using == are intentional and must be checked manually before upgrading, which tells you the OLE2 layer is where upgrades have broken things before. The others are range constrained: tzlocal for time zones, compressed-rtf, ebcdic for mainframe encoded text, beautifulsoup4 for HTML handling with a note that it can sometimes make weird versioning, RTFDE for the RTF body format, and red-black-tree-mod for the tree structure. RTFDE is the component that makes the --rtf and --html paths work, and it is also the component the --ignore-rtfde option exists for, so if you plan to lean on rich body output, that library is the part to watch. Installing from the requirements file is the supported path, since setup.py reads requirements.txt at build time and passes the entries through as install_requires.

## The format research is kept in the repository

The repository carries the documentation that makes this project possible, which is unusual and useful. There is a msg-documentation/ directory, and the README explains the lineage of the implementation. The script was originally built using Peter Fiskerstrand's documentation of the .msg format, and the discussion of the different property types used within Extended MAPI from Redemption was also useful. The README adds a note for the future: Microsoft have opened up their documentation of the file format, and that is what is currently being used for development. There is also a notes/ directory and a templates/ directory, plus an example-msg-files/ directory for trying the tool out. The important structural point for an adopter is that this is a reverse engineered format with a vendor behind it, and the project tracks that upstream documentation as it changes. Code that depends on specific property types should be reviewed when the underlying format documentation moves, not just when the library releases.

## Version branches, the Python floor and the GPL terms

The NOTICE section of the README explains the branch policy, and it is dated. The 0.29 branch is the one that supports both Python 2 and Python 3, and it is now only receiving bug fixes with no feature updates. The 0.39 versions are the last that supported Python 3.6 and 3.7, and support for those was dropped to allow the use of new features from 3.8 and because the life spans of those versions had ended. Current work therefore targets Python 3.8 and above. The release list shows v0.56.0 on 2026-07-18, v0.55.0 on 2025-08-12 and v0.54.1 on 2025-04-10, and the last push to the repository was on 2026-10-09, with the repository not archived. The licence is GPL v3, with LICENSE.txt in the tree and the setup metadata declaring the same. For a library that you import into a larger application, that is the term that decides whether you can link it, so treat it as a question for your own counsel rather than an afterthought. The one documentation gap to plan around is stated in the README itself, which says it is in the process of being redone and points you to the help dialog for usage in the meantime.

## Conclusion

extract_msg suits a Python team that has to process Outlook messages in bulk, for example an archive migration, an attachment intake pipeline or a compliance export, and that would rather read the file format directly than automate the Outlook client. It is a poor fit for a project that only needs email bodies, since a .msg file is a proprietary container and plain formats are simpler to work with, and the README itself is mid rewrite. Verify first which Python version your environment has, since the library supports 3.8 and above while the 0.29 branch is the only one covering Python 2, and check that the output format you need is implemented, because the command line offers a --skip-not-implemented switch precisely because some parts of the format are not handled.

## FAQ

### How do I extract emails from an Outlook .msg file with extract_msg?

Run python -m extract_msg example.msg, which creates a folder named after the date, time and subject of the message containing the email and its attachments. In code, use extract_msg.openMsg("path/to/msg/file.msg") instead.

### Which Python versions does extract_msg support?

The library currently supports Python 3.8 and above, and the setup metadata declares python_requires >= 3.8. The 0.29 branch is the only one covering Python 2 and it now receives bug fixes only, while 0.39 was the last line supporting Python 3.6 and 3.7.

### How do I save the message body as HTML or PDF?

Use the --html or --pdf options, each of which errors out if that output type is not possible for the message. --allow-fallback makes the program fall back to another save type, and --prepared-html prepares HTML for embedded attachments.

### What does the --out-name option do and when can I not use it?

It sets the name used for the file output, and it cannot be used when saving more than one file. It is also incompatible with --glob, which interprets all paths as having wildcards, and --no-folders requires --attachments-only and is likewise incompatible with --out-name.

### What licence is extract_msg released under?

GPL v3, with the licence text in LICENSE.txt and the setup metadata declaring the same licence. The project is written by Destiny Peterson and Matthew Walker according to the setup metadata.

## Sources

- [Issues](https://github.com/TeamMsgExtractor/msg-extractor/issues)
- [License: GPL-3.0](https://github.com/TeamMsgExtractor/msg-extractor/blob/master/LICENSE)
- [README](https://github.com/TeamMsgExtractor/msg-extractor/blob/master/README.md)
- [Releases](https://github.com/TeamMsgExtractor/msg-extractor/releases)
- [TeamMsgExtractor/msg-extractor on GitHub](https://github.com/TeamMsgExtractor/msg-extractor)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/teammsgextractor-msg-extractor
