PST Query
PST Query is an offline command-line tool for searching an Outlook PST archive. It keeps a local SQLite cache with an FTS5 index so agents can locate concise message candidates before retrieving a complete message.
The PST is opened read-only and remains authoritative. The SQLite index is a
disposable cache: delete it to force a complete rebuild. A working pypff /
libpff binding is required to create or synchronize the cache.
Installation
pypff, the Python binding for libpff, is required to read PST archives. It is
a native extension and upstream does not provide a Linux wheel for Python 3.13,
so pip install pstq alone cannot create or synchronize a cache.
The supported installation is the included devcontainer. It builds the pinned, checksum-verified libpff-python source release for Python 3.13, then installs the project dependencies:
$ git clone https://github.com/JNevrly/pstq.git
$ cd pstq
Open the clone in VS Code and choose Dev Containers: Reopen in Container. After the container is ready, verify the installation with:
$ uv run pstq --help
For a manual installation, first build and install a compatible pypff wheel
for the target Python and platform, then install pstq. The devcontainer
Dockerfile is the reference build recipe.
Configuration
Configure one PST and one SQLite cache path in YAML. The directory containing
index_path must already exist because cache builds use a sibling temporary
file before atomically replacing the index.
archive:
pst_path: /archives/mail.pst
index_path: /var/cache/pstq/mail.sqlite
history:
owner_emails:
- user@example.com
owner_names:
- Example User
timezone: Europe/Prague
Pass the file to every command:
$ pstq --config pstq.yaml status
Generate a commented configuration template with:
$ pstq --get-config-template pstq.yaml
Onacol environment overrides are also supported:
$ export PSTQ_ARCHIVE__PST_PATH=/archives/mail.pst
$ export PSTQ_ARCHIVE__INDEX_PATH=/var/cache/pstq/mail.sqlite
$ pstq status --json
Workflow
Start by checking the cache and inspecting its folder paths:
$ pstq --config pstq.yaml status --json
$ pstq --config pstq.yaml folders --json
search checks the configured PST's path, size, and modification time before
querying. If the source changed, it synchronizes the cache first; otherwise it
queries immediately. Results are capped at 20 by default and at 100 maximum.
They deliberately omit message bodies.
$ pstq --config pstq.yaml search 'Capon calibration' --json
$ pstq --config pstq.yaml search --from 'Sender' --after 2025-01-01 --json
$ pstq --config pstq.yaml search 'Capon calibration' --limit 20 --offset 20 --json
$ pstq --config pstq.yaml search invoice \
--from-owner \
--to accounting@example.com \
--after 2025-01-01 \
--before 2026-01-01 \
--folder 'Top of Outlook data file/Projects' \
--has-attachment \
--limit 10 \
--json
Search supports FTS5 query syntax. Quote punctuation-heavy or exact phrases,
and prefer the --to filter for recipient email addresses. QUERY may be omitted
when one or more filters are supplied. Filter-only results are ordered by date
descending and then stable message ID, with undated messages last; their
score is 0.0 and snippet is empty.
Use --offset to request later bounded pages. It is zero-based and defaults to
0; retain the same query and filters, then increase it by the page --limit
(for example, --limit 20 --offset 20 requests the second page). Each JSON
response remains an array of at most 100 records; a response shorter than the
requested limit is the final page. The source and cache must remain unchanged
while traversing pages to preserve a stable result set.
Each result has a stable id. Search JSON includes id, date, from, to,
subject, folder, snippet, and score. Pass the selected ID to show to
retrieve the persisted message with cleaned body content by default:
$ pstq --config pstq.yaml show edc4f1c4c743ad49a590c83842fd889f:2128196 --json
$ pstq --config pstq.yaml show edc4f1c4c743ad49a590c83842fd889f:2128196 --full --json
Default show reads only SQLite and returns persisted cleaned content. show --full synchronizes when needed, then reads the current preferred body from the
configured PST, including quoted history. Recovered quoted-history records keep
their persisted derived body because they have no independent source item. The
same selection applies to human-readable and JSON output. The returned record
includes the selected body representation, body format, participants, folder,
message headers, relationship metadata, and attachment count.
If the manually curated PST omits Sent Items, configure history.owner_emails
and, where needed, exact display-name history.owner_names aliases. During
synchronization PSTQ conservatively recovers complete Outlook reply and
forwarded-message blocks in English, Czech, German, and Japanese. Those
messages participate in normal search, show, and thread results. Use
--from-owner to match any configured owner alias. Ambiguous, edited, or
incomplete quotation blocks are intentionally omitted. history.timezone must
be an IANA timezone and is applied to quoted timestamps that omit an offset.
Command Reference
| Command | Purpose |
|---|---|
status [--json] |
Report source and cache metadata plus freshness. |
folders [--json] |
List indexed folder paths and stable IDs. |
search [QUERY] [filters] [--json] |
Synchronize when needed, then run a bounded FTS5 or filter-only search. |
show MESSAGE_ID [--full] [--json] |
Retrieve cleaned content from SQLite, or use --full to read the current source body. |
thread MESSAGE_ID [--json] |
Reconstruct a related-message view from persisted cache data. |
attachments MESSAGE_ID [--json] |
List persisted attachment metadata from SQLite. |
attachment ATTACHMENT_ID --output FILE |
Synchronize if needed, then extract one original attachment through its cached PST locator. --output must name a path that does not exist; PSTQ never overwrites an existing file. |
search filters are --from, --to, --after, --before, --folder,
--has-attachment, --from-owner, --limit, and --offset. The full agent contract,
including JSON schemas, stable ID formats, error envelopes, cache access,
limits, and known libpff limitations, is maintained in the CLI itself. Read it
before invoking a command:
$ pstq --help
$ pstq search --help
Development
Run all formatting, linting, type checking, and tests with:
$ uv run tox
This project uses Backlog.md for task
tracking. The workflow is documented in AGENTS.md.
Free software: MIT license.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pstq-0.2.0.tar.gz.
File metadata
- Download URL: pstq-0.2.0.tar.gz
- Upload date:
- Size: 145.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f22fe0e220e4fd438fe2773f22471514a51f7ea2e9804d6ebe8e9236b963fa84
|
|
| MD5 |
80c22bdb658c3674bf8a6e38fb12b39d
|
|
| BLAKE2b-256 |
07828ac8c8d32953c2e9632e55eabfb128e6f9e266daf8f52770fd9f0dab3dc1
|
Provenance
The following attestation bundles were made for pstq-0.2.0.tar.gz:
Publisher:
release.yml on JNevrly/pstq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pstq-0.2.0.tar.gz -
Subject digest:
f22fe0e220e4fd438fe2773f22471514a51f7ea2e9804d6ebe8e9236b963fa84 - Sigstore transparency entry: 2697085517
- Sigstore integration time:
-
Permalink:
JNevrly/pstq@2c83587cf3553d8e1f26396d820588b2414c6f09 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/JNevrly
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2c83587cf3553d8e1f26396d820588b2414c6f09 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pstq-0.2.0-py3-none-any.whl.
File metadata
- Download URL: pstq-0.2.0-py3-none-any.whl
- Upload date:
- Size: 39.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fa753594f3f07d948e91a5eebf4cda165e5507aa30777949712638c831d98f05
|
|
| MD5 |
b8a8dcef4bc10ee62b6dc4f2379fb894
|
|
| BLAKE2b-256 |
232981cb9805d06f70baebac7fdc24db0b56257de31bdc3c720406ecc261e779
|
Provenance
The following attestation bundles were made for pstq-0.2.0-py3-none-any.whl:
Publisher:
release.yml on JNevrly/pstq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pstq-0.2.0-py3-none-any.whl -
Subject digest:
fa753594f3f07d948e91a5eebf4cda165e5507aa30777949712638c831d98f05 - Sigstore transparency entry: 2697085572
- Sigstore integration time:
-
Permalink:
JNevrly/pstq@2c83587cf3553d8e1f26396d820588b2414c6f09 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/JNevrly
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2c83587cf3553d8e1f26396d820588b2414c6f09 -
Trigger Event:
push
-
Statement type: