Documentation · Hub

Mail

How the Hub provisions and authenticates against @ancientholdings.eu mailboxes for Baron-tier members.

Tier gate

Mail is a Baron-tier feature. Operators do not get mailboxes.

Graduate to Baron tier to receive an @ancientholdings.eu mailbox. Until then, the Account menu offers no Mail entry and the /hub/mail surface returns 404 — the gate is applied uniformly across the navbar widget, the API, and the hub pages themselves.

Mailcow architecture

Mail is hosted on the same VPS as the website + worker, by a self-managed Mailcow stack: Postfix for SMTP, Dovecot for IMAP/POP3, Rspamd for anti-spam + DKIM signing, ClamAV for virus scanning, an nginx front for SOGo + admin, and an ACME sidecar that renews the mail TLS certificates automatically. The Hub talks to Mailcow over localhostIMAP (port 993) and the Mailcow Admin API for provisioning — never over the open internet.

Baron-tier login flow via Navbar AccountMenu

The Account menu (gold avatar, top-right of every page) is the single sign-in surface. When a visitor clicks it, the menu inspects the active Mail session via useMailSession() and renders one of three states: signed-out (login form), signed-in (account list with switch + sign-out), or signed-in-with-Hub (extra row pointing at /hub).

For users whose admin role resolves to baron, the menu surfaces a Mail entry alongside the Hub link. Operator-tier and client-tier accounts see no Mail entry at all — the menu’s tier check is the first defensive layer; the second is the API gate on /api/mail/* and /hub/mail itself.

First-time prospects who sign in with a non-Baron account see a “graduate to Baron” call-to-action in the menu footer pointing at the tier-upgrade flow. Reference: lib/useMailSession.tsx (the React context), components/AccountMenu.tsx (the menu surface).

Multi-account session management

A single Baron may hold several @ancientholdings.eu mailboxes simultaneously — one per role, project, or cluster — and switch between them without re-entering credentials. The session is stored in an iron-session cookie named ah_mail as a MailSessionData with an accounts: MailAccount[] list and a single activeEmail pointer.

Adding a mailbox calls /api/mail/login (which validates credentials and pushes onto the list); switching calls /api/mail/activate (cookie-only flip, no credential re-check); signing one out calls /api/mail/logoutwith the target email. This mirrors Google’s account-switcher UX: many identities, one in scope at a time, no re-authentication on switch. Reference: lib/mail/session.ts, MailAccount type.

Mailbox password change

Password changes are driven from inside the Hub, not from SOGo’s settings. The in-app flow rotates the password in Mailcow via the Admin API and, on success, re-encrypts the new credential into the secrets vault so the scrypt fallback path stays in sync with what Dovecot will accept on the next login.

This is documented as design intent. The endpoint may not yet be wired in the version you’re reading — the constraint is that any future password-change surface must update both stores atomically (Mailcow first, vault second, with rollback on either failure) so the dual-auth invariant below holds across the rotation window.

Dual-auth model

On every login the Hub authenticates against Mailcow IMAP first via verifyImapCredentials() (see lib/mail/imap.ts). If IMAP returns { ok: true }, the credential is correct and a fresh scrypt hash is cached in the secrets vault for resilience.

If IMAP returns { ok: false, reason: "unreachable" } (DNS, refused connection, timeout), the Hub falls back to the scrypt-hashed credential cached on the previous successful login. Sessions established on the fallback path are flagged with mailUnreachable: true, which the Account menu surfaces as an amber banner: mail features (notifications, display-name lookups) gracefully degrade until Mailcow is reachable again.

An { ok: false, reason: "invalid_credentials" } response from IMAP is never overridden by the fallback — a real wrong-password gets the user a real rejection. The fallback only fires for infrastructure outages.

← back to Hub