Version 0.52.0 · Free software under the GPL
ShardPost logo

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

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.

OIDC sign-in JMAP LMTP auth · routing TLS+PROXY gRPC librados Web Push CLIENT APP JMapper Mail iPhone · iPad · Mac JMAP + OIDC MAIL CLIENTS IMAP · POP3 ManageSieve INBOUND MAIL MTA e.g. Postfix, queues SMTP NOTIFICATIONS Web Push the client's push service, e.g. JMapper's gateway IDENTITY & POLICY Nauthilus gRPC AuthService OIDC provider · MFA token introspection LDAP · Lua policies PROTOCOL DIRECTOR nauthilus-director authenticates sessions routes by mail shard proxies to a backend IMAP · POP3 · Sieve · LMTP · JMAP MAIL SERVER ShardPost IMAP POP3 JMAP LMTP Sieve IMAPSieve replica 1same pools replica 2same pools replica nsame pools push64 partitions runnersjobs, learning STORAGE Ceph RADOS index pool replicated · omap mailboxes, entries data pool erasure coded message blobs system pool optional store-wide state a namespace per account OUTBOUND SMTP relay Sieve actions, JMAP sending TELEMETRY Monitoring Prometheus, OTLP traces, logs ADMINISTRATION shardpostctl admin API (OpenAPI) Unix socket or mTLS
JMAP and sign-in mail protocols authentication storage outbound and push

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.

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

The storage guide explains it from the basics →

a mail arrives
# 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
The blob is written before the entry that points at it, so no client ever sees an entry without data.

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 under Public/
  • 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
shardpostctl
# 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
Examples from the manual pages shardpostctl(1), shardpostctl-account(1), shardpostctl-mailbox(1) and shardpostctl-trace(1).

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
account import
# 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
From shardpostctl-migrate(1).

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 librados2 of 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

Get started →

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

Everything that is not supported →

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.

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.