Skip to content

Benchmarks ​

Runtime benchmark of RustMail against Mailpit and MailHog on one machine. The harness lives in scripts/bench/ (bench.py, summarize.py, README.md); see that README to reproduce or extend it.

Docker comparison ​

These are the numbers on rustmail.app and in the README. RustMail v0.10.1 (ghcr.io/rustmailapp/rustmail:latest), Mailpit v1.32.0 (axllent/mailpit:latest), MailHog (mailhog/mailhog:latest, amd64 emulated), MailDev 3.0.0 (maildev/maildev:latest) and MailCrab v1.10.0 (marlonb/mailcrab:latest), each in Docker Desktop on the machine below, 100 000 small messages at 8 connections (--scenario S1-8c --http --restart); scripts/bench/README.md lists how each tool is configured. RustMail and Mailpit ran 6 times, the others 3 times, interleaved in rotating order on 2026-10-25. Each cell is the median; latencies are p50 in ms.

100 000 storedRustMailMailpitMailHogMailDevMailCrab
search rare term (10 hits)0.662031 26581no search endpoint
search common term (100 000 hits)2.953635863no search endpoint
list first page0.948.32.238no paged listing
list offset 90 0001.512.82.439no paged listing
get one0.460.380.410.380.27
disk (MB)110210memory only393memory only
idle memory, empty (MB anon)9.419.622.7106.56.6
restart to full count (s)0.300.640.51, mailbox empty240.30, mailbox empty
stored msgs/s through the port-forward2 6202 4025902 076see below
  • The load average at the start of a run ranged from 4 to 24. RustMail's sub-millisecond latencies are the most sensitive to it: its rare-term search took 0.47-0.79 ms in four runs and 3.6-4.2 ms in the two that started at load 24 and 9. The medians include every run.
  • Getting one message is faster on MailCrab (0.27 ms) and slightly faster on Mailpit and MailDev. MailCrab also idles in less memory than RustMail.
  • MailCrab accepted 5 190 msgs/s over SMTP and kept 99 940-99 975 of the 100 000 messages in each run, so its stored rate never reached the full count; its README notes that it can drop messages under heavy load.
  • MailDev reloads every stored message from its mail directory on start, which is why its restart takes 24 s and its memory after the restart is 449 MB.
  • smtp4dev 3 is supported by the harness but is not in the table: with its message cap raised to keep 100 000 messages, its ingest slowed to about 7 msgs/s after 58 000 messages, so a run could not finish in a reasonable time.
  • Through Docker Desktop's port-forward the ingest rates tie, as explained under Reading the ingest numbers. The website's ingest row (6 025 against 3 698 msgs/s) is the in-VM run from that section.

The sections below are the full single-session run of 2026-10-24 at commit c4b80da, including native RustMail.

Method ​

  • The harness prebuilds every SMTP payload before timing: small ≈ 2.1 KB multipart/alternative (text + HTML), medium ≈ 205 KB with a 150 KB random binary attachment in base64.
  • Each SMTP connection runs in its own forked process over a raw socket, one persistent connection, MAIL/RCPT/DATA without pipelining.
  • Stored throughput comes from polling each tool's API total every 250 ms until it reaches the sent count. All three tools answer 250 only after the message is stored, so the drain after SMTP finished was ≤0.03 s everywhere and the accepted and stored rates match.
  • CPU and memory were sampled every 1 s: native via ps RSS, containers via the cgroup's memory.stat anon and cpu.stat usage_usec. Native macOS RSS includes resident mmapped SQLite pages; container anon does not.
  • HTTP latency: 200 sequential keep-alive GETs per endpoint after 5 warm-ups, then DELETE all. Restart: SIGTERM/start (native) or docker restart -t 30, timed until the API reports the full count again, then idle memory after 10 s.
  • Every run used a fresh temp dir or named volume, one tool at a time, ports 9025-9226 only.
  • The S1 8-connection ingest rate is the median of 3 runs, interleaved across tools. Every other cell is a single run. Run to run, the native rate moved by about ±15% and the Docker rates by about ±20%.

Machine and versions ​

  • Mac16,8, Apple M4 Pro, 12 cores, 24 GiB RAM, macOS 27.0.1, on AC power. Loopback only. Load average 3-15 during the runs, with other apps open.
  • Docker Desktop 4.96.3 (engine 29.9.2), Linux VM with 12 vCPU and 11.7 GiB.
  • rustmail (native): commit c4b80da, built --release (fat LTO, 1 CGU, panic=abort).
  • rustmail-docker: the same commit cross-built for aarch64-unknown-linux-gnu and run in debian:bookworm-slim (--tool rustmail-docker-glibc). The published image still carries the previous release, so it does not measure this code; an earlier run put the glibc build within 5% of the musl image.
  • Mailpit v1.32.0 (axllent/mailpit, arm64), run with --max 0 --database /data/mailpit.db.
  • MailHog (mailhog/mailhog:latest, amd64-only, emulated on Apple Silicon). Stores messages in memory.

Ingest (stored msgs/s) ​

toolS1 1 connS1 8 connsS1 32 connsS2 8 conns (205 KB)
rustmail native3 84610 40314 8781 236
rustmail-docker8842 8535 235674
Mailpit (docker)6862 7792 191551
MailHog (docker, amd64 emu)–475–42
S1 8 conns, 100ktime to stored (s)peak mem during ingest (MB)CPU % avgdisk (MB)rejected / conn errors
rustmail native9.654.9 (RSS)1591100 / 0
rustmail-docker35.120.9 (anon)991090 / 0
Mailpit32.444.5 (anon)1462100 / 0
MailHog210.63 663 (anon)125memory only0 / 0

These rows are the first of the three S1 8-connection runs, the one that also measured HTTP latency and restart; the ingest table above gives the median.

Disk ​

100k small messages (S1)2 000 messages of 205 KB (S2)
rustmail 109-110 MB, Mailpit 210 MBrustmail 305-316 MB, Mailpit 400 MB

RustMail keeps each message's source once, compressed with zstd, and parses the bodies from it when a message is opened. The bench's small mail repeats its filler text, so it compresses better than typical mail does. The S2 attachments are random bytes and barely compress; the saving there comes from storing each message once.

HTTP latency with 100 000 stored (ms, p50 / p95 / p99) ​

toollist first pagelist offset 90 000search rare (10 hits)search common (100k hits)get onedelete all (s)disk after delete (MB)
rustmail native0.36 / 0.42 / 0.470.86 / 0.91 / 0.950.21 / 0.25 / 0.332.08 / 2.16 / 2.230.12 / 0.15 / 0.170.4700.2
rustmail-docker0.84 / 1.28 / 1.761.46 / 1.76 / 3.280.44 / 0.74 / 0.852.58 / 2.80 / 3.280.31 / 0.41 / 0.520.5580.2
Mailpit8.27 / 9.23 / 10.2115.1 / 16.5 / 16.9205 / 215 / 229498 / 534 / 8130.32 / 0.69 / 1.420.2490.1
MailHog2.47 / 4.69 / 5.562.36 / 4.77 / 23.61 271 / 1 401 / 1 576420 / 658 / 7590.38 / 0.45 / 0.830.001–

Both search terms returned the expected totals on every tool (10 hits for the rare term, 100 000 for the common one). Docker to Docker, RustMail's FTS5 search answers 160-2 900x faster than Mailpit and MailHog, and its first-page list is 3-10x faster. The deep page at offset 90 000 is 10x faster than Mailpit: listing reads only the message metadata, never the stored source. Getting one message parses its bodies from the compressed source, and still answers in about 0.1 ms natively.

Restart and idle memory ​

toolidle mem, empty (MB)restart to full count (s)idle mem after restart (MB)
rustmail native8.6 (RSS)0.0819.3 (RSS)
rustmail-docker8.3 (anon)0.308.1 (anon)
Mailpit20.8 (anon)0.5921.9 (anon)
MailHog24.5 (anon)0.5022.7 (anon), mailbox empty

Reading the ingest numbers ​

  • Through Docker Desktop's port-forward, RustMail and Mailpit both sit around 2 800 msg/s at 8 connections, and RustMail pulls ahead at 32 (5 235 against 2 191). At those rates the port-forward, not either server, sets the ceiling, which is why the runs swing by ±20%.

  • To compare the servers themselves, the client ran inside the same VM.bench.py ran in one python:3.14-slim-bookworm container against the Linux RustMail binary and Mailpit's own binary, both as plain processes, so no traffic crossed the port-forward. This is a manual variant of the harness, not one of its tools. Over 3 interleaved runs of S1 at 8 connections the medians were:

    in one Docker VM, no port-forwardstored msgs/s (3 runs)median
    rustmail6 239 / 6 025 / 3 1426 025
    Mailpit3 698 / 2 367 / 4 1323 698
  • Native RustMail beats Mailpit-in-Docker by 3.7x at 8 connections, but that comparison is not fair to Mailpit, which pays the Docker Desktop VM tax that native RustMail does not.

  • At 1 connection the Docker port-forward round trip dominates for both containerized tools, so both sit around 700-900 msg/s regardless of server-side differences.

  • The 32-connection runs use 32 Python load-generator processes on a 12-core machine. Treat 32c as a contended-client number, not a server ceiling.

  • MailHog holds every message in memory and loses them all on restart.

Caveats ​

  • Docker on macOS runs in a VM. Every containerized tool pays port-forward latency and VM disk I/O. Only the rustmail-docker row compares like for like with Mailpit and MailHog; the native row shows what a Homebrew/direct-binary user gets, not a Docker-vs-Docker comparison.
  • MailHog here runs under amd64 emulation and stores messages in memory only, so its throughput and CPU numbers are pessimistic and its "successful" restart just means an empty mailbox.
  • The load generator is Python running on the same machine as the server(s) under test; at higher connection counts it competes for CPU with the tool being measured.
  • Apart from the S1 8-connection ingest medians, each number above is a single run, not an average across repeated runs.

This is not a claim that RustMail is faster or slower than Mailpit or MailHog in general, only what this harness measured on this machine, on this day, for these scenarios.

Released under the MIT / Apache 2.0 License.