NodeMailServer
KumoMTA

KumoMTA overview

Use KumoMTA as the sending engine, with SMTP authentication, queues, rate limits and delivery webhooks.

KumoMTA is a high-volume mail transfer agent. The outbound/ folder holds its policy: Lua and TOML files that tell it how to accept, queue and deliver your mail.

Read the known issues first

DKIM signing and USE_RELAY=true work and were tested with KumoMTA 2026.09.29. Traffic shaping is not loaded yet. See Known issues.

What it does

client ──587 + AUTH──▶ KumoMTA ──▶ queue (one per recipient domain)
                                    ──▶ egress pool (your sending IPs) ──▶ recipient's MX
                                    ──▶ WEBHOOK_URL (delivery and bounce events)
  1. A client or your Node app connects on port 587 and logs in.
  2. KumoMTA checks the sender and recipient, and puts the message in a queue for each destination domain.
  3. It delivers from one of your outbound IP addresses, at a rate set for each provider, and retries temporary failures.
  4. Every delivery, bounce and delay is posted to your webhook as JSON.

Use it from the Node server

The Node server can send its queued mail through KumoMTA, so Node keeps ports 25 and 587 and KumoMTA stays on the private network:

  • OUTBOUND_DRIVER=relay submits over SMTP. KumoMTA needs the Node server in relay_hosts, or a login.
  • OUTBOUND_DRIVER=kumo-http posts to KumoMTA's HTTP API. KumoMTA needs the Node server in trusted_hosts on its HTTP listener.

KumoMTA's delivery events can be posted back to the Node server's bounce HTTP server. See Outbound drivers for what each driver sends.

Files

Everything KumoMTA reads is in outbound/policy/. The folder is mounted read-only at /opt/kumomta/etc/policy, so your .env file never enters the container.

FileWhat it does
init.luaEntry point. Starts the 587 listener, defines the spool and logs, and handles AUTH, sender and recipient checks.
dkim.luaDKIM signing. Keys come from a file, an HTTP service or Redis.
egress.luaBuilds the egress pool main-pool from KUMOMTA_SOURCE_IPS.
queue.luaPer-domain message rate and the retry schedule.
webhook.luaPosts log events to WEBHOOK_URL.
listener_domains.tomlWhich domains accept relayed mail and bounce reports.
domains.luaSender domains that are allowed when USE_RELAY=true.
shaping.tomlPer-provider connection and rate rules. Not loaded yet.
users.luaSMTP usernames and passwords. Not committed. Start from users.example.lua.

Run it

From the repository root:

cp outbound/.env.example outbound/.env
cp outbound/policy/users.example.lua outbound/policy/users.lua
# edit both files, then:
docker compose -f deploy/docker-compose.yml up -d outbound
docker compose -f deploy/docker-compose.yml logs -f outbound

Spool and logs live in the kumo-spool and kumo-logs volumes, so queued mail survives a rebuild.

KumoMTA's HTTP API listens on 127.0.0.1:8000 inside the container only. To look at the queues, use kcli from inside it:

docker compose -f deploy/docker-compose.yml exec outbound /opt/kumomta/sbin/kcli queue-summary

Next

On this page