NitroMTA NitroMTA

Your mail.
Your metal.

The open-source Amazon SES replacement.

A self-hosted mail delivery engine that speaks the Amazon SES v2 API. Keep your SES code, set up your own sending IPs and domains, and deliver straight from your servers.

Nitrosend's mail delivery engine, open sourced.

Built in Rust. Self-hosted. Apache-2.0.

The NitroMTA mark as a machined metal N with vermilion faces.

Drop-in

Your SES code, as it is.

One line changes: the endpoint. Your application's keys become an access key of the engine's own.

SMTP clients send to it too, with STARTTLS and a login. Tested against the official AWS SDKs for Python, JavaScript and Ruby. Compatibility lists every operation.

  ses = boto3.client(
      "sesv2",
+     endpoint_url="https://mta.example.com",
      region_name="us-east-1",
  )
  sent = ses.send_email(...)
  print(sent["MessageId"])
MessageId  00c4a9a286e00000

From a bare server to delivered mail.

Install it, point your DNS, and nitromta doctor checks every record a receiver looks at: reverse DNS, SPF, DKIM, DMARC and the bounce domain, with the exact record to publish for any that's missing.

$ dig +short -x 2.25.136.14
mta-proof.nitrosend.com.
$ timeout 10 bash -c 'exec 3<>/dev/tcp/gmail-smtp-in.l.google.com/25 && head -n1 <&3'
220 mx.google.com ESMTP af79cd13be357-94425d43b39si773558285a.535 - gsmtp
# The address has its reverse DNS name, and port 25 is open: the receiver greets you. A blocked port times out.
$ gh release download -R nitrosend/nitromta -p '*_amd64.deb'
$ sudo apt install ./nitromta_*_amd64.deb
…
Unpacking nitromta (0.1.5) ...
Setting up nitromta (0.1.5) ...
Creating group 'nitromta' with GID 995.
Creating user 'nitromta' (NitroMTA mail engine) with UID 995 and GID 995.
$ nitromta --version
nitromta 0.1.5
$ gh release download -R nitrosend/nitromta -p '*.x86_64.rpm'
$ sudo dnf install ./nitromta-*.x86_64.rpm
…
$ nitromta --version
nitromta 0.1.5
$ yay -S nitromta-bin
$ docker pull ghcr.io/nitrosend/nitromta:0.1.5
…
$ docker run --rm ghcr.io/nitrosend/nitromta:0.1.5 --version
nitromta 0.1.5
$ sudo install -d -o nitromta -g nitromta /etc/nitromta
$ sudo -u nitromta nitromta setup --config /etc/nitromta/config.toml \ --hostname mta.example.com --return-path-domain bounces.example.com
wrote /etc/nitromta/config.toml
wrote /etc/nitromta/keys, with key 1
Left to do:
1. A certificate for mta.example.com, from a certificate authority, at /etc/nitromta/tls.crt, and its key at /etc/nitromta/tls.key.
2. The configuration, the keys with the .lock and .engine files beside them, and the store's directory the engine's own user's: run setup as that user, or chown them to it.
3. Start the engine: systemctl start nitromta, or nitromta run --config /etc/nitromta/config.toml.
4. Its policy, through the admin socket: an identity for each domain mail is sent as, an egress for each address it goes out from, and a login or access key for each application.
5. The MX of bounces.example.com pointed at this host, and port 25 open to it: bounces and complaints arrive there.
6. nitromta doctor --config /etc/nitromta/config.toml, which checks it all and says which DNS records to publish.
$ sudo -u nitromta nitromta doctor --config /etc/nitromta/config.toml
ok store: 46765 MiB free on its disk, against a reserve of 1024 MiB
ok store: the running engine holds it, and checked it as it started
ok keys: /etc/nitromta/keys holds key 1, its owner's alone
ok submission: its certificate is valid for mta-proof.nitrosend.com, past two weeks
ok SES API: its certificate is valid, past two weeks
ok feedback: its certificate is valid for mta-proof.nitrosend.com, past two weeks
ok clock: -0.001 s from time.cloudflare.com:123
ok resolver: it validates DNSSEC, as DANE needs
ok egress 2.25.136.14: its PTR names mta-proof.nitrosend.com, which resolves back to it, and its HELO is that name
ok egress 2.25.136.14: port 25 reaches gmail-smtp-in.l.google.com:25
ok egress 2.25.136.14: not on zen.spamhaus.org
ok egress 2.25.136.14: not on b.barracudacentral.org
ok egress 2.25.136.14: not on bl.spamcop.net
ok egress 2.25.136.14: mta-proof.nitrosend.com's SPF covers it
ok identity mta-proof.nitrosend.com: s1._domainkey.mta-proof.nitrosend.com publishes its DKIM key
ok identity mta-proof.nitrosend.com: DMARC governs it, with policy reject
fail identity nitrosend.com: s1._domainkey.nitrosend.com publishes no DKIM key: publish s1._domainkey.nitrosend.com TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC…"
ok identity nitrosend.com: DMARC governs it, with policy reject
ok bounces: mta-proof.nitrosend.com has an MX, which bounces go to
1 failed, 0 to look at, 18 passed

Benchmarks

3.16× KumoMTA. 4.11× Postfix.

Each system on 4 CPUs of the same AWS bare-metal server, committing every message to disk before accepting it: 16 KiB messages, DKIM-signed, TLS in and out.

NitroMTA
3,313messages/s
KumoMTA2026.09.29
1,048messages/s
Postfix3.11.7 with OpenDKIM
806messages/s

1.35 million messages across nine runs, every one delivered once and DKIM-verified.

The highest rate each sustained, median of three runs, on an AWS c7gd.metal with each system's queue on local NVMe. Run it yourself with cargo harness quick. The method and every run.

Keep your integration. Own your delivery.

One Rust binary on your own Linux host, with its store in SQLite and a systemd unit.

SES v2 compatible

SendEmail, SendBulkEmail, templates, identities, configuration sets, tenants, the suppression list, dedicated IPs and SES-shaped events through SNS.

Durable

A message is accepted only once it is on disk. Tested by cutting the power at every I/O operation, and by moving a store to a new host.

Deliverability built in

New IPs warm up receiver by receiver from 50 messages a day. Per-provider connection limits and cooldowns, automatic suppression, DKIM signing, and TLS verified by MTA-STS and DANE.

Explains itself

nitromta explain says why any message is waiting: its last reply, the rule it matched, and the cooldown or cap holding it. Health checks and Prometheus metrics.

Encrypted at rest

Personal data under the installation's key, or each tenant's mail under a key of its own, so removing a tenant erases it.

Hardened

Refuses to run as root, seals its stored secrets apart from its store, bounds every connection, and runs under a hardened systemd unit.

Try it

No server yet? Try it on your machine.

Capture mode runs the whole engine locally and shows each message in a browser instead of sending it.

Send with the code above, then open 127.0.0.1:9090/capture. The capture quickstart has every step.

brew install nitrosend/tap/nitromta
nitromta setup --config config.toml --capture
nitromta run --config config.toml
gh release download -R nitrosend/nitromta \
  -p '*_amd64.deb'
sudo apt install ./nitromta_*_amd64.deb
nitromta setup --config config.toml --capture
nitromta run --config config.toml

Hear when a release ships.

One email per release: what's new and how fast it runs.