Mail that lives in Ceph.
No file system. No database.
ShardPost is a mail server written in Go. It keeps mailboxes, messages and its own coordination state directly in Ceph RADOS and serves them over IMAP, JMAP, POP3, LMTP and ManageSieve. Several replicas share one store, and Nauthilus decides who gets in.
GPL-3.0-or-later · Linux, with or without Kubernetes · needs a Ceph cluster · Requirements
- Written in Go
- Ceph RADOS via librados
- IMAP4rev2 · JMAP · POP3
- LMTP · ManageSieve · Sieve
- Nauthilus authentication
- OpenAPI admin interface
The stack
Nauthilus lets them in. ShardPost serves them. Ceph keeps it.
ShardPost is the mailbox and processing layer of a mail system. Internet SMTP, authentication and storage are separate components with their own contracts. This is how they work together, from the app on a phone to the object on a disk.
Select a part of the diagram to read what it does. The layout follows the reference deployment; Nauthilus is the authentication backend, nauthilus-director the optional front door.
ShardPost
Select a part of the diagram to read what it does.
ShardPost
The mailbox and processing layer: IMAP, POP3, JMAP, LMTP and ManageSieve on one store, with Sieve during delivery and IMAPSieve on mailbox events. Several identical replicas share the same pools. Connections, parsers and caches live in the process; the durable mail state lives in RADOS, so a client that lands on another replica needs nothing but a new connection.
Background work (maintenance jobs, learning, delayed sending, expiries) runs in the servers or in job runner pods of their own. JMAP push workers share 64 partitions through RADOS leases.
Ceph RADOS
ShardPost writes through librados straight into RADOS objects, with no file system and no database in between. The index pool holds each account's mailboxes, message entries, quota and full-text index as omap and must be replicated. The data pool holds the message blobs, which are written once and never changed, so it may be erasure coded. An optional system pool keeps the small, hot store-wide state (admin store, account directory, queues and locks) on fast OSDs.
Every account is one RADOS namespace in the shared pools; there is no pool per user. Read the storage guide →
Nauthilus
Nauthilus decides every login and resolves an address to the canonical account whose namespace holds the mail. ShardPost reaches it over gRPC AuthService (or its HTTP JSON API) and passes the client address, protocol and TLS state along for Nauthilus' brute-force and policy checks. Bearer tokens are checked by OAuth 2.0 token introspection (RFC 7662).
A short-lived auth cache in front of Nauthilus answers repeated logins, and shardpostctl auth flush empties it on every server at once. For JMapper, Nauthilus is also the OpenID Connect provider. nauthilus.org →
nauthilus-director
In the reference deployment the protocol traffic enters through nauthilus-director, a mail protocol director and transparent proxy. It authenticates each session through Nauthilus, picks a healthy backend by routing facts such as the mail shard, and forwards the connection to ShardPost with the PROXY protocol, so ShardPost sees the real client address. JMAP requests take the same path.
ShardPost accepts PROXY headers only from trusted networks. nauthilus-director on GitHub →
JMapper Mail
A native mail app for iPhone, iPad and Mac from the same author, built for JMAP and OpenID Connect. It registers itself with the identity provider, signs in through the system browser with PKCE and keeps only the issued tokens.
ShardPost serves it over JMAP (RFC 8620, RFC 8621) with a bearer token that Nauthilus verifies, including shared folders, Sieve scripts, quotas and delayed sending, and, on iPhone and iPad, wakes it with Web Push when new mail arrives. jmapper.de →
IMAP, POP3 and ManageSieve clients
Any standards client works: IMAP4rev2 and IMAP4rev1 with CONDSTORE, QRESYNC, IDLE, NOTIFY, COMPRESS=DEFLATE, METADATA, ACL, SORT and THREAD; POP3 for the INBOX; ManageSieve for filter scripts. Logins use a password, PLAIN, XOAUTH2 or OAUTHBEARER, over STARTTLS or implicit TLS.
The protocol reference lists every capability, command and deviation.
Inbound mail
ShardPost does not accept internet SMTP and has no submission listener. An MTA such as Postfix receives and queues the mail and hands it over with LMTP (RFC 2033). ShardPost asks for the canonical recipient, runs the user's Sieve script and stores the message; a storage error is answered with a temporary failure, so the MTA tries again.
For local delivery there is also shardpostctl deliver, with sysexits exit codes.
Web Push
When a JMAP client registers a push subscription, the separate shardpost push worker sends its notifications to the client's push service, encrypted with RFC 8291 and signed with VAPID (RFC 8292). JMapper's push service is a gateway that forwards them to Apple's push notification service. Clients that keep a connection open can use the JMAP EventSource stream instead.
Outbound mail
Mail the server creates (Sieve redirect, vacation and notifications, and JMAP EmailSubmission) goes to a configured SMTP relay, never directly to the internet. JMAP submissions can name the user with Postfix XCLIENT, and can be held back with FUTURERELEASE (RFC 4865) for delayed sending that the client can still cancel.
Observability
Prometheus metrics on /metrics, /healthz and /readyz (which checks the store and the drain state), OpenTelemetry traces over OTLP and structured JSON logs with a session ID and the real client address. An IMAP protocol trace can be switched on for a single account.
Administration
shardpostctl talks to the admin API, an OpenAPI contract, over a Unix socket in the pod or over mutual TLS. Roles such as diagnose, provision, repair, content and export keep metadata apart from message content. It shows accounts and quotas, runs maintenance jobs, traces the IMAP protocol of an account, exports and restores accounts, and imports them from another IMAP server with their UIDs and MODSEQ kept.
Storage
Three pools. One namespace per account.
Messages and ordered mailbox metadata map well onto Ceph's object and omap operations, so ShardPost talks to RADOS directly. Index and payload have different needs and get different pools.
ceph.index_pool · replicated
Index
Each account's mailboxes, message entries with envelope and preview, quota, change log, Sieve scripts and full-text index, as omap keys. Many small keys, read and rewritten all the time: replicated, ideally on fast disks.
ceph.data_pool · erasure coded
Data
The message bytes, in chunks of 4 MiB by default. A blob is written once and never changed, so the pool can be erasure coded without overwrites. COPY adds a reference instead of copying bytes.
ceph.system_pool · optional
System
The small, hot store-wide state: admin store, account directory, queues and locks. With its own pool it sits on fast OSDs, apart from the accounts' objects.
Many servers, no central database
Every replica can change every account. Instead of a database server, ShardPost coordinates through RADOS itself, and the order of writes is chosen so that a crash never leaves a broken account.
- Compare-and-swap on object versions for every change of one object
- Intents for changes across objects, which the scrub finds without listing the pools
- Watch and notify to wake IDLE, NOTIFY and push sessions on any server
- Locks with a time to live for POP3 maildrops, the learning worker and push partitions; leases on job records
# What one LMTP delivery does, in order 1 resolve the recipient through Nauthilus 2 run the Sieve script 3 reserve quota and record a blob intent (CAS) 4 write the blob into the data pool 5 index the text 6 announce the change in the account log 7 link the entry, next UID and MODSEQ (CAS) 8 record the change, drop the intent 9 notify the watchers, answer 250
Protocols
Every client, one store.
IMAP, POP3 and JMAP read the same index, the same blobs and the same account change log. A message flagged on the phone over JMAP is flagged in the desktop client over IMAP.
IMAP with its modern extensions
ShardPost's own IMAP parser and dispatcher advertise IMAP4rev2 next to IMAP4rev1, with the extensions clients rely on for fast synchronisation, search and sharing.
- CONDSTORE and QRESYNC, IDLE and NOTIFY, COMPRESS=DEFLATE
- SORT, THREAD, ESEARCH, full-text search with English and German stemming
- ACL with shared folders under
Shared/and public folders underPublic/ - QUOTA, METADATA, SPECIAL-USE, OBJECTID, PREVIEW, IMAPSIEVE
- RFC 9051 IMAP4rev2
- RFC 3501 IMAP4rev1
- RFC 7162 CONDSTORE/QRESYNC
- RFC 2177 IDLE
- RFC 5465 NOTIFY
- RFC 4978 COMPRESS
- RFC 4314 ACL
- RFC 9208 QUOTA
- RFC 5464 METADATA
- RFC 5256 SORT/THREAD
- RFC 4731 ESEARCH
- RFC 6851 MOVE
- RFC 4315 UIDPLUS
- RFC 6154 SPECIAL-USE
- RFC 8474 OBJECTID
- RFC 8970 PREVIEW
- RFC 6785 IMAPSIEVE
- RFC 7628 OAUTHBEARER
- RFC 1939 POP3
- RFC 2033 LMTP
- RFC 5804 ManageSieve
JMAP, built in
JMAP is a third front-end on the same store, not a second data store. It covers the RFC 8621 mail model with push, sharing and Sieve, and it is what the native app JMapper Mail is built on.
- Push over EventSource and Web Push with VAPID, from workers that share 64 partitions
- Sending through your relay, with delayed sending (FUTURERELEASE) that can be cancelled
- Shared and public folders as accounts, principals and share notifications (RFC 9670)
- Quotas, vacation response, blobs and Sieve scripts over JMAP
- jmap:core
- jmap:mail
- jmap:submission
- jmap:vacationresponse
- jmap:quota
- jmap:blob
- jmap:sieve
- jmap:principals
- jmap:mail:share
- jmap:webpush-vapid
- RFC 8620
- RFC 8621
- RFC 9425
- RFC 9404
- RFC 9661
- RFC 9670
- RFC 9749
- RFC 8030
- RFC 8291
- RFC 8292
- RFC 7662
The protocol reference lists every capability, command and method, and every place where ShardPost deliberately or knowingly deviates.
Operations
Built to be run, not just started.
An admin API with an OpenAPI contract, a command-line client with roles, a manual page for every command group, metrics, traces and structured logs.
Diagnose without guessing
shardpostctl talks to the admin API over a Unix socket inside the server or over mutual TLS. Diagnosis sees metadata only; reading message content is a role of its own.
- Accounts, aliases, quotas, mailboxes and their consistency
- Maintenance jobs for repairs, recounts and deletions, also over all accounts
- An IMAP protocol trace for one account, switched on for a limited time
- One auth cache flush for every server at once
# Is an account healthy, and what does it hold? $ shardpostctl account show user@example.org $ shardpostctl mailbox list -all user@example.org $ shardpostctl check -wait user@example.org # "Is my mailbox full?" $ shardpostctl quota show user@example.org # A phone does not show new mail: trace it for half an hour $ shardpostctl trace on -for 30m user@example.org
Move in without your users noticing
shardpostctl migrate imports an account from another IMAP server through a master-user login. UIDs, UIDVALIDITY and MODSEQ stay the same, so clients keep their caches and do not download everything again.
- Uses CONDSTORE, X-GUID and SAVEDATE when the source offers them
- Copies Sieve scripts over ManageSieve and keeps Dovecot POP3 UIDLs
- Runs repeatedly until a run reports few changes
- Export to a tar archive with a SHA-256 manifest, verify, restore
# A first copy while the account still lives on the old server. # Repeat it until a run reports few changes: $ shardpostctl -timeout 10m migrate \ -source-addr mail.example.org:993 \ -master-user admin -password-file /run/secrets/master \ -sieve-addr mail.example.org:4190 \ user@example.org
And more
The parts a real mail system needs.
Sieve and IMAPSieve
A Sieve interpreter with vacation, editheader, duplicate, date, body, imap4flags and more at delivery, ManageSieve to manage scripts, and IMAPSieve on mailbox events.
Learning for spam filters
Moving mail into or out of Junk can feed a classifier such as Rspamd through an allowlisted external program, from a durable queue that survives EXPUNGE and restarts.
Full-text search
Headers, text and HTML parts, file names and attached messages, with English and German stemming and substring search, for IMAP and JMAP.
Quota with rules
Storage and message limits with grace, per-mailbox rules, warning programs at thresholds and an over-quota flag in the user database.
Shared and public folders
Folders shared by ACL, public folders with their own quota and a private \Seen flag per user, in IMAP and JMAP.
Limits per account
Configurable upper bounds for mailboxes, keywords, metadata and shares, so one account cannot grow without end.
Before you start
What ShardPost needs, and what it does not do.
ShardPost is one layer of a mail system. It expects the others to be there, and it says plainly where it stops.
What you need
- A Ceph cluster, and
librados2of the cluster's version on each server host - Linux, in containers or natively with systemd; Kubernetes and Rook are optional
- Nauthilus for authentication and the canonical account (a static backend exists for tests)
- An MTA in front that delivers over LMTP, and an SMTP relay for outgoing mail
What it does not do
- Accept internet SMTP or offer a submission port (587/465)
- Encrypt or compress messages at rest
- Contacts and calendars, JMAP over WebSocket
- Search attachment content or CJK text
Sister projects
Identity, server and client. One open stack.
ShardPost works with standard mail clients and an MTA of your choice. Its sister projects provide the sign-in and the app it was built with.

Identity & policy
Nauthilus
The authentication and policy engine with a built-in OpenID Connect provider, MFA and WebAuthn. It decides every ShardPost login and resolves addresses to accounts; nauthilus-director routes the protocol traffic.
nauthilus.org →Mail server
ShardPost
The mail server that stores mail directly in Ceph RADOS: IMAP, JMAP, POP3, LMTP and ManageSieve on one shared store. Free software under the GPL.
You are hereClient
JMapper Mail
The native mail app for iPhone, iPad and Mac. It speaks JMAP, signs in with OpenID Connect and, on iPhone and iPad, receives new-mail notifications through Web Push.
jmapper.de →All three come from Rößner Network Solutions. Using them together is optional.
Free software
Read it, run it, change it.
ShardPost is licensed under the GNU General Public License, version 3 or later. The code, the deployment files and the full documentation are in one repository.