Skip to main content
Available only when APP_EXPERIMENTAL=mail is set on the control plane and a mail app from the mail-mox template is running.

Who owns what

Each mail domain belongs to one team. Mailboxes and aliases on that domain belong to the same team. Members of other teams do not see them: the API answers 404, as if they did not exist. A second team cannot claim a domain that is already taken. Levelrail keeps only metadata: the address, the owning team, an optional linked Levelrail user, whether the mailbox is suspended, who created it and when. Passwords live in the mail server only, and the control plane never reads message content.

Claim a domain

The domain must already be served by the mail server. A team owner or admin with the root ability claims it:

Mailboxes

  • add and passwd print a generated password once. Levelrail does not store it, so copy it and hand it to the mailbox user. Users cannot choose their own password. The length comes from APP_MAIL_PASSWORD_LENGTH (default 24).
  • suspend blocks IMAP, SMTP submission and webmail logins. Mail keeps arriving.
  • When a member leaves a team, is removed from it, or is disabled through SCIM, mailboxes linked to them in that team are suspended. Nothing is deleted automatically.
  • If the mail server is unreachable at that moment, the mailbox is marked suspend_pending, the audit log records it, and the suspend is applied on the next mail call that reaches the server. mailboxes list shows the pending state.
  • --linked-user must be a member of the team that owns the domain.
  • delete asks for your password, needs the address typed again, and refuses a mailbox that still holds mail unless you pass --delete-mail. Suspending is usually the better choice.
  • Each new mailbox reports a send cap from APP_MAIL_DEFAULT_SEND_CAP_PER_HOUR (default 200). It is recorded as data only and is not enforced yet.

Aliases

Alias targets must be mailboxes your team owns.

Queue

The queue view shows the sender account, recipient domain, attempts, next attempt and last error. It never shows subjects or bodies. Listing needs the read:sensitive ability. Hold and release apply to the whole queue unless you pass --ids. Release asks for your password. The queue is an instance wide view, so only instance members can use it.

Permissions

IAM policies can name mail:service:<app>, mail:domain:<domain> and mail:mailbox:<address>. A Deny on a domain hides every mailbox on it. Every change is written to the audit log with the address, never a password.

System emails

levelrail-cli mail system-email enable <domain> (or System emails on the Mail page) sends invites, password resets, alerts and status page notices as noreply@<domain> through this mail server. It creates or reuses the noreply mailbox with a generated password that is sealed straight into the email provider settings and never shown, then sends a test to your own address (--to to change it). The provider submits to the mail server’s private network address on port 587 (STARTTLS, or --port 465), trusting exactly the certificate it saw at enable time (a pinned SHA-256 fingerprint), so the self-signed default certificate works without turning verification off. An existing provider is never replaced silently: pass --replace-existing. disable puts the earlier provider back and suspends (never deletes) the mailbox; rotate sets a new sealed password; status shows the sender, the last test and whether SPF, DKIM and DMARC pass. Until DNS passes and the server’s PTR matches its mail host, mail to outside recipients lands in spam or is rejected. Enable, rotate and test are rate limited per caller (APP_MAIL_SYSTEM_EMAIL_ACTIONS_PER_HOUR, default 10) and every action, status included, needs an instance admin; enable, disable, rotate and test also need step-up. The DNS check behind status is cached for APP_MAIL_SYSTEM_EMAIL_DNS_CACHE_SECONDS (default 60). The noreply mailbox is marked as the system account: it is hidden from team mailbox lists, cannot be an alias target, and the team mailbox and alias routes (password reset, suspend, resume, delete, aliases) refuse it. Only mail system-email manages it. If the provider was switched to something else after enable, a later enable asks for --replace-existing again, disable leaves the newer provider and its credentials alone and only suspends the mailbox, and rotate changes only the system email’s own sealed password. A failed enable puts the earlier credentials, provider and mailbox state back. The engine is trusted only when the instance identity is readable, its container carries this instance’s label and the address is on the mail app’s own network; otherwise enable and test are refused. The pin is bound to that address: if the mail server’s address changes (redeploy or restart), status reports pin: mismatch and test refuses; re-run enable to re-pin. Residual risk: the submission hop is plain IP on a Docker bridge, so a container on the same bridge with NET_RAW could try ARP spoofing. The default container hardening (APP_CONTAINER_HARDENING=enforce) drops all capabilities including NET_RAW; with warn or off, Docker’s default set grants it. The certificate pin still makes a spoofed listener fail the TLS handshake.

Limits

  • mox keeps one password per account, so revoking one device means resetting the password for all of them.
  • mox has no per hour send limit and no app passwords.
Last modified on October 31, 2026