The single-binary model
When you run OxiMail you get one process that hosts:- One axum HTTP server that serves the JMAP API, the admin API, push endpoints, and the ACME / TLS plumbing.
- One SMTP component with two distinct listeners: port 25 for inbound mail and port 587 for authenticated submission. They are separate code paths on purpose, because submission validates the sender against the authenticated user’s identities and inbound does not.
- One storage stack: a SQLite database per organization (a tenant in the storage model), a Tantivy full-text index, and a content-addressed filesystem blob store.
The crates, by layer
OxiMail is a Cargo workspace of 30 AGPL-3.0-or-later crates, plus two in-house path-dependency crates that live alongside it. Crates are grouped by the architectural layer they own, not alphabetically.Binary entry and HTTP dispatch
| Crate | Responsibility |
|---|---|
oximail-bin | The single executable: CLI, bootstrap, method-registry wiring, and the glue that opens the storage backends and starts the server. |
oximail-server | The axum HTTP server: JMAP routes, the admin REST API, ACME / TLS, and rate-limit / fail2ban integration. |
oximail-core | JMAP Core (RFC 8620): session, request parsing, method dispatch, blob plumbing, errors, capabilities, push, and the EventBus. |
Mail surface
| Crate | Responsibility |
|---|---|
oximail-mail | JMAP Mail (RFC 8621): Mailbox, Email, EmailSubmission, Identity, VacationResponse, and the ingestion path. |
oximail-thread | First-class Thread type with merge and changes tracking. |
oximail-label | Flat label model: labels as shareable collections rather than nested folders. |
oximail-conversation | A channel-agnostic conversation timeline that aggregates email and chat messages into one ordered, account-scoped view. |
oximail-sieve | The in-house Sieve compiler and runtime, plus rule translation. |
oximail-spam | Content scoring: heuristic checks (URL shorteners, header forgery), network checks (DNSBL, rDNS), and greylisting, combined into a scored pipeline. |
oximail-blob | The canonical blob read path: metadata lookup, decryption, and scheme checks. |
Groupware
| Crate | Responsibility |
|---|---|
oximail-calendar | JMAP Calendars and JSCalendar (RFC 9553) events. |
oximail-contact | JMAP Contacts (RFC 9610). |
oximail-task | JMAP Tasks: tasks and task lists with sharing. |
oximail-file | JMAP File Storage: file nodes with sharing and share links. |
oximail-sharing | The RFC 9670 Principal model, share notifications, and ACLs. |
oximail-chat | Chat over JMAP: rooms, messages, and read receipts, delivered over WebSocket. |
oximail-scheduler | A generic in-process job scheduler for time-deferred work (scheduled sends, snoozed wake-ups, reminders). |
oximail-link-unfurl | An SSRF-safe URL fetcher and Open Graph parser for link previews in chat and mail. |
Storage and crypto
| Crate | Responsibility |
|---|---|
oximail-store | The storage core: the SqlBackend trait, the SQLite backend with an r2d2 connection pool, the Tantivy index, the filesystem blob store, and the versioned migrations. |
oximail-keyring | Authentication and key management: Argon2 passwords, app passwords, passkeys (WebAuthn), and the key service that arbitrates decryption. |
oximail-crypto | The cryptographic primitives: X25519 envelope encryption, AES-256-GCM symmetric operations, and key wrapping. |
Transport and interop
| Crate | Responsibility |
|---|---|
oximail-smtp | SMTP inbound and submission, DKIM/SPF/DMARC, the spam pipeline orchestration, MTA-STS, SRS, and the backup-MX path. |
oximail-imap | IMAP4rev2 (RFC 9051), translated to JMAP Mail. Feature-gated legacy. |
oximail-caldav | CalDAV (RFC 4791), translated to JMAP Calendar. Feature-gated legacy. |
oximail-carddav | CardDAV (RFC 6352), translated to JMAP Contact. Feature-gated legacy. |
oximail-managesieve | ManageSieve (RFC 5804), read-only: it exposes the account’s rules as Sieve text but rejects script mutations, since clients manage rules through JMAP. Feature-gated legacy. |
oximail-rate-limit | A generic per-key token-bucket rate limiter, consumed by both the HTTP and SMTP layers. |
oximail-distribution-groups | Organization-wide distribution groups: aliases that expand to many recipients, with a per-organization fanout budget. |
Cross-cutting
| Crate | Responsibility |
|---|---|
oximail-admin | The admin surface: organization management, principal administration, account storage, and audit logging. |
oximail-migrate | A one-shot import tool for migrating from other servers. Not part of the running production binary. |
In-house mail-protocol crates
Two crates sit beside the workspace as path dependencies. They replace the third-party mail libraries OxiMail used to depend on, so the whole mail pipeline is in-house:| Crate | Responsibility |
|---|---|
oximail-parser | Unified RFC 5322 parsing and building, DSN generation, and an async SMTP client. One message type flows from parse to build to send. |
oximail-auth | Email authentication: DKIM (sign and verify), SPF, DMARC, ARC, MTA-STS, DANE, and SRS. |
Request paths
OxiMail exposes one storage model behind several front doors.- JMAP over HTTPS. A client posts a batched request to
/jmap. The server authenticates the bearer token, parses the request, validates the advertised capabilities, dispatches each method call against the method registry, and returns one batched response (RFC 8620). - Legacy protocols translate to the same storage. IMAP, CalDAV, CardDAV, and ManageSieve are translation layers, not parallel implementations. An IMAP
FETCHends up calling the same internal handlers as the equivalent JMAP method, then re-encodes the result back to the legacy wire format. Thelegacyfeature gate controls whether they are compiled in. - SMTP inbound and submission. Inbound mail on port 25 runs DKIM/SPF/DMARC verification and the spam pipeline, then is delivered through the same ingestion path that writes both the raw and structured forms of a message. Authenticated submission on port 587 is a separate path that validates the sender.
- Push. Clients receive state changes over Server-Sent Events (EventSource) or WebSocket, so a client knows what changed without polling.
The storage model
A few invariants shape every read and write.- One backend trait, pooled connections. Every store is built on
Arc<dyn SqlBackend>with an r2d2 connection pool. There is noMutex<Connection>anywhere. A store never embeds a connection; it borrows one from the pool for the duration of a request. - Organization isolation in the storage layer.
tenant_idis a mandatory parameter on every store method, and every query carries aWHERE tenant_id = ?clause. A bug in a handler cannot physically reach another organization’s rows. This is defense in depth, separate from access control. - Raw and structured email, stored once. At ingestion a message is parsed a single time. The raw RFC 5322 bytes go to the blob store and the parsed structure goes to the database. Derived properties such as
hasAttachment,preview, andthreadIdare computed at ingestion and stored, never re-derived on read. - Content-hash blob dedup. A blob is addressed by the SHA-256 of its content, so two identical attachments share one blob. Two emails that happen to share a Message-ID are still always two separate objects pointing at the same blob; deduplication never happens at the message level.
- Transactional
/set. All writes in a/sethandler bind to the sameBEGIN IMMEDIATEtransaction, so creates, updates, destroys, and change records either all land or none do.
The EventBus
Domain crates do not import each other. The mail crate never reaches into the calendar crate, and so on. Instead they communicate through an EventBus: one crate emits a domain event after a successful write, and any interested crate subscribes to it. For example, when an inbound message carries a calendar invitation, the mail side emits an event; the calendar side, subscribed to that event, creates or updates the calendar object. This keeps the crates decoupled and makes cross-domain behavior explicit and testable.Where to go next
- Architecture Reference for per-crate detail.
- Core concepts for the data model and terminology.