Going to Production: Hardware Sizing and Domain DNS
Quick Start gets the system running; this guide covers two things you must do before production: choosing the right server, and setting up the DNS records for your mail domain.
Hardware and System Requirements
OS and Software
- Production deployment: a Linux server (x86_64 / aarch64 both fine) with
Docker (Compose v2). All four production stacks (ce / ee / multi / ha) are
fully containerized and managed by
mailezctl; the distribution'sinstall.shalso offers an interactive installer (pre-checking Docker, Compose v2, openssl, curl). - Development experience: the engine and control plane run natively on Linux / macOS / Windows with no restrictions on the dev machine; the Go toolchain (1.26+) is only needed when building images from source.
- Time sync: enable NTP. The lease mechanism of multi-instance HA / multi-active clusters is sensitive to clock skew; keep single-node deployments time-synced too (scheduled sending and log troubleshooting rely on accurate clocks).
Sizing
The mail stack itself is light: a single Mailezine engine instance idles at about 24 MiB of memory and peaks around 34 MiB under a 10k-user load model (internal benchmark). The bulk of system resources goes to the control plane and infrastructure components (MySQL, TiDB, Rspamd, RustFS). The table below lists recommended starting points — pick a stack by mailbox count:
| Scenario | Stack | vCPU | Memory | Disk (SSD) |
|---|---|---|---|---|
| Personal / trial / demo | dev or ce | 1–2 | 2 GiB | 20 GB |
| Small team (≤ 50 mailboxes) | ce | 2 | 4 GiB | 100 GB |
| Mid-size org (≤ 500 mailboxes) | ce or ee, single node | 4 | 8–16 GiB | sized by quota |
| Scale-out (ee + multi/ha) | components on separate nodes | 4+ per node | 8 GiB+ per node | sized by quota |
Disk estimation: each mailbox defaults to a 1 GiB quota (adjustable per user or per domain in the admin console), plus headroom for full-text search indexes, logs, and attachment staging. Large message blobs and cloud-drive files should land in S3-compatible object storage (Enterprise; RustFS by default, MinIO also supported), keeping only hot data on local disk.
Network and Ports
The server needs a public IP with the following ports open:
| Port | Purpose | Exposure |
|---|---|---|
| 25 | SMTP (server-to-server mail — see the section below) | inbound public required; outbound must be unblocked or relayed |
| 465 / 587 | Client sending (SMTPS / Submission) | public (for mail clients) |
| 110 / 995 | POP3 / POP3S | public (if using POP3) |
| 143 / 993 | IMAP / IMAPS | public (for mail clients) |
| 4190 | ManageSieve (filter sync) | public (if clients manage Sieve) |
| 80 / 443 | Gateway: webmail, /admin console, /dav sync + ACME auto certificates | public |
| 8081 / 8082 / 8083 | Backend API / admin console / webmail (debug ports) | loopback (127.0.0.1) only by default; production access always goes through the gateway on 80/443 |
Port 25: Irreplaceable, Inbound and Outbound
Port 25 is the mail server's lifeline — nothing replaces it for either sending or receiving. Clients can use 465/587 to send and 993 to read, but server-to-server delivery happens exclusively over port 25 — there is no alternative port and no way to "just use another one".
Inbound: every server delivering mail to you follows the DNS MX record to
mail.example.com and connects to its port 25. If inbound 25 is closed,
your domain receives no external mail at all — senders retry for hours,
then bounce, while your side shows no error, which makes the problem painfully
hard to notice.
Outbound: your server delivering mail to others also connects straight to the recipient MX's port 25. If outbound 25 is blocked, all outbound mail cannot leave and just piles up in the delivery queue.
The two most common pitfalls:
- Cloud providers block outbound 25 by default (anti-spam). Either request an unblock from the provider (usually requiring a PTR record and a legitimate use statement), or configure an external SMTP relay (smarthost, SASL auth supported) in the admin console so outbound mail is delivered through the relay; for forwarding scenarios the engine rewrites envelopes with SRS automatically, so bounces never return to your domain. Before buying a cloud server, confirm the provider will unblock outbound 25.
- Cloud server IPs are commonly listed on Spamhaus PBL. PBL is a policy listing — "IP ranges like this should not send directly" — not spam evidence: inbound is unaffected, but direct outbound may be rejected by recipients. Request removal, or again use an external relay. The admin console's Health page queries the ZEN blocklist automatically and surfaces PBL as an advisory with guidance.
Self-check port 25 before going live:
# From a network outside your server, verify inbound 25 is reachable
telnet mail.example.com 25
# or
nc -vz mail.example.com 25
# On the server, verify outbound 25 is not blocked
nc -vz gmail-smtp-in.l.google.com 25
Then run the admin console's Health page once: MX targeting, blocklist status, engine/database/certificate probes — all green means ports and DNS are ready.
Domain DNS Setup
Using mail domain example.com and mail host mail.example.com (i.e.
MAILEZ_HOSTNAMES) as an example. All records are added at your DNS
provider:
| Type | Host | Value | Purpose | Required? |
|---|---|---|---|---|
| A / AAAA | mail | server public IP | host name clients connect to and MX points to; also required for ACME issuance | ✅ required |
| MX | @ | mail.example.com (priority 10) | routing rule for other servers delivering *@example.com | ✅ required |
| TXT (SPF) | @ | v=spf1 a mx -all | declares "only this host may send for me"; receivers use it to prevent spoofing | ✅ strongly recommended |
| TXT (DKIM) | <selector>._domainkey | v=DKIM1; k=rsa; p=<public key> | outbound signing public key; generate in the admin console and copy the full record | ✅ strongly recommended |
| TXT (DMARC) | _dmarc | v=DMARC1; p=quarantine; rua=mailto:postmaster@example.com | tells receivers what to do when SPF/DKIM fails and where to send reports | ✅ strongly recommended |
| PTR (rDNS) | server IP | mail.example.com | reverse DNS; set at your cloud/ISP provider, not the DNS provider | ⭐ strongly affects deliverability |
| CNAME (optional) | autodiscover | mail.example.com | autodiscovery for Outlook and other Microsoft clients | optional |
| CNAME (optional) | autoconfig | mail.example.com | autoconfig for Thunderbird and Mozilla-family clients | optional |
| CNAME (optional) | mta-sts | mail.example.com | MTA-STS policy host | optional |
| TXT (optional) | _mta-sts | v=STSv1; id=20260101; max_age=86400 | MTA-STS transport-security policy (system default mode testing, reporting only) | optional |
| TXT (optional) | _smtp._tls | v=TLSRPT; rua=mailto:postmaster@example.com | TLS delivery reporting (TLSRPT) | optional |
Notes:
-
DNS wizard: the admin console's domains page includes a built-in DNS wizard that lists all nine records for each domain with the exact names and values (the DKIM value comes from the current key) and verifies each one live — configured / missing / mismatch. Copy, paste, done.
-
DKIM: generate the key in the admin console under "Domains → DKIM"; the page shows the exact record name and value, ready to copy into DNS. The selector defaults to
dkim(changeable viaMAILEZ_DKIM_SELECTOR), so the record looks likedkim._domainkey.example.com. Regenerating rotates the key (in-flight mail during the switch window may fail DKIM — expected). -
SPF:
a mxcovers both sending paths (mail host A record + MX target); if you also send through a relay provider, appendinclude:per their docs. On the inbound side, the engine runs full SPF / DKIM / DMARC verification on every incoming message and emits Authentication-Results, regardless of whether you publish these records — publishing them protects the mail you send out. -
PTR reverse DNS: most receivers' anti-spam systems (including mailezine's built-in reputation scoring) check that the sending IP's PTR matches the EHLO host name. Mail from a public IP without a PTR easily lands in spam; configure it in your cloud console before going live.
-
TLS certificates: with
MAILEZ_TLS=letsencryptthe gateway obtains and renews certificates automatically (requires 80/443 reachable and themail.example.comA record in effect); you can also place your owncert.pem/key.pem(see the repo'sdeploy/certs/README.md). Implicit TLS on 465/993/995 reuses the same certificates. -
Verification: after saving records, confirm they have propagated globally with
dig/nslookup(dig MX example.com,dig TXT dkim._domainkey.example.com); the admin console's DKIM status turns verified once DNS propagates. Then exchange a test message with an external mailbox (any mainstream provider) and check the Authentication-Results the recipient shows:spf=pass dkim=pass dmarc=pass. An online mail health-check service works well for a comprehensive audit too.
Production Checklist
- Server meets the sizing guidance, NTP enabled, Docker + Compose v2 ready
- Public IP ready, inbound 25 open to the public and outbound 25 unblocked or relayed (see the port 25 section)
-
mailez.envsetsMAILEZ_SECRET_KEY/MAILEZ_STACK_SECRET/MAILEZ_DOMAIN/MAILEZ_HOSTNAMES(production refuses placeholder secrets) - DNS: A / MX / SPF / DKIM / DMARC in effect, PTR configured at the cloud provider; autoconfig / autodiscover / MTA-STS records published as needed
- Ports: mail ports open to the public; debug ports (8081–8083) kept on
loopback, production access through the gateway (80/443,
/admin,/dav) - TLS:
MAILEZ_TLS=letsencrypt(or your own certs), implicit TLS handshakes on 465/993/995 - The admin console's Health page is all green: MX / SPF / DMARC / DKIM pass item by item, no SBL/XBL blocklist hits (handle the PBL advisory per its guidance), certificates and system probes normal
- Admin account created (
mailez-seed), default password changed, two-factor enabled - Backups: config export + engine
backup(full KV+blob export) in routine ops - Enterprise:
MAILEZ_LICENSE_FILEmounted, license shown as valid on the overview page
For scale-out operations details see the repo's docs/scaling.md; upgrade
procedures are in docs/upgrades.md and docs/rollout-runbook.md.