Convert an orphaned Exchange .ost file into a real, spec-valid .pst
— with no Outlook, no paid library (Aspose et al.), and no cost.
Just Python.
ostpst reads a source mailbox with libpff
(which opens both OST and PST) and writes the output PST itself, byte by
byte, following the Microsoft [MS-PST] file format.
pip install libpff-python
python -m ostpst yourfile.ost output.pstThere is no free, open-source tool that writes a valid PST. Readers are
plentiful (libpff, readpst, XstReader) — they export to EML/MBOX. But to
get an actual .pst you normally need Microsoft Outlook or the
~$1,200 Aspose.Email library. Every "free OST→PST" project on GitHub either
wraps Aspose or invents a fake format that no mail client can open.
ostpst implements the PST format directly. The reason it works where fakes
fail is a validation loop: every structure we write is immediately opened
back up with libpff — a completely independent, trusted reader. If libpff
accepts our bytes, they are genuinely spec-valid. The whole test suite is built
this way.
- Converts
.ostor.pst→.pst(libpff reads both as the source). - Preserves the full folder tree (nested folders).
- Copies each message's subject, plain-text body, HTML body, sender name, sender email, To/Cc display names, and delivery date.
- Copies attachments (filename + data), byte-exact, including multi-MB files.
- Scales: 20,000+ messages in a single folder, multi-MB message bodies and attachments, mailboxes well past the format's internal block/allocation limits — via paginated multi-level B-trees, subnode B-trees, multi-block heaps, and XBLOCK/XXBLOCK data trees.
Requires Python 3.9+ and the libpff Python bindings (used for reading the
source and for validation):
pip install libpff-python
# or, from a clone:
pip install -e .Command line:
python -m ostpst source.ost output.pst
# or, if installed:
ostpst source.ost output.pstAs a library:
from ostpst.ostreader import convert
stats = convert("source.ost", "output.pst")
print(stats) # {'folders': ..., 'messages': ..., 'attachments': ...}Build a PST from scratch (no source file):
from ostpst.messaging import PstBuilder
b = PstBuilder()
inbox = b.add_folder(b.root, "Inbox")
msg = b.add_message(inbox, subject="Hello", body="World", sender="me@example.com")
b.add_attachment(msg, "note.txt", b"file bytes")
open("out.pst", "wb").write(b.build())The PST format is a stack of layers; ostpst implements each one:
ostpst/
crc.py weak CRC-32 (poly 0xEDB88320, seeded, no in/out inversion)
primitives.py NID/BID, ComputeSig, page/block trailers, geometry
ndb.py NDB layer: header, AMap, paginated NBT/BBT B-trees,
blocks, XBLOCK/XXBLOCK data trees, subnode B-trees
ltp.py LTP layer: multi-block Heap-on-Node, BTree-on-Heap,
Property Context
tc.py LTP layer: Table Context (subnode-backed when large)
messaging.py message store, folders, messages, attachments
ostreader.py read a source OST/PST via libpff and re-emit
__main__.py CLI
tests/ regression tests, every one validated against libpff
The output targets the 64-bit (Unicode) PST format with encryption set to
none (a valid configuration). See FORMAT_NOTES.md for the
hard-won byte-layout details reverse-engineered from libpff's source and the
MS-PST spec (header offsets, dual CRC ranges, the "root folder = node whose
parent is itself" rule, TCOLDESC tag order, etc.).
- Validated against
libpff, not Microsoft Outlook. libpff is a rigorous independent reader and accepts everythingostpstproduces, and the bytes follow the MS-PST spec — but Outlook itself has not been the test oracle. Open the output in Outlook to confirm on your data. Bug reports welcome. - Recipients are carried as display strings (To/Cc names + sender email),
not as a structured recipient table with individual SMTP addresses —
libpffdoes not expose structured recipients on the source side. - Not copied: named/custom properties, categories, per-recipient address entries, and message flags beyond read state.
- AMap allocation pages are written but marked invalid (
fAMapValid=0), so Outlook rebuilds them on first open. This is normal for generated PSTs.
pip install libpff-python pytest
python -m pytest -q # all tests validate output against libpff- Reading and validation use libpff by
Joachim Metz (via the
libpff-pythonbindings). - Format details follow Microsoft's [MS-PST] Outlook Personal Folders File Format specification.
Licensed under the MIT License — see LICENSE. This project is not affiliated with or endorsed by Microsoft.