# Mail — self-hosted Mailu AgentBBS gives **every verified member** (free and paid alike) a real mailbox at `@bbs.profullstack.com`, reached two ways: - **Webmail** — `https://mail.profullstack.com` (Roundcube). - **AgentMail** — the in-BBS client (`internal/mailbox`): the `Mail` hub entry or `ssh mail@bbs.profullstack.com` (a TUI for humans, a JSON bot mode for agents). It connects to this stack. Two distinct names are involved — don't conflate them: | | value | role | |---|---|---| | **Address domain** | `bbs.profullstack.com` | the `@`-part of member addresses (`AGENTBBS_MAIL_ADDR_DOMAIN`) | | **Mail server host** | `mail.profullstack.com` | where IMAP/SMTP/webmail actually run (`AGENTBBS_MAIL_DOMAIN`) | The apex `profullstack.com` is **reserved for corporate mail** and is not served here. ## Architecture The host already runs **Caddy** (owns `:80`/`:443`) and the **agentbbs** process. Mailu (Postfix + Dovecot + Roundcube + rspamd) runs as a Docker Compose stack: - Mailu owns the **mail ports** on the host: `25, 465, 587, 993, 995`. - Mailu's HTTP front is bound to **loopback** (`127.0.0.1:8080`); **Caddy** reverse-proxies `https://mail.profullstack.com` to it (webmail + admin + API). - **TLS:** `TLS_FLAVOR=mail` — Mailu does *not* run its own ACME (Caddy is the only ACME client). Caddy obtains the `mail.profullstack.com` cert; the cert refresher copies it into Mailu and reloads on renewal. - The **agentbbs gateway** reads/sends on behalf of members: IMAP via a Dovecot **master user** (one secret opens any mailbox), SMTP via the co-located relay on `127.0.0.1:25`. Members therefore never manage an IMAP/SMTP password. - **Provisioning** is automatic: when a member verifies their email at `join@` (or opens `Mail`), agentbbs ensures `@bbs.profullstack.com` exists via Mailu's **admin REST API** (`internal/mailu`, token = `API_TOKEN`). The manual `deploy/mailu/provision-mailbox.sh` is only for the gateway master user and backfills. ``` ┌─────────── Caddy (:443) ───────────┐ webmail → │ mail.profullstack.com → 127.0.0.1:8080 (Mailu front: webmail/admin/API) └───────────────┬─────────────────────┘ │ copies LE cert (refresh-certs.sh) clients → Mailu front (:25 :465 :587 :993 :995) ──→ Postfix / Dovecot / rspamd ▲ agentbbs ──IMAP 993 (master user)──┘ ──SMTP 127.0.0.1:25 (local relay)──▶ agentbbs ──admin API (token) http://127.0.0.1:8080/api/v1──▶ (auto-provision) ``` ## DNS Mail is delivered to the **address domain** (`bbs.profullstack.com`), so its MX must point at the **server host** (`mail.profullstack.com`): | Type | Host | Value | |---|---|---| | A | `mail.profullstack.com` | host IP | | MX | `bbs.profullstack.com` | `10 mail.profullstack.com.` | | TXT (SPF) | `bbs.profullstack.com` | `v=spf1 mx -all` | | TXT (DMARC) | `_dmarc.bbs.profullstack.com` | `v=DMARC1; p=quarantine; rua=mailto:postmaster@bbs.profullstack.com` | | TXT (DKIM) | `dkim._domainkey.bbs.profullstack.com` | from `flask mailu config-export` after first boot | | PTR | host IP | `mail.profullstack.com` (set at your VPS provider) | > **Port 25 / deliverability:** many cloud providers (incl. DigitalOcean) block > outbound `:25` by default — request an unblock, set the PTR/rDNS, and warm the > IP, or relay outbound through a smarthost. Inbound MX and the gateway's local > submission work regardless. ## Install ```bash cd /opt/agentbbs/deploy/mailu cp mailu.env.example mailu.env # fill SECRET_KEY, INITIAL_ADMIN_PW, API_TOKEN, DOMAIN=bbs.profullstack.com, HOSTNAMES=mail.profullstack.com docker compose up -d # add the address domain + the gateway master user: docker compose exec admin flask mailu domain bbs.profullstack.com AGENTBBS_MAIL_MASTER_USER=gateway ./provision-mailbox.sh --master "$(openssl rand -hex 16)" ``` setup.sh writes the Caddy `mail.profullstack.com` site and the cert-refresh timer when `MAIL=1`, and brings the stack up once `mailu.env` exists. ## agentbbs gateway env Set these on the agentbbs service (setup.sh §9e upserts the non-secret ones): | Var | Value | |---|---| | `AGENTBBS_MAIL_ADDR_DOMAIN` | `bbs.profullstack.com` | | `AGENTBBS_MAIL_DOMAIN` | `mail.profullstack.com` | | `AGENTBBS_MAIL_IMAP_ADDR` | `127.0.0.1:14143` (Dovecot direct, loopback) | | `AGENTBBS_MAIL_IMAP_PLAINTEXT` | `1` (the loopback path is plaintext) | | `AGENTBBS_MAIL_SMTP_ADDR` | `127.0.0.1:25` | | `AGENTBBS_MAIL_ADMIN_URL` | `http://127.0.0.1:8080` | | `AGENTBBS_MAIL_API_TOKEN` | the Mailu `API_TOKEN` (secret) | | `AGENTBBS_MAIL_MASTER_USER` | `gateway` | | `AGENTBBS_MAIL_MASTER_PASS` | the master password set above (secret) | | `AGENTBBS_WEBMAIL_URL` | `https://mail.profullstack.com` (default = mail host) | Without `AGENTBBS_MAIL_API_TOKEN` auto-provisioning is skipped (the address is still shown); without `AGENTBBS_MAIL_MASTER_PASS` the gateway can't open mailboxes. > **`AGENTBBS_MAIL_SMTP_ADDR` is `127.0.0.1:25` on purpose** — it's the gateway's > *loopback* hand-off into the on-box Mailu/Postfix MTA, not a remote submission > client, so `25` is correct (465/587 are for authenticated remote clients). > This is a separate config from `AGENTBBS_SMTP_*` (the transactional sender for > confirmation codes and `notify-creds`, which defaults to STARTTLS `:587`). See > [`credentials.md`](credentials.md#two-smtp-paths-and-why-one-is-25). ### Why the gateway talks to Dovecot directly (plaintext loopback) Mailu's **front** (nginx mail proxy) pre-authenticates every IMAP/SMTP login against Mailu's user DB before proxying to Dovecot — and it rejects the Dovecot master-user login form `*gateway`. So the gateway must reach **Dovecot directly**, bypassing the front. The `imap` container has no TLS cert (only the front does), so the bypass is plaintext over loopback — safe because the connection (and the master password) never leave the host. Wiring: - Publish Dovecot's IMAP on loopback (docker-compose.override.yml): `imap.ports: ["127.0.0.1:14143:143"]`. - The Dovecot master user is defined in `data/overrides/dovecot/dovecot.conf` (Mailu includes exactly that filename — *not* `*.conf`): ``` auth_master_user_separator = * passdb { driver = passwd-file; master = yes; args = /overrides/master-users } ``` with `data/overrides/dovecot/master-users` holding `gateway:{SHA512-CRYPT}$6$…` (the hash of `AGENTBBS_MAIL_MASTER_PASS`). The file must be **world-readable (644)** — Dovecot reads it as a non-root user, and 640 root:root yields a `temp_fail`. Do **not** add `result_success = continue` (that would also require the target user's own password); the target mailbox comes from userdb. - Point the gateway at it: `AGENTBBS_MAIL_IMAP_ADDR=127.0.0.1:14143` + `AGENTBBS_MAIL_IMAP_PLAINTEXT=1`. ## Sending mail from the BBS (verify codes + notifications) The join@ verification code and signup notifications use `internal/mail` (the `AGENTBBS_SMTP_*` knobs), separate from the per-member mailbox client. Point them at the local Mailu relay so codes actually send: ``` AGENTBBS_SMTP_HOST=127.0.0.1 AGENTBBS_SMTP_PORT=25 AGENTBBS_SMTP_FROM=bbs@bbs.profullstack.com # user/pass omitted: the co-located relay accepts local submission unauthenticated ``` ## Provisioning member mailboxes Provisioning is automatic at `join@` verification. To create or backfill by hand: ```bash deploy/mailu/provision-mailbox.sh alice # creates alice@bbs.profullstack.com ``` (Set `MAIL_DOMAIN=bbs.profullstack.com` for the script, since the address domain differs from the server host.) The Dovecot **master user** (`gateway`) authenticates as any member with the login form `alice*gateway` + the master password — exactly what `internal/mailbox`'s IMAP adapter sends. See [`deploy/mailu/README.md`](../deploy/mailu/README.md) for details. ## Webmail only for members Members are pointed at `https://mail.profullstack.com` (Roundcube) and the BBS `Mail` client — they are not given the Mailu admin UI or alias management. Admin is operator-only.