Mail

Mail server with domains, mailboxes, aliases, forwarders, catch-all, autoresponders, DKIM/SPF/DMARC/MTA-STS and queue control.

In preview mail version 0.1.0 Part of Mail and webmail

Actions

55 actions, callable from the panel, the command palette and the API as POST /api/v1/a/<id>. Internal actions used between modules are not listed.

ActionWhat it doesRiskPreview
mail.server.enable Enable mail on a server critical
mail.hostname.ensure Certificate for a mail hostname medium
mail.server.config.apply Apply mail server settings high
mail.config.status Hand-edited mail files (read) low No dry run
mail.config.adopt Keep a hand edit on top of the panel's configuration medium
mail.config.reapply Render the panel's version again medium
mail.config.reset Reset a file to defaults medium
mail.config.unmanage Hand a file off to the administrator high
mail.server.selftest Mail server self-test (read) low No dry run
mail.server.get Mail server settings (read) low No dry run
mail.reconcile Reconcile mail servers medium No dry run
mail.domain.add Add a mail domain medium
mail.domain.list List mail domains (read) low No dry run
mail.domain.get Get a mail domain (read) low No dry run
mail.domain.delete Remove a mail domain high
mail.domain.dns.check Check mail DNS records (read) low No dry run
mail.domain.dns.fix Publish mail DNS records medium No dry run
mail.dkim.rotate Rotate the DKIM key medium No dry run
mail.dmarc.set Set DMARC policy medium No dry run
mail.mtasts.set Set MTA-STS and TLS-RPT medium No dry run
mail.catchall.set Set the catch-all medium No dry run
mail.mailbox.create Create a mailbox medium
mail.mailbox.list List mailboxes (read) low No dry run
mail.mailbox.update Change a mailbox medium No dry run
mail.mailbox.delete Delete a mailbox high
mail.mailbox.password.set Reset a mailbox password medium No dry run
mail.alias.set Create or change an alias low No dry run
mail.alias.list List aliases and forwarders (read) low No dry run
mail.alias.delete Delete an alias low No dry run
mail.forwarder.set Create or change a forwarder low No dry run
mail.forwarder.delete Delete a forwarder low No dry run
mail.autoresponder.set Set an autoresponder low No dry run
mail.autoresponder.delete Delete an autoresponder low No dry run
mail.queue.list List the mail queue (read) low No dry run
mail.queue.retry Queue: retry messages medium No dry run
mail.queue.hold Queue: hold messages medium No dry run
mail.queue.release Queue: release messages medium No dry run
mail.queue.delete Queue: delete messages high No dry run
mail.queue.flush Queue: flush medium No dry run
mail.limit.set Set outbound limits medium No dry run
mail.account.hold Hold outbound mail of a mailbox medium No dry run
mail.account.release Release a held mailbox medium No dry run
mail.sso.prepare Single sign-on credentials for webmail medium No dry run
mail.client.config Mail client settings (read) low No dry run
mail.config.reset_all Reset the whole service to defaults high
mail.config.snapshot.list Snapshots before resets (read) low No dry run
mail.config.snapshot.restore Undo a reset (restore a snapshot) high
mail.config.history.list Earlier versions of a file (read) low No dry run
mail.config.history.diff Compare an earlier version (read) low No dry run
mail.config.history.restore Restore an earlier version of a file high
mail.unmanaged.scan Scan now for objects made outside the panel low No dry run
mail.unmanaged.list Objects made outside the panel (read) low No dry run
mail.unmanaged.ignore Ignore or un-ignore an unmanaged object low
mail.unmanaged.adopt Adopt an object made outside the panel medium
mail.unmanaged.remove Remove an object made outside the panel high

Permissions and limits

Permissions

  • mail.server.manage Manage the mail server
  • mail.server.view View the mail server
  • mail.domain.manage Add and remove mail domains
  • mail.domain.view View mail domains
  • mail.dkim.manage Manage DKIM keys
  • mail.dmarc.manage Manage DMARC policy
  • mail.mtasts.manage Manage MTA-STS and TLS-RPT
  • mail.mailbox.manage Create, change and delete mailboxes
  • mail.mailbox.view View mailboxes
  • mail.mailbox.password Reset mailbox passwords
  • mail.alias.manage Manage aliases, forwarders and the catch-all
  • mail.autoresponder.manage Manage autoresponders
  • mail.queue.view View the mail queue
  • mail.queue.manage Act on the mail queue
  • mail.limit.manage Set outbound limits
  • mail.abuse.manage Hold and release senders
  • mail.client.view View mail client settings

Plan limits

  • mail.domains Mail domains
  • mail.mailboxes Mailboxes
  • mail.forwarders Forwarders

Engineering notes

Generated from modules/mail/docs.md at build d90e9e2. These are the notes the engineers keep next to the code: precise, technical, and honest about what is not done yet.

Desired state lives in the panel DB (mail_* tables, mailboxes). Every mutation re-renders the whole node configuration in the control plane (cp/render.go, templates in templates/) and sends it to the agent with mail.files.apply; the agent writes only a fixed set of file kinds (sdk/agentop/mail.go), atomically, validates with postfix check / doveconf -n / rspamadm configtest / sievec, restores the previous files on failure, and reloads only the services whose files changed. Idempotent; mail.reconcile re-applies everything.

Decisions

  • Storage: all mailboxes are virtual users owned by one vmail system user: /var/vmail/<domain>/<local>/Maildir. Per-account Unix ownership was rejected: Dovecot LMTP/IMAP run as one uid and hosting accounts are shell users who should not read mail. Quotas are Dovecot maildir quotas (userdb_quota_rule), not filesystem quotas.
  • Passwords: bcrypt ({BLF-CRYPT}$2a$10$) hashes in the panel DB, exported to a Dovecot passwd-file (/etc/dovecot/rc-users, 0640 root:dovecot). One password store: Postfix authenticates through Dovecot SASL. Argon2 was skipped (memory cost per login under Dovecot's auth worker, libsodium dependency).
  • Maps: Postfix texthash: files (no postmap step) for domains, mailboxes, aliases, sender-login, held senders.
  • DKIM: per-domain RSA keys generated on the node (/var/lib/rspamd/dkim/<domain>.<selector>.key, 0640 root:_rspamd); the private key never enters the DB, results, logs or events (only the public key is stored). Rspamd dkim_signing + arc use dkim_selectors.map. Ed25519 not yet.
  • Rotation (two steps): mail.dkim.rotate makes a pending key and publishes it; activate=true (or immediate) switches signing and keeps the old selector published 7 days (retired, removed by mail.reconcile).
  • TLS: self-signed cert at /etc/rc-mail/tls/{fullchain,privkey}.pem until ssl installs one at the same path.
  • Ports: 25 (STARTTLS), 587 (STARTTLS, auth), 465 (implicit TLS, auth), 143/993 IMAP, 4190 ManageSieve. Submission requires login = sender (reject_sender_login_mismatch) and refuses held / outbound-disabled senders.
  • Sieve layout (mail/012): two owners, two files. The mail module owns ~/.rc-vacation.sieve (the out-of-office vacation, date window via the date extension with a UTC-offset timezone; a comment-only script when off), rendered for every mailbox on every sync and loaded by Dovecot as a per-user sieve_before script (after the global spam filter, so the reply runs ahead of the user's rules and a rule that files or discards a message never silences it). The user's active script ~/.dovecot.sieve belongs to ManageSieve clients (webmail saves its rules as script rc-webmail and activates it); the mail module never writes or includes it, so neither side can overwrite the other and there is no config_drifted on a file nobody edited. Mailboxes written by the old layout are migrated by the agent on the next sync: the old managed wrapper is removed, and webmail rules that had overwritten it move to ~/sieve/rc-webmail.sieve and become the active script again.
  • Catch-all (mail/011): @domain -> target sits in virtual_alias_maps, which Postfix reads before the mailbox map and again for every alias destination. Each mailbox of a catch-all domain therefore gets an identity line (info@d info@d) ahead of it, so existing mailboxes and alias destinations are never swallowed; a mailbox that is itself an alias key keeps that mapping.
  • Auto-provision: domains.created -> mail.on_domain_created only when the single mail server has auto_provision on (never publish MX for a domain nobody asked mail for). domains.deleted removes the mail domain but keeps the maildirs on disk (orphans under /var/vmail/<domain>).
  • Limits: mail.domains, mail.mailboxes, mail.forwarders (Reserve/Release, compensated on failure).

DNS records (per mail domain)

MX @ -> 10 <hostname>.; SPF @ TXT v=spf1 mx ~all; DKIM <selector>._domainkey TXT v=DKIM1; k=rsa; p=… (pending/ active/retired selectors); DMARC _dmarc TXT (starts p=none); MTA-STS _mta-sts TXT + mta-sts CNAME -> host and TLS-RPT _smtp._tls TXT when enabled; autoconfig and autodiscover CNAME -> host. Publishing calls dns.record.upsert_managed with {domain, owner_module:"mail", key, record:{name,type,ttl,value}} for each record (assumption from docs/specs/dns.md §6; zone resolved by domain since w2-01 may use zone_id). If that call fails for any reason (module missing, zone not hosted here) the action returns dns: {published:false, records:[…]} to copy. The DKIM value can exceed 255 chars: the dns module must split it into TXT strings. MTA-STS policy text is written to /var/lib/respirecloud/mail/mta-sts/<domain>/.well-known/mta-sts.txt for the web module to serve at https://mta-sts.<domain>/.well-known/mta-sts.txt.

Verified in the lab (2026-10-09, FAKENET=full, Ubuntu 24.04, Postfix 3.8.6, Dovecot 2.3, Rspamd 3.8.1)

enable (apt + config + self-test all green) · domain add (DKIM + files) · inbound from "outside" to a mailbox read over IMAPS · bad IMAP password refused · submission 587 with auth -> relay -> Mailpit, DKIM-Signature + ARC seal present and dkim.verify = True (also after rotation, new selector) · sender mismatch 553 · open relay refused · no auth without TLS · alias, forwarder with keep-copy, catch-all, loop rejected, autoresponder reply received, held and send-disabled senders rejected, quota-exceeded -> 552 bounce · usage via doveadm · queue list/hold/release/retry/delete/flush · dry-run config diff · domain deletion event keeps the maildir.

Not done (follow-ups)

Relays/smarthost per domain, IP pools, suppression lists, send API/webhooks, distribution lists, raw Sieve/filters UI, app passwords, DMARC report parsing, deliverability checker, mail logs, IMAP import, POP3, DANE, per-mailbox/domain outbound limit enforcement (stored; only node limit via Rspamd ratelimit + Redis, and a per-IP submission rate are enforced), auto-hold on abuse, ClamAV, mailbox rename/move, mobileconfig/QR, UI, quota warnings at 80/90 %.