Email

Last updated

Operator workflow? See Mail Deliverability, Mail Throttles, Email Logs, Email Queue. Tenant workflow? See Email (User), Mailboxes, Forwarders. This page covers the underlying mail stack.

Jabali’s mail stack is Stalwart (SMTP submission + MTA + JMAP + IMAP, single process) with Bulwark — a Next.js JMAP-native webmail client — running behind nginx on a Unix socket. Bulwark also acts as the rate-limit / spam policy bridge for outbound mail.

Per-mailbox

  • Authentication: per-mailbox Argon2id-hashed password stored by Stalwart.
  • Quota: per-mailbox MiB, enforced by Stalwart.
  • Webmail: https://mail.<domain>/ (Bulwark). SSO bridge in /jabali-panel/mail/mailboxes (M6.2, uses the M22 self-deleting jabali-sso-*.php file, not the failed M22 magic-link/mu-plugin path).
  • IMAP / SMTP submission: imap.<panel-hostname>:993 (TLS), smtp.<panel-hostname>:465 (TLS) or :587 (STARTTLS).
  • Autoconfig / autodiscover: Apple mobileconfig, Thunderbird autoconfig.xml, Outlook autodiscover.xml (see platform/mail-autoconfig.md).
  • mailboxes.send_only — a mailbox flagged send-only can authenticate for SMTP submission but does not receive mail. queryRecipient is filtered by send_only = 0, queryLogin is not. Use it for per-service credentials (monitoring, backups, appliances) that only need to send; leaking one credential rotates one account, not a real inbox.
  • mailboxes.display_name — Bulwark reads this as the principal description; it renders as the From name in outgoing mail.

Webmail toggle

Bulwark is gated by three flags, AND-ed by the webmail reconciler. A vhost is active only if all three are on. Mail delivery (IMAP / SMTP / JMAP) is unaffected — this is a UI toggle.

ScopeColumnDefaultSet via
Globalserver_settings.webmail_enabledonPATCH /api/v1/server-settings (admin)
Per-domaindomains.webmail_enabledonPATCH /api/v1/domains/:id (owner or admin)
Per-userusers.webmail_enabledonPATCH /api/v1/users/:id (admin)

Turning off any tier tears down the vhosts owned by that scope on the next reconciler tick.

Send-as delegations

mailbox_send_delegations grants a delegate mailbox permission to send as a grantor mailbox without receiving the grantor’s mail.

  • Composite key (delegate_mailbox_id, grantor_mailbox_id); cascades on either mailbox delete; survives renames.
  • Reconciler materialises the pairs into Stalwart’s MtaStageAuth.mustMatchSender expression — for verified pairs mustMatchSender = false (allow), for everything else the built-in anti-spoofing check stays intact.
  • queryRecipient is unchanged: the grantor keeps receiving its own mail.

REST:

  • GET /api/v1/mailboxes/:mbid/send-as — list grantors a delegate can send as.
  • POST /api/v1/mailboxes/:mbid/send-as — add.
  • DELETE /api/v1/mailboxes/:mbid/send-as/:grantorId — remove.

Each change fires mail.sendas.reconcile to sync Stalwart.

Mail groups: internal-only

mail_groups.internal_only (default false). When on, the group address accepts mail only from senders in the same domain; external senders are rejected. Enforced by a Sieve delivery filter applied via the agent verb mailgroup.apply. Toggle in POST /api/v1/domains/:id/mailgroups or PATCH /api/v1/mailgroups/:gid.

Per-domain mail features (M6.5)

Mail tabs (/jabali-panel/mail/<tab>):

  • Mailboxes: create, change password, set quota, delete.
  • Forwarders: forward alice@example.com to one or more external addresses.
  • Autoresponders: vacation responder per mailbox, with start/end window and subject template.
  • Catch-all: send unmatched recipients to a chosen mailbox or :drop / :reject.
  • Disclaimer: append HTML / plaintext disclaimer server-side to outbound mail per domain (HTML coverage validation pending on test VM, ADR-0052).
  • Shared Folders: create IMAP shared folders for the team; manage ACLs.
  • Logs: live tail of in / out deliveries for the domain.

Per-domain deliverability (admin)

/jabali-admin/mail/deliverability, for every domain, shows:

  • DKIM key presence + DNS publication state
  • SPF record presence + soft/hard fail
  • DMARC record + policy
  • MTA-STS policy + MX host alignment (ADR-0109, per-domain MTA-STS)

Buttons:

  • Rotate DKIM: generate a new DKIM key, publish DNS record, retire the old key on the configured grace period.

Outbound throttles

/jabali-admin/mail/throttles (M47 Wave 3): per-sender + per-domain rate limit (msgs / minute, msgs / hour, recipients / message). Bulwark enforces; CrowdSec sees throttle hits and can escalate.

Stalwart reports

Stalwart ingests inbound TLS-RPT, MTA-STS-RPT, and DMARC aggregate reports (M47 Wave 2) into the panel DB. Visible per-domain under Deliverability.

Expression filters (M47 Wave 3v2)

Admin-defined Stalwart expressions for routing / drop / quarantine. UI under Server Settings → Mail.

Architecture choices

  • DB-as-truth, reconciler-converged. Mailboxes / forwarders / autoresponders are panel-DB rows; Stalwart’s JMAP API is called by the agent on every change.
  • Self-deleting SSO file for webmail one-click (M22 rework, ADR-0040). Not a magic-link plugin, not a session cookie hand-off, the panel writes /jabali-sso-<43-char-nonce>.php into the mail vhost, redirects the user to it; the file flocks + unlinks itself on first hit or after 60 s.
  • Unix sockets only. Stalwart admin HTTP pinned to 127.0.0.1:8080; MariaDB skip-networking; nothing exposes mailbox auth over TCP outside the SMTP/IMAP ports themselves.

CLI

jabali mailbox list --domain example.com
jabali mailbox create user@example.com --quota-mib 1024
jabali mailbox set-quota user@example.com 2048
jabali mailbox passwd user@example.com
jabali mailbox delete user@example.com