# 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](https://rspamd.com) 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) ```bash cd docker-deploy docker compose --profile standalone up -d --build ``` See [`docker-deploy/README.md`](docker-deploy/README.md) for the rspamd-bundled variant and full details (ports, volumes, first-boot config). ### From source ```bash 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 ```bash 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: ```bash 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: ```ini [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/`](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](LICENSE).