Skip to main content

Crate mailrs_mailbox

Crate mailrs_mailbox 

Source
Expand description

§mailrs-mailbox

Crates.io docs.rs License Downloads

Mailbox-metadata storage for Rust mail servers — the IMAP/JMAP-shaped abstraction every project building an inbox needs, plus a PostgreSQL reference implementation. Extracted from mailrs so any IMAP, JMAP, or chat-style mail UI can lean on the same battle-tested store.

This is, at the time of writing, the only standalone server-side mailbox-metadata library on crates.io: a portable trait covering mailbox CRUD, message storage, IMAP CONDSTORE flag ops, threading, and JMAP-shape change tracking — plus an in-memory fixture that doubles as a test harness and as proof the trait is genuinely abstract.

§Highlights

  • Trait-first — code against MailboxStore, a 24-method async trait covering the IMAP and JMAP intersection. Swap the backend without changing handler code.
  • Two reference implementations includedpg::PgMailboxStore (PostgreSQL, the production-tested one) and fixtures::InMemoryMailboxStore (in-process, for tests and the trait-conformance smell test).
  • CONDSTORE built-in — per-message modseq, store_flags_if_unchanged compare-and-swap, messages_changed_since for IMAP CHANGEDSINCE and JMAP Email/changes.
  • Threading helpers — pure-function extract_message_id / extract_in_reply_to / normalize_message_id / resolve_thread_id in threading, no I/O.
  • Flag bitmask interopFLAG_* constants matching mailrs-maildir plus maildir_flags_to_bitmask / bitmask_to_maildir_flags if you’re pairing with filesystem delivery.

§Two-tier API: portable trait vs PG-EXT inherent

The crate intentionally exposes two surfaces:

  • MailboxStore trait — 24 methods rooted in IMAP / JMAP primitives. This is the portable contract; downstream consumers should program against &dyn MailboxStore. Trait methods return store-agnostic types (Mailbox, Message, MailboxStatus, Inserted, etc).

  • PgMailboxStore inherent methods — the PostgreSQL implementation carries additional methods for content projections, contact-tracking, semantic search via pgvector, thread-level UI state (pin / archive / snooze), and similar product-shape concerns from the parent mailrs project. These methods are public so mailrs can consume them, but documented as PG-EXT — they are NOT part of the trait contract and should not be relied on by store-agnostic code.

The split is the cleanest expression of “open source isn’t just stripping the pub keyword off internal code”. The trait covers what mail-server projects actually share. The PG-EXT methods carry mailrs’s specific product surface without contaminating the abstraction.

§Methods covered (1.0)

The MailboxStore trait covers 24 operations grouped by concern:

GroupMethodsPurpose
Mailbox CRUD7 (create_mailbox, delete_mailbox, rename_mailbox, list_mailboxes, get_mailbox, get_mailbox_by_id, mailbox_status)IMAP CREATE/DELETE/LIST/RENAME/STATUS; JMAP Mailbox/{get,set,query}
Message CRUD8 (insert_message, get_message_by_uid, get_message, find_by_message_id, copy_message, move_message, expunge, messages_changed_since)IMAP APPEND/FETCH/COPY/MOVE/EXPUNGE/CHANGEDSINCE; JMAP Email/{get,set,changes}
Flags + CONDSTORE4 (set_flags, add_flags, remove_flags, store_flags_if_unchanged)IMAP STORE / STORE.SILENT / UNCHANGEDSINCE compare-and-swap (RFC 7162)
Threading3 (thread_id_for_message, thread_message_ids, thread_references)JMAP Thread/get; ancestry walk for inReplyToId display
Query1 (query_messages)JMAP Email/query-shape filter: mailbox + text + has_keyword + not_keyword + pagination
Quota1 (user_storage_bytes)per-user byte sum

Plus pure helpers in threading (Message-ID parsing, thread resolution) and bitmask conversions in types.

§Quick start

use mailrs_mailbox::{MailboxStore, PgMailboxStore};

let pool = sqlx::PgPool::connect("postgres://localhost/mailrs").await?;
let store = PgMailboxStore::new(pool);

let mb = MailboxStore::create_mailbox(&store, "alice@example.com", "INBOX").await?;
let status = MailboxStore::mailbox_status(&store, mb.id).await?;
println!("INBOX: {} total, {} unread", status.total, status.unread);

For testing without a database, use InMemoryMailboxStore:

use mailrs_mailbox::fixtures::{InMemoryMailboxStore, EXAMPLE_USER};
use mailrs_mailbox::MailboxStore;

let store = InMemoryMailboxStore::new();
let inbox = store.create_mailbox(EXAMPLE_USER, "INBOX").await.unwrap();
assert_eq!(inbox.name, "INBOX");

§Schema (PG impl)

The PG reference impl expects the mailrs PostgreSQL schema. The authoritative DDL lives at scripts/init-schema.sql in the mailrs repo; the minimum tables PgMailboxStore reads from are mailboxes and messages. PG-EXT methods additionally touch email_analysis, contacts, sender_feedback, snoozed_conversations.

sqlx is used in runtime-query mode (sqlx::query / query_as), not compile-time-checked macros. No DATABASE_URL needed at build time.

If you want a different schema, implement MailboxStore against your own schema and the trait-driven handlers using this crate just work.

§Tested

1.0.0 ships 111 tests across 4 layers:

LayerCountSurface
src/*/tests (inline)67Pure helpers (threading, flag bitmask conversion, type roundtrips)
tests/trait_contract.rs35Every trait method against InMemoryMailboxStore — the portable contract suite
tests/smoke.rs5PG-specific behaviour against a real Postgres 18 + pgvector container (via testcontainers) — schema application, modseq atomicity, sqlx integration
tests/perf_gate.rs4Threading-helper regression budgets (see BUDGETS.md)

Total density: ~31 tests/kloc, in the same band as the published mailrs-jmap (28) and mailrs-dav (34).

Run the portable suite (no Docker needed):

cargo test -p mailrs-mailbox --test trait_contract

Run the PG suite (needs Docker):

cargo test -p mailrs-mailbox --test smoke -- --test-threads=1

§Performance

Two bench files cover this crate:

  • benches/threading.rs — pure-helper microbenchmarks (header extraction, message-id normalization, thread resolution).
  • benches/store_ops.rsInMemoryMailboxStore ops, exercising the trait dispatch + RwLock + Vec backing.

Measured with criterion 0.8 on Apple Silicon (M-series), cargo bench, release profile.

OperationMedianNotes
extract_message_id(short header)~150 nstypical 6-header message
extract_message_id(15-line marketing header)~470 nsscans the full header block
extract_in_reply_to(short header)~180 nsearly-exit when missing
extract_in_reply_to(15-line marketing header)~485 nsscans for the In-Reply-To: line
normalize_message_id(" <abc-123@…> ")~8 nstrim + lowercase
resolve_thread_id(<new root>)~16 nsno parent lookup
resolve_thread_id(<known parent>)~14 nswith parent-lookup closure
insert_message (first, empty mailbox)~1.2 µsmetadata write through RwLock + 12 string allocations
query_messages (mailbox scope, paginate first 50 of 1k)~115 µsclone + sort_unstable across 1000 messages; the PG impl uses SQL ORDER BY + LIMIT
query_messages (text substring match on 1k)~120 µsthree case-insensitive substring scans per message
add_flags (hot path)~55 nsone Vec lookup + flag OR + modseq bump
store_flags_if_unchanged (CONDSTORE)~57 nscompare-and-swap, same cost as add_flags
mailbox_status(1k messages)~520 nstotal / unread / recent counts

Run with cargo bench -p mailrs-mailbox. The query_messages and insert_into_1k_mailbox numbers are dev-fixture cost — the PG impl pushes the work into the database and is not benched here (its cost is dominated by network + planner latency).

See BUDGETS.md for the regression budgets gated by tests/perf_gate.rs.

§Versioning

1.x follows semver. The stable public surface:

  • MailboxStore trait method signatures
  • StoreError type alias
  • All types in mailrs_mailbox::types (marked #[non_exhaustive] where growth is anticipated, so new fields are minor-bump compatible)
  • pg::PgMailboxStore::new + pool accessor
  • Pure helpers in threading::* and the FLAG_* constants

The set of inherent PG-EXT methods on PgMailboxStore may grow or re-shape within 1.x to track the parent mailrs project’s needs. Trait-first code is unaffected.

§License

Licensed under either Apache License, Version 2.0 or MIT license at your option.

Re-exports§

pub use store::MailboxStore;
pub use store::StoreError;
pub use types::bitmask_to_maildir_flags;
pub use types::maildir_flags_to_bitmask;
pub use types::ConversationSummary;
pub use types::EmailAnalysisRow;
pub use types::FlagAction;
pub use types::FlagOp;
pub use types::InsertMessage;
pub use types::Inserted;
pub use types::Mailbox;
pub use types::MailboxStatus;
pub use types::Message;
pub use types::MessageMeta;
pub use types::QueryFilter;
pub use types::FLAG_ANSWERED;
pub use types::FLAG_DELETED;
pub use types::FLAG_DRAFT;
pub use types::FLAG_FLAGGED;
pub use types::FLAG_RECENT;
pub use types::FLAG_SEEN;
pub use pg::EmailAnalysisInput;
pub use pg::PgMailboxStore;

Modules§

fixtures
In-memory MailboxStore implementation suitable for tests, examples, and downstream-consumer test harnesses.
pg
PostgreSQL reference implementation of MailboxStore.
store
Portable MailboxStore trait — the IMAP/JMAP-shaped abstraction backend implementations must satisfy. The MailboxStore trait — the abstraction every mailbox metadata backend implements.
threading
Pure-function helpers for message-ID parsing and thread resolution. No I/O; safe to call from hot paths.
types
Store-agnostic data types (Mailbox, Message, Inserted…) and the FLAG_* bitmask constants shared across every backend.

Type Aliases§

LegacyMailboxStoreDeprecated
Back-compat alias for the legacy struct name. Prefer PgMailboxStore in new code. Will be removed in 2.0.