System architecture

Stalwart 1.0 mail server: what's new, the benchmarks, and how to upgrade from 0.16

Stalwart 1.0 is out: what's new, how its benchmarks against Postfix and Dovecot hold up, and how to migrate from 0.16 with stalwart-migrate.

Kemo
9 min read
A wall of old numbered metal post office boxes in rows, in black and white.
On this page
  1. What is new in Stalwart 1.0?
  2. How fast is Stalwart compared with Postfix and Dovecot?
  3. How to run Stalwart 1.0 in Docker
  4. Why upgrading from 0.16 to 1.0 needs a migration
  5. Upgrade Stalwart 0.16 to 1.0, step by step
  6. What about 0.15 or earlier, or moving from Dovecot and Postfix?
  7. Before you upgrade
  8. Questions people ask about Stalwart 1.0
  9. Sources and further reading

Stalwart 1.0, released on 26 October 2026, is the first stable version of the Rust mail and collaboration server that speaks JMAP, IMAP, POP3, SMTP, CalDAV, CardDAV and WebDAV from one binary. It finalises the database schema so later upgrades can happen in place — which is exactly why this one cannot: a 1.0 server will not open a store created by 0.16 or earlier.

This article covers what changed in 1.0, how to read the project's benchmarks against Postfix and Dovecot (including the caveats the project itself publishes), how to start a fresh 1.0 server in Docker, and the full upgrade path from 0.16 with stalwart-migrate, step by step. It is for people who run their own mail, or are deciding whether to.

Released26 October 2026 (announcement)
Language and licenceRust; AGPL-3.0 for the Community edition, with a proprietary Stalwart Enterprise License for commercial features (GitHub)
ProtocolsJMAP (with EventSource and WebSocket push), IMAP, POP3, SMTP, CalDAV, CardDAV, WebDAV
Data storesRocksDB, PostgreSQL, MySQL, FoundationDB, SQLite; Redis for in-memory; S3, filesystem or Azure Blob for blobs
Upgrade from 0.16One-time copy with stalwart-migrate, from 0.16.27 only (guide)
Stalwart 1.0 at a glance, from the release post and repository

What is new in Stalwart 1.0?

The headline work is performance and a schema the project commits to keeping. According to the release post, the parsers were rewritten with SIMD and SWAR techniques, a new encodify library handles Base64 and quoted-printable, identifiers are assigned inside the write transaction, objects are zstd-compressed with dictionaries for mail, calendar and contact data, and per-account message indexes are cached in packed form so most IMAP sorting, threading and searching needs few or no database reads.

  • Full-text search rewritten: word positions, phrase search, stemming and prefix matching. The project reports a full-text search across a 250,000-message mailbox in under 4 ms.
  • Document search: text extraction for PDF, docx, xlsx, pptx, odt, ods, odp, EPUB and RTF attachments, with a Rust extractor that enforces size and complexity limits.
  • Metadata: JMAP Object Metadata and IMAP METADATA (RFC 5464).
  • DKIM2 signatures with a post-quantum composite of ML-DSA-44 and Ed25519.
  • OAuth and OIDC: Client ID Metadata Documents, per-client scopes, a revocable list of connected apps, and opaque-token validation for external providers.
  • Operations: a built-in in-memory store for single nodes, secondary LDAP servers, a memory budget for buffered data, and SSRF protection for outbound HTTP.

An AI spam classifier supporting OpenAI-compatible, Anthropic, Gemini and Ollama models ships in the Enterprise edition only. The changelog has the full list.

How fast is Stalwart compared with Postfix and Dovecot?

In the project's own tests, faster in 35 of 38 headline comparisons. The benchmarks repository pits Stalwart 1.0 on RocksDB against Postfix 3.11.7 and Dovecot 2.4.5 on identical Azure Standard_D8ads_v7 servers (8 Zen 5 vCPUs, 32 GiB RAM, local NVMe), alternating the systems run by run and reporting medians. Scripts, configurations and raw results are published.

Stalwart 1.0 speed-up over Dovecot 2.4.5 and Postfix 3.11.7Median latency ratio, higher means Stalwart is faster; below 1 means the other server wins
IMAP login with TLS5.5×Fetch a 1 MB message6.3×Thread INBOX4.9×Move 100 messages18×Mark whole INBOX read0.5×SMTP receive 4 KB12×SMTP relay3.6×Search 250k-message mailbox36×

Source: [stalwartlabs/benchmarks README](https://github.com/stalwartlabs/benchmarks); 0.53 is Dovecot's 1.9× win shown as a ratio

Stalwart 1.0 speed-up over Dovecot 2.4.5 and Postfix 3.11.7
Speed-up
IMAP login with TLS5.5×
Fetch a 1 MB message6.3×
Thread INBOX4.9×
Move 100 messages18×
Mark whole INBOX read0.5×
SMTP receive 4 KB12×
SMTP relay3.6×
Search 250k-message mailbox36×
TestStalwart 1.0Postfix or DovecotRatio
Inbound SMTP messages per second5,445723.17.5×
Relayed messages per second1,984516.53.8×
Mixed IMAP sessions per second2,178405.05.4×
Sustained load before failing the 99%-within-1-second target, from the project's benchmarks

Read these as vendor benchmarks with unusually good disclosure, not as a verdict. The repository's own caveats matter:

  • Disk sync differs. Postfix fsyncs each message before accepting it; Stalwart and Dovecot, as configured, do not. That favours Stalwart on SMTP. A full-fsync Dovecot variant is published for write tests.
  • Builds differ. Stalwart was compiled for the exact CPU; Postfix and Dovecot are standard builds.
  • One server each, cloud VMs and generated message text. No clustering, and storage-backend comparisons are still being prepared.
  • Dovecot wins bulk flag changes: marking every message read is 1.9× faster on INBOX and 4.2× faster on a 250,000-message mailbox.

How to run Stalwart 1.0 in Docker

The Docker guide recommends the major tag stalwartlabs/stalwart:v1 over latest, with two named volumes: one for config.json, one for data. The admin port 8080 is bound to localhost because the first-run wizard is not protected against password guessing.

compose.yamlYAML
services:  stalwart:    image: stalwartlabs/stalwart:v1    container_name: stalwart    init: true    restart: unless-stopped    ports:      - "25:25"      - "465:465"      - "993:993"      - "995:995"      - "4190:4190"      - "443:443"      - "127.0.0.1:8080:8080"    volumes:      - stalwart-etc:/etc/stalwart      - stalwart-data:/var/lib/stalwart volumes:  stalwart-etc:  stalwart-data:
first startShell
docker compose up -d # Temporary admin credentials for the setup wizarddocker logs stalwart 2>&1 | grep -A8 'bootstrap mode' # From your machine, tunnel to the localhost-only wizardssh -L 8080:127.0.0.1:8080 your-mail-host# then open http://127.0.0.1:8080/admin and sign in as admin

A new bootstrap password is generated on every start in bootstrap mode, so only the latest log block is valid. The wizard's last screen shows the real administrator account, admin@<domain>, and its password once — it is not written to the log. Record it, then docker restart stalwart and sign in at https://<hostname>/admin.

Why upgrading from 0.16 to 1.0 needs a migration

Because 1.0 uses a new database layout and refuses the old one. Start a 1.0 binary on a 0.16 store and you get "The data store was created by a Stalwart version prior to 1.0" (upgrade guide). The project calls this the last disruptive migration; the schema is final from here.

The supported route is stalwart-migrate, which reads the 0.16 store and copies into a new, empty 1.0 store. It runs repeatedly while 0.16 keeps serving users, copying only what changed each time, so the downtime is just the final run. Account and message IDs, IMAP UIDs and change history are preserved.

Before cut-overAfter cut-overStalwart 0.16.27keeps serving0.16 storeread-only to the toolstalwart-migrateprepare · run · run --finalState dirprogress, reports1.0 storenew and emptyStalwart 1.0starts after final runincrementalreadscopy changes
  • Stalwart 0.16.27 → 0.16 store
  • 0.16 store → stalwart-migrate: incremental reads
  • stalwart-migrate → State dir
  • stalwart-migrate → 1.0 store: copy changes
  • 1.0 store → Stalwart 1.0
The 0.16 server stays live through any number of incremental runs; only the final run needs it stopped.
AreaWhat happensWhat to do
OAuth tokensNot migratedWarn users that OAuth clients will ask them to sign in again
Full-text indexNot copied; rebuilt after startExpect incomplete search results until the rebuild finishes
CalDAV, CardDAV, WebDAVEntity tags change; clients re-fetch onceExpect a one-off sync spike; sync tokens stay valid
SettingsAbout a hundred keys renamedprepare converts them; review its report
ClustersCoordinator needs a Redis-based in-memory storeProvision Redis before upgrading a cluster
External OIDC providersOAuth Profile for Open Public Clients enforcedTest sign-in; compatibility settings exist at a security cost
What changes for users and operators after the 0.16 → 1.0 migration

Upgrade Stalwart 0.16 to 1.0, step by step

These steps follow the quick migration guide for a script-installed server on RocksDB. Upgrade to 0.16.27 first if you are on an earlier 0.16 release, and make sure the host has room for a second copy of the data store.

  1. Install stalwart-migrate

    installShell
    curl --proto '=https' --tlsv1.2 -LsSf \  https://github.com/stalwartlabs/stalwart-migrate/releases/latest/download/stalwart-migrate-installer.sh \  | sudo sh

    Release archives with checksums and gh attestation verify support, and a stalwartlabs/stalwart-migrate Docker image, are listed on the installation page. Servers built without the enterprise feature need the --oss variant.

  2. Create the empty 1.0 store

    /etc/stalwart-v1/config.jsonJSON
    {  "@type": "RocksDb",  "path": "/var/lib/stalwart-v1/"}

    This file becomes the 1.0 server's configuration later. For PostgreSQL, MySQL or FoundationDB, create a new empty database or cluster instead.

  3. Prepare and review the conversion

    prepareShell
    sudo stalwart-migrate prepare --source /etc/stalwart/config.json \  --state /var/lib/stalwart-migrate

    It asks about every conversion that needs a decision. Read prepare-report.txt in the state directory: it lists every dropped object and changed setting.

  4. Copy while 0.16 runs

    incremental copyShell
    sudo stalwart-migrate run --source /etc/stalwart/config.json \  --target /etc/stalwart-v1/config.json --state /var/lib/stalwart-migrate \  --owner "$(id -u stalwart):$(id -g stalwart)" sudo stalwart-migrate status --state /var/lib/stalwart-migrate

    Repeat as often as you like; it can be interrupted and resumed. Each run copies only changes since the last.

  5. Stop 0.16 and run the final copy

    final copyShell
    sudo systemctl stop stalwart   # every 0.16 node sudo stalwart-migrate run --final --source /etc/stalwart/config.json \  --target /etc/stalwart-v1/config.json --state /var/lib/stalwart-migrate \  --owner "$(id -u stalwart):$(id -g stalwart)"

    It asks you to type yes, watches the 0.16 store for 30 seconds to confirm nothing is writing, copies the last changes, finalises the store and verifies a sample of accounts.

  6. Check the report and start 1.0

    reviewShell
    sudo stalwart-migrate report --state /var/lib/stalwart-migrate --severity error # If the window allows, compare every accountsudo stalwart-migrate verify --full --state /var/lib/stalwart-migrate

    Then keep a copy of the 0.16 configuration, copy /etc/stalwart-v1/config.json over /etc/stalwart/config.json, and run the 1.0 install script, which replaces the binary and keeps the configuration. In Docker, start stalwartlabs/stalwart:v1 with the new configuration and data volume.

Files after a migration on one host
  • etc/
    • stalwart/
      • config.json0.16 config, keep a copy
    • stalwart-v1/
      • config.jsonbecomes the 1.0 config
  • var/lib/
    • stalwart/0.16 store, keep until 1.0 has proven itself
    • stalwart-v1/new 1.0 RocksDB store
    • stalwart-migrate/
      • prepare-report.txtdropped objects, changed settings

Keep the 0.16 store and the state directory until you trust 1.0: stalwart-migrate rollback copies a 1.0 store back into a new, empty 0.16 store (rollback).

What about 0.15 or earlier, or moving from Dovecot and Postfix?

stalwart-migrate only accepts 0.16.27. For older versions, per-user moves, or migrating from Dovecot and Postfix, the project points to its migration guide: a migration proxy routes each account to the old or new server through a mapping table, and the Vandelay import/export tool moves accounts one at a time. That path is slower to run but lets you move users in batches with no flag day.

Before you upgrade

  • To do: Upgrade 0.16 to 0.16.27 and take a backup of the data store.
  • To do: Check free disk for a second copy of the data store (only the data store, if blobs are external).
  • To do: Run prepare and read prepare-report.txt before scheduling the window.
  • To do: Provision Redis first if you run a cluster.
  • To do: Test external OIDC sign-in against a 1.0 test instance.
  • To do: Tell users that OAuth clients will ask them to sign in again and search will be incomplete for a while.
  • To do: Keep the 0.16 store and state directory until 1.0 has run cleanly.

Questions people ask about Stalwart 1.0

What is Stalwart mail server?

An open-source mail and collaboration server written in Rust. One binary serves JMAP, IMAP, POP3 and SMTP, plus CalDAV, CardDAV and WebDAV for calendars, contacts and files.

Is Stalwart free?

The Community edition is AGPL-3.0. Some features, such as the AI spam classifier, are only in the Enterprise edition, which uses a proprietary licence.

Can I upgrade Stalwart 0.16 to 1.0 in place?

No. 1.0 uses a new database layout and will not open a 0.16 store. Use stalwart-migrate, which copies into a new 1.0 store while 0.16 keeps running; future versions are meant to upgrade in place.

Which 0.16 version does stalwart-migrate need?

0.16.27. Upgrade earlier 0.16 releases first; for 0.15 or older, use the migration proxy and Vandelay instead.

How much downtime does the migration need?

Only the final run. Incremental runs copy data while 0.16 serves users, and the final run copies the last changes with 0.16 stopped.

Is Stalwart faster than Dovecot and Postfix?

In the project's published benchmarks it won 35 of 38 headline comparisons, with Dovecot faster at bulk flag changes. They are vendor-run tests with documented caveats, such as Postfix fsyncing every message and Stalwart not.

Which databases does Stalwart support?

RocksDB, PostgreSQL, MySQL, FoundationDB and SQLite for data, Redis for the in-memory store, and S3, the filesystem or Azure Blob Storage for message blobs.

Do users need to do anything after the upgrade?

Users of OAuth clients must sign in again, calendar and contact clients re-fetch their data once, and search results are incomplete until the index rebuild finishes.

Sources and further reading

Discussion

Join the discussion

Sign in with Google, Apple or an email code to comment and reply.

One practical article a week

Follow k13 in any RSS reader. No account needed.