Build1 publisher3 min readPublished
University team runs 10,000 Stalwart mailboxes on PostgreSQL and S3 storage
Operators running Stalwart for a 10,000-mailbox university keep settings in PostgreSQL and messages in S3 storage, so no mailbox sits on one machine's disk. Their mailcow and Mailu verdicts come from documentation, where failover means a cold standby or a shared ReadWriteMany volume.
The Engineer · Build desk

What happened
- A team running Stalwart for a 10,000-mailbox university compared it with mailcow and Mailu, and says its sections on the other two are a reading of their official documentation.
- The team keeps Stalwart's metadata and settings in PostgreSQL and message blobs in MinIO, an S3-compatible object store, so no mailbox depends on one machine's local disk.
- mailcow documents a single-host Compose stack whose redundancy is a cold standby: an rsync copy on a second machine that operators switch to when the first dies.
- Mailu's Helm chart can run its front end as replicas or a DaemonSet, but spanning nodes on the default single volume needs a ReadWriteMany storage class.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint At 10,000 users, a mailcow host failure ends in a switch to the cold copy; the team expects that switch to be an outage people must be scheduled around.
- decision Picking Mailu for multi-node service first means picking storage with ReadWriteMany access, since the team rates that layer at least as important to HA as Mailu itself.
- exposure With every Stalwart setting in PostgreSQL since v0.16, that database's backup and replication now protect the whole server configuration as well as mailbox metadata.
A Stalwart 0.16 node keeps one small JSON file on local disk, and that file says only how to reach the datastore [14]. Listeners, domains, DKIM and spam settings are objects in the database, managed through the web admin or the management API [14]. In the university setup that database is PostgreSQL, with message blobs in MinIO [12]. I'd expect a replacement node to need that JSON file and a network path to both stores, and little else. Most Stalwart tutorials online predate 0.16 and walk through a big config.toml that no longer exists [14].
Bulk provisioning would also go through that management API. The write-up says that at this size the questions become "what happens when one node dies" and "how do I provision ten thousand accounts without clicking ten thousand times" [3]. Mailu has a REST API on its admin side [8]. mailcow builds mailbox migration into its admin UI as sync jobs that run imapsync [7]. The available excerpt of the write-up ends before it describes a failover drill or the tooling the team used to create its accounts.
mailcow's requirements page is unusually candid about memory. It asks for at least 6 GiB of RAM plus 1 GiB of swap [4]. One SOGo worker can use around 350 MiB, and a company with 15 ActiveSync phones and about 50 concurrent IMAP connections should plan for 16 GiB [4]. Fifty sessions is 0.5 percent of 10,000 mailboxes [1]. Anyone sizing a campus from that page is extrapolating far past its example. The memory buys webmail, calendars, contacts and Exchange ActiveSync through SOGo out of the box [7]. The docs also rule out LXC, OpenVZ and Virtuozzo hosting [6].
Mailu asks for 1 GB of RAM and 1 GB of swap without antivirus, or 3 GB with ClamAV, and ships under the MIT license [8]. Its Kubernetes route is a Helm chart that can run the front end as several replicas or as a DaemonSet [9]. Mailu's own docs point to that chart and note that they are looking for maintainers for it [9].
The team left IceWarp for Stalwart for three stated reasons: JMAP, external storage, and a clustering path that does not require an enterprise contract [11]. Stalwart is a single Rust binary that implements the mail and groupware protocols itself, instead of wiring Postfix to Dovecot [10]. The Community edition is AGPL-3.0, and Enterprise is commercially licensed [10].
I think the storage split is the right choice at 10,000 mailboxes. The team's reason holds: PostgreSQL gets backed up and replicated like any other database, and the blob store scales on its own [12]. That judgment rests on one operator's deployment at one university [1].
What to watch
- A published failover drill or recovery time from the university's Stalwart cluster would move the node-failure case from design to field data.
- Whether Mailu finds maintainers for its Helm chart, the Kubernetes route its own docs point to.
- A supported multi-node or active-active mode in mailcow's documentation would remove the cold-standby outage at this scale.