Skip to main content
OxiMail is a single Rust binary. It speaks JMAP, SMTP, IMAP, CalDAV, CardDAV, ManageSieve, and WebSocket on its own, with no Postfix, no Dovecot, no nginx, no external fail2ban, and no separate antivirus daemon. Every traditional mail-server sidecar is collapsed into the binary as a Rust crate. This page is a map, not a manual. For per-crate depth, follow on to the Architecture Reference.

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.
Everything below runs in that one binary. There are no microservices to orchestrate.

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

CrateResponsibility
oximail-binThe single executable: CLI, bootstrap, method-registry wiring, and the glue that opens the storage backends and starts the server.
oximail-serverThe axum HTTP server: JMAP routes, the admin REST API, ACME / TLS, and rate-limit / fail2ban integration.
oximail-coreJMAP Core (RFC 8620): session, request parsing, method dispatch, blob plumbing, errors, capabilities, push, and the EventBus.

Mail surface

CrateResponsibility
oximail-mailJMAP Mail (RFC 8621): Mailbox, Email, EmailSubmission, Identity, VacationResponse, and the ingestion path.
oximail-threadFirst-class Thread type with merge and changes tracking.
oximail-labelFlat label model: labels as shareable collections rather than nested folders.
oximail-conversationA channel-agnostic conversation timeline that aggregates email and chat messages into one ordered, account-scoped view.
oximail-sieveThe in-house Sieve compiler and runtime, plus rule translation.
oximail-spamContent scoring: heuristic checks (URL shorteners, header forgery), network checks (DNSBL, rDNS), and greylisting, combined into a scored pipeline.
oximail-blobThe canonical blob read path: metadata lookup, decryption, and scheme checks.

Groupware

CrateResponsibility
oximail-calendarJMAP Calendars and JSCalendar (RFC 9553) events.
oximail-contactJMAP Contacts (RFC 9610).
oximail-taskJMAP Tasks: tasks and task lists with sharing.
oximail-fileJMAP File Storage: file nodes with sharing and share links.
oximail-sharingThe RFC 9670 Principal model, share notifications, and ACLs.
oximail-chatChat over JMAP: rooms, messages, and read receipts, delivered over WebSocket.
oximail-schedulerA generic in-process job scheduler for time-deferred work (scheduled sends, snoozed wake-ups, reminders).
oximail-link-unfurlAn SSRF-safe URL fetcher and Open Graph parser for link previews in chat and mail.

Storage and crypto

CrateResponsibility
oximail-storeThe 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-keyringAuthentication and key management: Argon2 passwords, app passwords, passkeys (WebAuthn), and the key service that arbitrates decryption.
oximail-cryptoThe cryptographic primitives: X25519 envelope encryption, AES-256-GCM symmetric operations, and key wrapping.

Transport and interop

CrateResponsibility
oximail-smtpSMTP inbound and submission, DKIM/SPF/DMARC, the spam pipeline orchestration, MTA-STS, SRS, and the backup-MX path.
oximail-imapIMAP4rev2 (RFC 9051), translated to JMAP Mail. Feature-gated legacy.
oximail-caldavCalDAV (RFC 4791), translated to JMAP Calendar. Feature-gated legacy.
oximail-carddavCardDAV (RFC 6352), translated to JMAP Contact. Feature-gated legacy.
oximail-managesieveManageSieve (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-limitA generic per-key token-bucket rate limiter, consumed by both the HTTP and SMTP layers.
oximail-distribution-groupsOrganization-wide distribution groups: aliases that expand to many recipients, with a per-organization fanout budget.

Cross-cutting

CrateResponsibility
oximail-adminThe admin surface: organization management, principal administration, account storage, and audit logging.
oximail-migrateA 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:
CrateResponsibility
oximail-parserUnified RFC 5322 parsing and building, DSN generation, and an async SMTP client. One message type flows from parse to build to send.
oximail-authEmail 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 FETCH ends up calling the same internal handlers as the equivalent JMAP method, then re-encodes the result back to the legacy wire format. The legacy feature 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 no Mutex<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_id is a mandatory parameter on every store method, and every query carries a WHERE 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, and threadId are 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 /set handler bind to the same BEGIN IMMEDIATE transaction, 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