Files
mailgoserver/README.md
T
2026-08-15 06:22:02 +01:00

8.1 KiB

mailgoserver

A self-hosted email server in one static Go binary: SMTP (send + receive, direct-to-MX delivery, DKIM signing), IMAP mailbox storage, an admin web dashboard, and a self-service webmail portal. It's a Go port of PyMTA-server — same feature set, one process instead of a Python venv + separate services.

What it does

  • SMTP MTA — accepts mail for domains you configure, relays outbound mail directly to the recipient's MX (no smarthost needed), signs outgoing mail with DKIM, and enforces per-domain sender authentication or IP whitelisting so it can't be used as an open relay.
  • IMAP mailbox storage — real, retrievable mailboxes (Thunderbird, Outlook, any IMAP client) with AES-encrypted-at-rest message storage, per-mailbox quotas, send-as aliases, and filter rules (move to folder, forward, discard, based on from/subject/ body conditions with AND/OR logic).
  • Spam filtering — a built-in heuristic score always runs; optionally point it at an rspamd instance for a lot more signal (see docker-deploy/ for a container that bundles rspamd for you).
  • Webmail portal (/webmail) — inbox/folders, HTML compose with attachments, drafts, search, keyboard shortcuts, conversation grouping, recipient autocomplete, filter-rule management, and PGP (OpenPGP encrypt/decrypt/sign/verify) and S/MIME (sign/verify) support per mailbox.
  • Admin dashboard (/pymta-manager) — manage domains, senders, mailboxes, DKIM keys, IP whitelisting, TLS (self-signed or Let's Encrypt via DNS-01: Cloudflare, Route53, DigitalOcean, Google Cloud DNS), and review email + auth logs.
  • Security hardening built in — CSRF protection, security headers (CSP, X-Frame- Options, etc.), Cloudflare-aware trusted-proxy IP resolution, login rate limiting and account lockout, TOTP + WebAuthn/passkey MFA (admin and mailbox owners), and automatic temporary IP blacklisting for SMTP/IMAP brute-force or relay-abuse attempts with an admin-visible Blacklist page and dashboard attack-count tiles.

Quick start

Docker (fastest)

cd docker-deploy
docker compose --profile standalone up -d --build

See docker-deploy/README.md for the rspamd-bundled variant and full details (ports, volumes, first-boot config).

From source

go build -o mailgoserver .
./mailgoserver

One static binary — no venv, no pip install, no gunicorn. Requires Go 1.26+ to build; the binary itself has no runtime dependencies (pure-Go SQLite driver, no cgo).

What to expect on first run

There's no config to write up front. The first time it starts in a given working directory, it generates:

  • settings.ini — every setting with an inline comment explaining it (SMTP/IMAP ports, hostname, TLS, DKIM key size, mailbox quotas, MFA enforcement, rate limits, and so on). Regenerated only if missing — it's never overwritten or merged into on later runs, so edits stick.
  • A self-signed TLS certificate (used until you either supply your own or enable Let's Encrypt from the admin dashboard).
  • An empty SQLite database, with one seeded admin account: username admin, password Password123!. Logging in with it immediately forces a username + password change before anything else in the dashboard is reachable — the default credentials can never be left in place.
  • A mailstore master key and a CSRF-signing app secret, both generated once and reused on every subsequent start — back these up like any other secret (losing the mailstore master key makes all stored mail unrecoverable, even for admins).

From there: log into /pymta-manager, add a domain (and complete its DNS ownership verification), add a mailbox or sender, and you're sending/receiving. The bare / root redirects to the webmail login (/webmail/login) by default, since most visitors are mailbox owners, not admins — there's a "Login as Admin" link from there to /pymta-manager.

Default ports (all configurable in settings.ini): SMTP 25, direct-TLS SMTP 465, IMAP 143, direct-TLS IMAP 993 — the real standard mail ports, so binding them directly needs root or setcap (see below), which the Docker deployment already handles for you. Admin/webmail HTTP 5000 / HTTPS 5001 stay deliberately non-privileged; put a reverse proxy or your own 80/443 mapping in front of those if you want the dashboard on standard web ports too.

Build

cd mailgoserver
go build -o mailgoserver .

Bind ports 25/143/465/993 without root

The default SMTP/IMAP ports are the real standard ones now, so running the binary directly (not via Docker) needs one of:

sudo setcap 'cap_net_bind_service=+ep' ./mailgoserver

or run it as root, or via the systemd unit below (which grants the capability instead of running as root). Same purpose as script_setup_py_environment.sh's setcap step on the Python venv, applied to the compiled binary instead. Not needed for the Docker deployment — those containers run as root, so binding 25/143/465/993 directly just works with no extra setup.

systemd (unified process)

The Go binary runs the SMTP listeners and the web UI in one process (no GIL, so no need to split them into separate services the way script_install_service.sh split pymta-smtp.service / pymta-web.service for the Python version). One unit is enough:

[Unit]
Description=mailgoserver (SMTP + web admin)
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/mailgoserver
ExecStart=/opt/mailgoserver/mailgoserver --host 127.0.0.1 --port 5000
Restart=always
RestartSec=5
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/opt/mailgoserver
ProtectHome=true

[Install]
WantedBy=multi-user.target

If you do want the SMTP and web parts as separate services (matching the Python split exactly), run two units with --smtp-only and --web-only respectively — both flags exist for this.

nginx

No changes needed. script_nginx_setup.sh reverse-proxies to http://127.0.0.1:5000 and terminates its own TLS for the web UI — mailgoserver listens on the same host:port by default, so the existing nginx config works unmodified. The SMTP TLS listener still consumes ssl_certs/server.crt/server.key, same as before.

Docker

See docker-deploy/ — a standalone image and one that bundles the latest rspamd in the same container, both via a single docker-compose.yml using Compose profiles.

Admin dashboard login

First run seeds one account: username admin, password Password123!. Logging in with it immediately forces a username + password change before anything else in the dashboard is reachable — the default credentials can never be left in place.

Optional second factors, enabled per-account from Account in the sidebar:

  • Authenticator app (TOTP) — works anywhere, no extra config.
  • Passkeys / security keys (WebAuthn) — bound to the exact origin the dashboard is served at. Set [Auth] rp_id / rp_origin in settings.ini to your real public domain before registering passkeys in production (e.g. rp_id = mail.example.com, rp_origin = https://mail.example.com). The defaults (localhost / http://localhost:5000) only work for local testing — WebAuthn requires either HTTPS or the literal host localhost, so passkeys need the nginx+TLS setup above (or the Docker deployment) to work behind a real domain.

Abuse protection

Failed SMTP/IMAP auth attempts (bad passwords, denied relay attempts) are counted per source IP; once a configurable threshold is hit within a window, that IP is temporarily blocked at the listener level — before the SMTP/IMAP banner is even sent — with the block duration doubling on repeat offenses up to a cap. Manage active blocks and exempt trusted IPs from this check entirely from Blacklist in the admin sidebar (a global- admin-only page) — this is separate from the IP whitelist used to authorize unauthenticated relay for a domain. Tunable in settings.ini's [Security] section.


License

See LICENSE.