Skip to main content

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's install.sh also 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:

ScenarioStackvCPUMemoryDisk (SSD)
Personal / trial / demodev or ce1–22 GiB20 GB
Small team (≤ 50 mailboxes)ce24 GiB100 GB
Mid-size org (≤ 500 mailboxes)ce or ee, single node48–16 GiBsized by quota
Scale-out (ee + multi/ha)components on separate nodes4+ per node8 GiB+ per nodesized 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:

PortPurposeExposure
25SMTP (server-to-server mail — see the section below)inbound public required; outbound must be unblocked or relayed
465 / 587Client sending (SMTPS / Submission)public (for mail clients)
110 / 995POP3 / POP3Spublic (if using POP3)
143 / 993IMAP / IMAPSpublic (for mail clients)
4190ManageSieve (filter sync)public (if clients manage Sieve)
80 / 443Gateway: webmail, /admin console, /dav sync + ACME auto certificatespublic
8081 / 8082 / 8083Backend 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:

  1. 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.
  2. 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:

TypeHostValuePurposeRequired?
A / AAAAmailserver public IPhost 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 -alldeclares "only this host may send for me"; receivers use it to prevent spoofing✅ strongly recommended
TXT (DKIM)<selector>._domainkeyv=DKIM1; k=rsa; p=<public key>outbound signing public key; generate in the admin console and copy the full record✅ strongly recommended
TXT (DMARC)_dmarcv=DMARC1; p=quarantine; rua=mailto:postmaster@example.comtells receivers what to do when SPF/DKIM fails and where to send reports✅ strongly recommended
PTR (rDNS)server IPmail.example.comreverse DNS; set at your cloud/ISP provider, not the DNS provider⭐ strongly affects deliverability
CNAME (optional)autodiscovermail.example.comautodiscovery for Outlook and other Microsoft clientsoptional
CNAME (optional)autoconfigmail.example.comautoconfig for Thunderbird and Mozilla-family clientsoptional
CNAME (optional)mta-stsmail.example.comMTA-STS policy hostoptional
TXT (optional)_mta-stsv=STSv1; id=20260101; max_age=86400MTA-STS transport-security policy (system default mode testing, reporting only)optional
TXT (optional)_smtp._tlsv=TLSRPT; rua=mailto:postmaster@example.comTLS 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 via MAILEZ_DKIM_SELECTOR), so the record looks like dkim._domainkey.example.com. Regenerating rotates the key (in-flight mail during the switch window may fail DKIM — expected).

  • SPF: a mx covers both sending paths (mail host A record + MX target); if you also send through a relay provider, append include: 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=letsencrypt the gateway obtains and renews certificates automatically (requires 80/443 reachable and the mail.example.com A record in effect); you can also place your own cert.pem / key.pem (see the repo's deploy/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.env sets MAILEZ_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_FILE mounted, 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.