# mailserver A complete mail server on Debian 13, administered entirely from the terminal. Postfix accepts and sends, Dovecot stores and serves, Rspamd filters and signs. Domains, mailboxes, aliases and sender lists live in **one** SQLite file that both services read directly — changes take effect at once, with no restart. The whole thing is operated through a single tool: `mailctl`. No web interface. Nothing that opens ports by itself at night. ``` mailctl user add martin@example.com -g -q 5G mailctl blacklist add '@*facebook*' mailctl status ``` --- ## What is in it | Component | Job | |---|---| | **Postfix 3.10** | SMTP: acceptance (25) with postscreen, submission by our own users (587/465), sending with DANE | | **Dovecot 2.4** | IMAP (143/993), delivery via LMTP, Sieve, ManageSieve (4190), quotas | | **Rspamd 3.12** | spam filter as a milter, Bayes, greylisting, DKIM and ARC signing | | **Redis** | storage for Bayes, greylisting and ratelimit | | **unbound** | local validating resolver — a prerequisite for DNSBL and DANE | | **nftables** | packet filter, default policy `drop` | | **fail2ban** | bans repeated failed logins, reading the systemd journal | | **certbot** | certificate from Let's Encrypt, renews itself | | **mailctl** | the administration tool, in Go, with no runtime dependencies | Spam detection in four stages: postscreen fends off botnets before a process is even created; greylisting from a score of 4; Rspamd scores SPF, DKIM, DMARC, blocklists, structure, language and the statistical filter; Sieve files the result into Junk. Rejection only starts at 15 points, and then with an error message — never a silent delete. --- ## Quick start These have to be in place first: a static A record pointing at the server, a PTR record for the same name, outbound port 25 unblocked. Without those three there is no point — details in [docs/installation.md](docs/installation.md). If the host sits behind a firewall of its own, section 2 of that document lists every port that has to be permitted — **inbound and outbound**. The outbound half is the one that gets forgotten: port 25 for sending, and DNS on UDP *and* TCP 53 out to the whole internet, because the local resolver does its own recursion. ```sh git clone https://git.fhi.mpg.de/mike/mailserver.git cd mailserver cp install/mailserver.conf.example install/mailserver.conf $EDITOR install/mailserver.conf # MAILHOST, MAILDOMAIN, SSH_PORT sudo install/mailserver-install ``` Afterwards the script prints the DNS records still missing, ready to copy. > **The one value where a mistake hurts:** `SSH_PORT`. The firewall lets > exactly that port in. The script checks that an `sshd` really is listening > there and asks if not — even so, keep a second SSH session open during the > installation. --- ## Layout of the repository ``` install/ mailserver-install installation script, ten stages, repeatable verify-templates compares config/ with the running server mailserver.conf.example site data config/ every configuration file, with placeholders postfix/ dovecot/ rspamd/ unbound/ fail2ban/ systemd/ nftables.conf mailserver/schema.sql src/mailctl/ sources of the administration tool (Go) complete.go the command tree behind Tab completion _mailctl zsh function that asks the tool for candidates bin/mailbackup daily backup of the database and the keys docs/ installation.md from Debian to the first message, migration included operations.md operating manual: day-to-day, spam filter, troubleshooting ``` The placeholders in `config/` are `@@MAILHOST@@`, `@@MAILDOMAIN@@`, `@@SERVER_IP@@` and `@@SSH_PORT@@`. The installer fills them in; `install/verify-templates` factors them back out again for comparison. --- ## mailctl One tool, all in the terminal. `mailctl help` shows the full list. ``` domain add|list|del domains; the DKIM key is created automatically user add|list|del|passwd|quota mailboxes, passwords, quotas enable|disable alias add|list|del forwardings, catch-all included sieve list|edit|test|del personal rules per mailbox lang allow|list|del|test language filter blacklist|whitelist add|list block senders, or always let them through del|test dns every DNS record this domain needs check verifies they are published status services, totals, queue, certificate queue [-f] the mail queue ``` Block and allow lists take wildcards: `@*facebook*` hits every sender whose domain contains facebook, `martin@*` every Martin anywhere. Where they overlap, the allow entry wins. Build it with the Go from Debian 13: ```sh cd src/mailctl && make check && sudo make install ``` The binary is statically linked (`CGO_ENABLED=0`, SQLite driver in pure Go) and needs no libraries from the system. Tab completion comes out of the tool itself. `mailctl __complete` is handed the words typed so far and answers with what may follow the last of them; the command tree and the providers behind its values live in `complete.go`, next to the commands they describe. `make install` puts a generic zsh function in `/usr/local/share/zsh/site-functions/_mailctl` that does nothing but pass the line in and hand the answer back, so it needs no change when a command is added. The values come from the database, so only what exists is offered: `mailctl user del ` lists the mailboxes that are there, `mailctl user enable ` only the locked ones, `mailctl sieve edit
` that mailbox's scripts. Completing opens the database read-only and never creates or migrates it. --- ## Provenance and limits This was extracted from a running server, not designed on a drawing board. Every configuration file is commented, and the comments do not explain *what* is written there but *why* — including the cases where the obvious route did not work. The chapter *Pitfalls* in the [operating manual](docs/operations.md) collects the most expensive of them. It was verified against exactly one setup: Debian 13.6, Dovecot 2.4.1, Rspamd 3.12.1, Postfix 3.10, on amd64. **arm64 needs no changes** — every package exists, `mailctl` compiles unchanged, and Rspamd keeps its regex acceleration through Vectorscan; see [docs/installation.md](docs/installation.md), section 1. In particular: - **Dovecot 2.4 has a different configuration language from 2.3.** On a system with Dovecot 2.3, `config/dovecot/dovecot.conf` will not run. Older guides found on the net do not apply either. - **IPv6 is not set up.** The original server had none. Anyone using IPv6 has to extend `inet_protocols`, unbound and nftables accordingly — and then also publish an AAAA record and a second PTR, or it does more harm than good. - **No webmail client is included.** Deliberately: the requirement was administration and access through terminal and mail client only. Documentation, comments and identifiers are in English.