Skip to content

rteoo/outlook-pst

v0.2.1MIT

Read, search, export, and manage local Outlook PST/OST mail.

Outlook PST/OST

Have an old .pst archive, an .ost cache, or mail in Classic Outlook? Outlook PST helps you browse folders, find messages by sender or date, and export the results. On Windows, you can also choose a mailbox and preview changes before applying them.

Use it from the command line or with Codex, Claude Code, Cursor, or OpenClaw. Archive commands work with files on your computer and need no mail-service sign-in. One plugin package includes the shared skill, Python CLI, and project artwork. This is an early release; see platform status and limitations for what has been verified.

Highlights

  • Find messages quickly: filter by folder, sender, recipient, subject, body text, date range, or attachments.
  • Read local archives: inspect PST/OST files through libpff on Windows, macOS, and Linux.
  • Choose your mailbox: list stores in Classic Outlook and select the one you want by name or data-file path.
  • Export useful files: rebuild .eml messages, export bodies and attachments into folders, or save live Outlook items as .msg.
  • Check exported-file integrity: archive exports include a CSV manifest with file sizes, MD5, and SHA-256 hashes.
  • Preview mailbox changes: move mail, edit text, mark read/unread, add categories, or move items to Deleted Items. Item changes require --apply.
  • Use it with your agent: one package for Codex, Claude Code, Cursor, and OpenClaw, with the same Python CLI and local archive access.

Download the plugin

Choose an archive from the latest release:

Installation methodDownload
Add plugin / Upload local plugin dialogStandalone plugin ZIP · SHA-256
CLI marketplace registrationMarketplace bundle ZIP · SHA-256

For a file-upload dialog, select outlook-pst-plugin.zip. It contains one outlook-pst/ directory with plugin.json, .claude-plugin/plugin.json, .codex-plugin/plugin.json, the skill, icon, and documentation. The marketplace bundle adds catalogs around that directory and is intended for extracted CLI installation; direct-upload dialogs cannot use that outer marketplace layout.

Release tags and packaged plugin versions now match. Download filenames stay the same across releases, so these links always point to the latest version.

To install from the marketplace bundle, with Python and uv already available:

Expand-Archive ./outlook-pst-marketplace.zip -DestinationPath ./outlook-pst-marketplace
cd outlook-pst-marketplace
codex plugin marketplace add .
codex plugin add outlook-pst@outlook-pst-local

For other runtimes, use the extracted outlook-pst/ directory:

  • Claude Code: claude --plugin-dir ./outlook-pst.
  • Cursor Agent CLI: cursor --plugin-dir ./outlook-pst.
  • OpenClaw: on the Gateway host, openclaw plugins install ./outlook-pst.

Read the installation guide for requirements, persistent installation, IDE setup, and verification limits. Archive dependencies still need to be available or approved before downloading. Installing the plugin in a cloud workspace does not grant access to mail files on your computer.

Quick start

You need Python 3.10+. The examples use an existing uv installation to resolve dependencies per run. Archive access needs libpff-python; live Outlook access also needs pywin32, Windows, and Classic Outlook. Review dependency downloads before running them. Help, packaging, and tests need only Python's standard library.

Clone the repository, then check the available commands:

git clone https://github.com/rteoo/outlook-pst.git
cd outlook-pst
$scriptPath = '.\skills\outlook-pst\scripts\outlook_pst.py'
python -B $scriptPath --help

Browse an archive

Replace the example path with your archive. For evidence work, use a copy taken while Outlook was closed; --via pff keeps access strictly offline.

uv run --with libpff-python python $scriptPath tree 'C:\mail\archive.pst' --via pff

Find the first 20 messages about invoices:

uv run --with libpff-python python $scriptPath list 'C:\mail\archive.pst' --via pff --subject invoice --limit 20 --format json

Read a message using an ID returned by list:

uv run --with libpff-python python $scriptPath show 'C:\mail\archive.pst' 2097188 --via pff

JSON listings contain one object per line, making them easy to pipe into other tools. On macOS/Linux, set S=./skills/outlook-pst/scripts/outlook_pst.py and use "$S" in place of $scriptPath, with your own archive paths.

Choose a mailbox or account

For mail already available in Classic Outlook, first list its attached stores:

uv run --with pywin32 python $scriptPath outlook stores

Then pass the exact displayed mailbox name to --store:

uv run --with pywin32 python $scriptPath outlook list --store 'Personal Mailbox' --folder @inbox --limit 20 --format json

--store also accepts a PST/OST filename or full path. Ambiguous matches stop with an error. Selection is by Outlook store; an email address works only if it matches that store's displayed name. There is no interactive account picker. Offline commands select the archive file directly.

Common folders have shortcuts: @inbox, @sent, @drafts, @deleted, @junk, and @outbox. Use --recursive to include subfolders. For a cross-mailbox move, --to-store selects the destination.

Export the messages you need

Choose a new or empty output directory and export matching messages as EML:

uv run --with libpff-python python $scriptPath export 'C:\mail\archive.pst' 'C:\mail\export' --via pff --subject invoice --format eml
FormatWhat you get
--format emlReconstructed mail messages with transport headers, bodies, and attachments
--format dirA folder per message containing its summary, recipients, headers, bodies, and attachments
outlook export --store S OUTLive Outlook items saved as .msg files

Archive exports write manifest.csv with the original folder path, message ID, file path, size, MD5, and SHA-256. Live .msg exports avoid filename collisions but do not generate that manifest.

Hashes check exported-file integrity. They do not authenticate a message; reconstructed EML is not a byte-identical copy. Keep the original archive when preserving evidence or checking mail signatures.

Preview changes before applying them

Start with a dry run. This example previews marking up to ten matching messages as read:

uv run --with pywin32 python $scriptPath outlook edit --store 'Personal Mailbox' --folder @inbox --subject invoice --limit 10 --mark read

Review the would: lines, including each item's EntryID. Once the exact items are approved, use their IDs and add --apply:

uv run --with pywin32 python $scriptPath outlook edit --store 'Personal Mailbox' --id 'ENTRY_ID_FROM_LIST' --mark read --apply

Repeat --id for more items. Explicit IDs bypass folder and message filters; duplicate IDs and non-mail items are rejected. Filter selections are evaluated again on every invocation, so IDs help keep approval tied to particular items.

ActionOptions
Move messagesoutlook move --to-folder 'Archive'; optional --to-store and --create
Change textoutlook edit --set-subject 'New subject' or --replace OLD NEW
Update read stateoutlook edit --mark read or --mark unread
Add a categoryoutlook edit --add-category 'Reviewed'
Move to Deleted Itemsoutlook delete; refuses items already there or in its descendants

All these commands require --store and default to a preview. Destination folders are created only after a valid, nonempty selection with --apply. Replacement previews follow command order; HTML replacements edit raw HTML, and RTF bodies are skipped with a warning to preserve formatting.

Exchange-backed changes can sync to the server and other devices. Changes are not transactional: if a later operation fails, earlier ones may remain applied. An applied: line is printed only after Outlook reports success.

Use it with your agent

One package, four agent runtimes. Choose a runtime below; the same skill and Python CLI run locally in each documented layout.

Build the checked-out version from source:

python -B skills/outlook-pst/scripts/build_plugin.py --out dist/plugin

The output contains outlook-pst/, a ZIP, and separate marketplace catalogs for Codex, Claude Code, and Cursor. The package includes this README, the icon, installation instructions, and the shared skill. OpenClaw imports it as a bundle.

RuntimePackage formatVerification status
CodexAgent Plugins manifest plus compatibility overlayInstalled and enabled in an isolated configuration
Claude CodeClaude manifest pointing to the shared skillInstalled; component inventory detected the skill
CursorPortable manifest plus Cursor metadata and logoCLI loading option confirmed; skill loading unverified
OpenClawImports the package as a compatible bundleDocumented layout; Gateway loading unverified

Install with Codex

From the source checkout, register the build output and install the plugin:

codex plugin marketplace add ./dist/plugin
codex plugin add outlook-pst@outlook-pst-local

Start a new chat and select Outlook PST/OST. The repository also includes a marketplace catalog, so codex plugin marketplace add . can register the checkout without building. Register one source under the outlook-pst-local name.

Try with Claude Code

Load the package for one session:

claude --plugin-dir ./dist/plugin/outlook-pst

Invoke /outlook-pst:outlook-pst. For persistent installation, follow the Claude Code instructions.

Use Cursor or OpenClaw

Cursor's Agent CLI accepts cursor --plugin-dir ./dist/plugin/outlook-pst. For the Cursor IDE, follow the local installation steps. On your OpenClaw Gateway host, install the unpacked package with openclaw plugins install ./outlook-pst, then inspect it with openclaw plugins inspect outlook-pst. See the OpenClaw guide for detection and session details.

See the installation guide for full commands, reload steps, format details, and verification limits. Installer commands change local runtime configuration; building the package does not install anything. The plugin requires an execution host with access to your archives. Installing it in a cloud session does not grant access to files on your computer.

Try prompts such as:

Find messages about invoices in my local PST archive from January onward.

Export the matching messages to my chosen local folder with a hash manifest.

Show me a preview of marking these Outlook messages as read.

The builder includes only allowlisted files and refuses nonempty output directories. No mail, exports, credentials, or dependencies are packaged.

Command reference

CommandPurpose
tree FILEBrowse folders and message counts
list FILEFind messages; output table, JSON Lines, or CSV
show FILE IDRead one message as JSON; add --html for its HTML body
export FILE OUTExport selected archive messages and a hash manifest
outlook stores / outlook list --store SDiscover mailboxes and list live messages
outlook move / edit / deletePreview or apply changes to selected MailItems
outlook export --store S OUTSave live items as .msg
outlook attach PST / detach PSTAttach or detach a PST from the Outlook profile
outlook openLaunch Classic Outlook; supports --profile, --select, --msg, and --safe

Shared filters: --from, --to, --subject, --text, --since, --until, --has-attachments, and positive --limit. Dates use local calendar days. For archive commands, --folder matches part of a path; for live commands, it selects an exact folder path or one of the shortcuts above.

Data safety and privacy

Mail and exports stay in the local destinations you choose; the CLI does not send messages or upload mail. Keep exported content private. HTML and attachments can contain active content, which the CLI does not render or execute.

--via auto first tries libpff and, when locked, can read a store already attached to Outlook. --via outlook may temporarily attach a PST; attachment can modify the archive. Attaching, detaching, and creating PSTs require separate approval. Use --via pff for strict offline evidence access.

Human-facing output escapes terminal controls. CSV presentation fields neutralize common spreadsheet formula prefixes; JSON and exported evidence preserve their values. Warnings return exit code 1 and mean results may be incomplete. Report them before relying on an export. See SECURITY.md.

Platform status and limitations

ModeRequirements
Archive reads and exportsWindows, macOS, or Linux; Python 3.10+ and libpff-python
Live Outlook and locked-file accessWindows, pywin32, and Classic Outlook; New Outlook has no compatible COM API
Help, tests, and plugin packagingPython 3.10+ standard library

Tests use synthetic libpff and COM backends. CI covers Windows, Linux, and macOS on Python 3.10 and 3.14. Native libpff loading and invalid-file rejection were checked on Windows; successful real-archive reads, live Outlook changes, and agent session execution remain unverified. Codex and Claude marketplace registration and installation passed in isolated temporary configuration directories; Claude detected the skill, and Codex listed it as installed and enabled. See plugin verification status and the review.

Large live mailboxes can be slow to scan. MIME assembly holds attachments in memory, and RTF extraction is intentionally focused rather than a full RTF interpreter. Malformed bodies and unreadable attachments produce warnings while later readable items continue. Rebuilt EML currently omits inline attachment Content-ID/related MIME metadata, and sanitized folder names can share an output directory; original paths remain in the manifest.

Develop and build

Source and tests live under skills/outlook-pst. From a source checkout, run:

python -B -W error::ResourceWarning -m unittest discover -s skills/outlook-pst/tests -v
python -m ruff check --no-cache skills/outlook-pst
python -B skills/outlook-pst/scripts/build_plugin.py --out dist/plugin

Ruff is optional and must already be installed. Tests use temporary directories that clean up on failure and never access a mailbox. Native and live validation require a separately approved scratch archive.

License

Outlook PST is released under the MIT License.