A portable SQLite-plus-blobs store format for text-based personal-information items (mail, calendar events, contacts, notes, tasks)
This repository is the specification only, with no reference implementation. Implementations may use any language with a SQLite binding; the shared, canonical part is the schema and its migration scripts, so every implementation reads and writes the same store.
- Generic: one store for any text-based item kind (mail, calendar, contacts, notes, tasks), keyed by media type rather than one store per domain.
- Multi-account: optionally one store for several accounts too, so a merged view across all of them is a query rather than a fan-out. The account groups collections and partitions nothing: where one identity or one body occurs across collections and accounts is a fact the store reports, and what that means (a mail list showing every placement, a contact view offering to merge them) is the interface's to decide.
- Scalable and indexed: hundreds of thousands of items with real secondary indexes, not a file open per item.
- Portable: one SQLite file, byte-identical across every OS and architecture, with none of the case-sensitivity, forbidden-character or path-length pitfalls of file-per-item layouts.
- Transactional: a whole flag-set change or a multi-item move is one atomic commit a reader never catches half-done.
- Deduplicated: bodies are stored once by content hash, so a message filed in two mailboxes costs a single copy.
- Retentive: an item the last source dropped is retained rather than erased, so an expunge upstream never destroys the local copy; purging is explicit, and restoring one costs no network.
- Rebuildable: the database is a derived index over the authoritative bodies and the remote, so corruption is survivable by re-sync.
The complete, normative specification is SPEC.md, written to RFC 2119. A pimdir store is a SQLite database (the queryable index and mutable state) plus a content-addressed blob directory (the item bodies, each stored as an immutable blob): it keeps the scale, indexing and cross-OS uniformity of SQLite while keeping large bodies beside the database rather than inside it. A blob is never rewritten; editing a mutable-content item (a CardDAV contact, a CalDAV event) writes a new blob and repoints the item at it, and the old blob is collected once unreferenced. The canonical schema and its forward-only migration scripts live under migrations/, so every implementation converges on the same on-disk store.
Two properties are worth knowing before reading further. Removal is a soft delete: when the last source that held an item drops it, the store retains the row (keeping its body pinned) and hides it from the sync seam and from the live reads, so nothing re-derives it and nothing loses it; only an explicit purge, by id or by cutoff, truly deletes. And the action queue is the write door for every process that does not own the store: a producer appends a kind plus a versioned JSON payload, the owner applies it in append order, and an owner that does not recognise a kind, or cannot perform it, skips the row rather than parking it, so one queue carries store mutations any owner can apply beside intents only a particular tool can carry out.
SPEC.md the store specification (normative, RFC 2119)
migrations/ canonical, forward-only schema migrations (SQL)
0001_init.sql schema version 1
queries/ the reference statements, one file per concern
README.md this file
LICENSE-MIT dual license
LICENSE-APACHE
Draft. Schema version 1 is defined and stable in shape; there is no conformance suite yet. While the spec is draft, version 1 is edited in place rather than superseded: a schema change folds into 0001_init.sql, the version stays 1, and a store created by an earlier draft is recreated rather than migrated. Once the spec leaves draft, breaking changes bump the schema version and ship as a new migration script.
This project is developed with AI assistance. This section documents how, so users and downstream packagers can make informed decisions.
- Tools: Claude Code (Anthropic), invoked locally with a persistent project-scoped memory and a small set of repo-specific rules.
- Used for: Refactors, mechanical multi-file edits, boilerplate (feature gates, error enums, derive macros, trait impls), test scaffolding, doc polish, exploratory design conversations.
- Not used for: Engineering, critical code, git manipulation (commit, merge, rebase…), real-world tests.
- Verification: Every AI-assisted change is read, compiled, tested, and formatted before commit. Behavioural correctness is verified against the relevant spec, not assumed from the model output. Tests are never adjusted to fit AI-generated code; the code is adjusted to fit correct behaviour.
- Limitations: AI models occasionally produce code that compiles and passes tests but is subtly wrong. The verification workflow catches most of this; it does not catch all of it. Bug reports are welcome and taken seriously.
- Last reviewed: 02/08/2026
This project is dual-licensed under the MIT and Apache-2.0 licenses.
- Chat on Matrix
- News on Mastodon or RSS
- Mail at pimalaya.org@posteo.net
Contributions are welcome: start with CONTRIBUTING.md, which opens with the Pimalaya-wide guides to read first.
Special thanks to the NLnet foundation and the European Commission that have been financially supporting the project for years:
- 2022 → 2023: NGI Assure
- 2023 → 2024: NGI Zero Entrust
- 2024 → 2026: NGI Zero Core
- 2027 in preparation…
If you appreciate the project, feel free to donate using one of the following providers: