eSlider 2bb6ed310e feat(dovecot): shared mailboxes — info@ reads all accounts (#79)
Dovecot ACL + shared namespace (shared/%%u/), acl_shared_dict in mail-state,
idempotent user-patches.sh grants info@ read-only (lookup read) on every
mailbox of ano@, andriy.oblivantsev@, postmaster@ and pre-subscribes the
shared folders so Roundcube's subscribed folder list shows them. Delivery and
passwords untouched; other accounts see no shared folders.
2026-09-01 16:02:52 +01:00

mail-server

Docker Compose mail stack for mail.produktor.io on arc-01, based on docker-mailserver (DMS).

Service Container Ports
Mail server (DMS) mailserver 25 (SMTP), 465 (SMTPS), 587 (Submission STARTTLS), 143 (IMAP STARTTLS), 993 (IMAPS)
Webmail (Roundcube) webmail 127.0.0.1:19944 / 172.17.0.1:19944 (HTTP, behind NPM)
Account admin — (removed) —

Accounts

Source of truth is file-based: config/postfix-accounts.cf (SHA512-CRYPT hashes). Current mailboxes:

  • info@produktor.io
  • andriy.oblivantsev@produktor.io
  • ano@produktor.io
  • postmaster@produktor.io
  • postman@produktor.io

Passwords live in .env (INFO_PASSWORD, ANDRIY_PASSWORD; ano@ uses GATOR_MAIL_PASS in the gator repo .env). Do not commit .env.

Web UI (Roundcube)

Webmail runs as the webmail service (official roundcube/roundcubemail image) and is reachable at https://mail.produktor.io (alias https://webmail.produktor.io) via Nginx Proxy Manager (proxy host 66 → 172.17.0.1:19944, Let's Encrypt).

Login: any mailbox address from the table above + its real password. The UI shows one mailbox per login; the account list is the postfix-accounts.cf file (see Accounts).

Since the shared-mailbox setup (below) info@ additionally sees every other mailbox under Shared/ and can read them — one login covers the whole account list.

Connection details used by the webmail (IMAP/SMTP):

  • IMAP: mail.produktor.io:143 STARTTLS (or :993 SSL)
  • SMTP submission: mail.produktor.io:587 STARTTLS, AUTH required

Host note: the webmail must connect to the DMS container via the FQDN mail.produktor.io (Docker embedded DNS resolves it to the mailserver container inside the compose network). Connecting to the bare container alias mailserver fails TLS peer-name verification, because the DMS certificate is issued for mail.produktor.io.

Manage

docker compose up -d            # start mailserver + webmail
docker compose logs -f webmail  # webmail logs
docker exec webmail sh          # shell into webmail

The webmail stores its sqlite database (addressbook, settings) in data/roundcube/db/. ROUNDCUBEMAIL_DES_KEY (session encryption) must be set in .env — compose fails without it.

Shared mailboxes (единый вход info@)

info@produktor.io can read all mailboxes (ano@, andriy.oblivantsev@, postmaster@) as read-only shared folders — one login in Roundcube covers the whole account list. Delivery is unchanged (no aliases, no redirects); other accounts keep their own passwords and full rights.

How it works (Dovecot 2.3 ACL + shared namespace):

  • config/dovecot.cf (→ /etc/dovecot/local.conf) enables the acl plugin, adds a shared namespace shared/%%u/ (list=children, read index per reader via INDEXPVT), and points acl_shared_dict to /var/lib/dovecot/db/shared-mailboxes.db (persistent via mail-state).
  • config/user-patches.sh re-applies read-only (lr) ACLs from each shared owner's mailboxes to user=info@produktor.io via doveadm acl set — the only way Dovecot records the share in the shared dictionary — and pre-subscribes the shared folders for info@. DMS runs it on the first start of each container instance (plain docker compose restart skips the setup step by design); ACLs, the shared dict and subscriptions persist in mail-state/maildirs, so nothing is lost on restarts. Idempotent — safe to run manually: docker exec mailserver /bin/bash /tmp/docker-mailserver/user-patches.sh.

Upgrade behavior (image :latest): the config survives container recreation because both files live in the mounted config/. On image upgrade the entrypoint re-applies dovecot.cf and runs user-patches.sh again on the new container's first start, so ACLs and subscriptions are recreated. The only state kept outside the repo is shared-mailboxes.db (inside data/mail-state/); if it is lost, the next (re)creation rebuilds it via doveadm acl set.

Limitation (Dovecot semantics): new mailboxes created by an owner after the last start do not inherit the share (no ACL inheritance); they appear for info@ after the next container start.

Reverse proxy (NPM)

mail.produktor.io is a proxy host in Nginx Proxy Manager (provider container, see the gitea repo): forward http://172.17.0.1:19944, Let's Encrypt cert (SAN: mail.produktor.io, webmail.produktor.io), SSL forced, HTTP/2.

TLS

DMS uses a Let's Encrypt certificate for mail.produktor.io mounted from tls/letsencrypt/mail.produktor.io/ (SSL_TYPE=letsencrypt).

S
Description
docker-mailserver stack for produktor.io (compose + config + scripts)
Readme
130 KiB
Languages
Shell 100%