Skip to content

Latest commit

 

History

History
173 lines (137 loc) · 8.08 KB

File metadata and controls

173 lines (137 loc) · 8.08 KB

Mail — self-hosted Mailu

AgentBBS gives every verified member (free and paid alike) a real mailbox at <name>@bbs.profullstack.com, reached two ways:

  • Webmailhttps://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 <name>@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

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.

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 <addr>*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:

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 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.