ZoneMTA: an outbound SMTP relay with per-sender reputation zones
📤 Modern outbound MTA cross platform and extendable server application
At a glance
- What is it?
- ZoneMTA is a Node.js outbound mail transfer agent, or MSA, that queues messages in MongoDB and routes them through virtual sending zones so that trusted senders go out over high reputation addresses and untrusted ones over slower pools. It is outbound only, it is extensible through plugins inherited from Nodemailer, and it needs MongoDB and Redis to run.
- Who is it for?
- ZoneMTA suits a team running its own transactional mail, an application platform or a newsletter service, that wants to separate good senders from bad ones at the IP level and to keep full control of the queue. It is the wrong tool for receiving mail, since it is an MSA and does not do inbound, and it is the wrong tool if you do not want to operate MongoDB and Redis.
- Can I use it commercially?
- Yes, with conditions. EUPL-1.2 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Outbound only, and built for reputation management
ZoneMTA is an outbound SMTP relay, described as an MTA and MSA, built on Node.js with MongoDB for queue storage. The word outbound is the whole design brief. The README says it is comparable to Haraka but unlike Haraka it is for outbound only, and both run on Node.js with a built in plugin system even though the designs differ. Haraka is an inbound and outbound mail server, so the comparison is a way of saying what ZoneMTA is not. The problem ZoneMTA addresses is reputation. Trusted senders should go out over high speed virtual sending zones that use high reputation IP addresses, and less trusted senders should be routed through slower zones with less reputable addresses. That matters because a shared sending pool punishes everyone when one sender misbehaves, and separating the pools at the IP level is the only real remedy. The audience is a platform team or an application operator who sends transactional mail at volume and needs to control which of its own senders gets which reputation, and it is a server you operate, not a service you subscribe to.
The incoming pipeline streams into GridFS
Messages are dropped for delivery either over SMTP or through the HTTP API, and the interesting property is that the message is processed as a stream, so the size of the message should not matter, except when a very large message is submitted through the JSON API. The same streaming property applies to the DKIM body hash calculation, because the hash is computed chunk by chunk as the message stream flows through, and the actual signature is generated from the body hash at delivery time. The incoming stream starts at the incoming connection and ends in MongoDB GridFS, and if there is an error at any step in between, the error is reported back to the client and the message is rejected. Leftover partial data is garbage collected after a while, since message bodies without a referencing delivery row are deleted automatically. That last detail is the kind of operational behaviour that separates a server you can leave running from one that slowly fills a disk, and it is worth noting that the queue lives in MongoDB rather than on the local filesystem, which is also why the outgoing diagram in the README carries an outdated note about LevelDB no longer being used.
Sending zones are the routing unit
You can define as many sending zones as you want, and each one can have its own local address IP pool used for the messages designated for that zone. IP addresses are not locked, so you can assign the same address to multiple zones, or the same address multiple times within one zone. Each zone can also specify the maximum number of parallel outgoing connections per process, which is the knob that separates a fast zone from a slow one. Routing happens in two ways. The first is by zone name, using a header on the message:
X-Sending-Zone: zone-identifierThe second is by assigning specific recipient domains to specific sending zones. The README also warns that the header based routing is enabled by default only for the api and bounce zones, and points at the allowRoutingHeaders option in the default configuration for the details, which is the kind of default that matters if you rely on it. The feature list adds the operational consequences of this model. Throttling per sending zone connection, automatic back-off if an IP address gets blacklisted, smarter bounce handling, and built-in IPv6 support are all zone aware, and STARTTLS is used for outgoing messages by default.
Running it: a template, MongoDB, Redis and three free ports
The quick start assumes Node.js 16 or newer, MongoDB on localhost and git, and that nothing is listening on the ports the server wants. Those are 2525 for SMTP, 12080 for the HTTP API and 12081 for the internal data channel, and all of them are configurable. The install path goes through a separate application template rather than the repository itself:
$ git clone git://github.com/zone-eu/zone-mta-template.git
$ cd zone-mta-template
$ npm install eslint --save-dev
$ npm init
$ npm install --production
$ npm startIf that succeeds you have an SMTP relay with no authentication running on localhost port 2525, and it does not accept remote connections, which is a sensible default for a first run. The next step in the documentation is to install and configure an additional plugin, or to edit the default configuration in the config folder. The web administration console is a separate project that has to be installed separately, and it is not part of the default installation. The requirements list is short: Node.js 16 or newer to run the app, MongoDB to store messages in the queue, and Redis for locking and counters. The Redis role is the one to note, because it is not optional decoration, it is what serialises work between the processes that handle the queue.
The plugin system comes from Nodemailer, not Haraka
The README is explicit about the ancestry of the extension mechanism. The plugin system for ZoneMTA, and a lot more besides, is inherited from the Nodemailer project, and therefore has no direct relation to Haraka. That matters if you are choosing a platform to build on, because the plugin ecosystem you inherit is the Nodemailer one, with its conventions, its packages and its documentation. The list of built-in plugins and features in the repository covers a lot of ground. Automatic DKIM signing, with keys supplied through the built-in DKIM plugin or handled by your own plugin, and ZoneMTA computes the required hashes and signs when one or more keys are provided. The Message-Id and Date headers are added if missing. Delayed messages are built in, a future value in the Date header means the message is not sent before that time, so no separate scheduler is needed. Spam detection uses Rspamd, reports go to Prometheus through the prom-client package, and delivery to HTTP using POST instead of SMTP is supported for destinations that prefer it. Email Address Internationalization and the SMTPUTF8 extension are supported too, so unicode addresses can be used.
Large messages, the HTTP API, and the queue you have to watch
Two features shape how you would use ZoneMTA in production. The first is large message handling. All data is processed in chunks without reading the entire message into memory, so it does not matter whether the message is 1kB or 1GB in size, and the same chunked approach is what keeps the DKIM body hash honest. The second is the HTTP API to send messages, which exists so that applications do not have to speak SMTP. The queue is stored in MongoDB, and the web interface exists to see queue status and to debug deferred messages, which tells you the intended operating posture: this is a system where messages can get stuck, and you look at them. That is also why the web admin is a separate installation, and why the repository ships it as zmta-webadmin rather than bundling it. The other features sit around the same theme of control. Message rewriting, IP warm-up, connection pooling, and per-zone throttling are all about managing reputation over time rather than simply relaying mail.
Licensing, versions and how the project is packaged
The repository ships two licence files, LICENSE and LICENSE_ET, which is the pattern of the European Union Public Licence, and the repository's licence field records EUPL-1.2 while the package manifest declares EUPL-1.1+. The manifest also marks the package as not private, so it is published to the registry, and the version there matches the newest release, 3.11.4. The release history is busy and patch-level, with 3.11.4 on 2026-10-07, 3.11.3 and 3.11.2 both on 2026-09-27, and the last push to the master branch on 2026-10-14. The codebase is a JavaScript project with a Gruntfile for the test task, a Grunt based test script, and a set of npm scripts that are worth reading before you deploy. The start script runs the app, the show script starts it with configuration only so you can print the effective configuration, the certs script generates a self-signed certificate with OpenSSL for local use, and the update script removes node_modules and the lockfile, checks for outdated dependencies with ncu and reinstalls. The dependency list mixes Zone's own packages, mailsplit for streaming message parsing and wild-config for the configuration system, with Nodemailer itself, the smtp-server package for the inbound side, mailauth for the authentication work, ioredis, the MongoDB driver and restify for the HTTP API.
Editorial conclusion
ZoneMTA suits a team running its own transactional mail, an application platform or a newsletter service, that wants to separate good senders from bad ones at the IP level and to keep full control of the queue. It is the wrong tool for receiving mail, since it is an MSA and does not do inbound, and it is the wrong tool if you do not want to operate MongoDB and Redis. Before adopting it, read the routing rules for the api and bounce zones, because the X-Sending-Zone header is honoured by default only for those, and check the licence, since the manifest declares EUPL-1.1+ while the repository records EUPL-1.2 and ships two separate licence files.
Frequently asked questions
What does ZoneMTA do that a normal SMTP relay does not?
It routes messages through virtual sending zones. Each zone has its own local IP address pool and its own limit on parallel outgoing connections, so trusted senders can go out over high reputation addresses and less trusted ones over slower pools, with throttling and automatic back-off applied per zone.
What does ZoneMTA need installed to run?
Node.js 16 or newer, MongoDB for storing messages in the queue, and Redis for locking and counters. The web administration interface is a separate project and is not part of the default installation.
How do I route a message through a specific sending zone?
You add an X-Sending-Zone header with the zone identifier, or you assign specific recipient domains to specific zones. The header based behaviour is enabled by default only for the api and bounce zones, and the allowRoutingHeaders option in the default configuration controls the rest.
Is ZoneMTA an inbound mail server?
No. It is an outbound SMTP relay, an MTA and MSA, and the README compares it to Haraka while noting that unlike Haraka it is for outbound only. It accepts messages for delivery by SMTP or its HTTP API and sends them out.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/zone-eu-zone-mta)Was this useful?