Default container environment variables
diff --git a/mie-opensource-landing/docs/admins/core-concepts/external-domains.md b/mie-opensource-landing/docs/admins/core-concepts/external-domains.md
index 967723fb..8d6d5a76 100644
--- a/mie-opensource-landing/docs/admins/core-concepts/external-domains.md
+++ b/mie-opensource-landing/docs/admins/core-concepts/external-domains.md
@@ -103,6 +103,18 @@ On container or service deletion, cross-site A records are cleaned up automatica
When creating a container service, users select an external domain and specify a subdomain (e.g., `app` for `app.example.com`). All external domains are available regardless of which site the container is on. See the [Web GUI guide](../../users/creating-containers/web-gui.md) for details.
+## Mail
+
+Every domain gets an RSA-2048 DKIM key at creation. Mail stays off until an admin enables it on the domain's edit page:
+
+1. Publish the DNS records shown in the **Mail** section (SPF, DKIM, DMARC; MX to receive). Copy buttons provided.
+2. **Check DNS** — verified against public resolvers (configurable in [Settings](../email.md#settings)).
+3. **Enable mail** — refused (`dns_not_configured`) until SPF + DKIM + DMARC verify. A failing MX only blocks *receiving*.
+
+Gates: `enabled + DNS verified = can send` (accounts can be created and SMTP login works); `enabled + MX verified = can receive`. Re-verification is manual — re-run Check DNS after changing records. Disabling keeps keys and config; accounts stop authenticating immediately, IMAP stays readable.
+
+PTR problems and missing `postmaster@`/`abuse@` mailboxes are warnings, not blockers. See [Email Service](../email.md) for the mail host and settings.
+
## Security
- Issue Cloudflare User API Tokens with minimum scope (`Zone:DNS:Edit` for the target zone only)
diff --git a/mie-opensource-landing/docs/admins/deploying-agents.md b/mie-opensource-landing/docs/admins/deploying-agents.md
index 133c93f4..0a806bae 100644
--- a/mie-opensource-landing/docs/admins/deploying-agents.md
+++ b/mie-opensource-landing/docs/admins/deploying-agents.md
@@ -70,6 +70,7 @@ lxc.environment = API_KEY=
| `SITE_ID` | Numeric site ID from the manager (visible in the URL when viewing the site) |
| `MANAGER_URL` | Base URL of the manager container (e.g., `http://192.168.1.10:3000`) |
| `API_KEY` | API key from an admin account. Used to authenticate check-ins. |
+| `AGENT_SERVICES` | Optional. Comma-separated service groups to run: `nginx`, `dnsmasq`, `mail`. Default `nginx,dnsmasq`. Add `mail` to make this host the [mail host](email.md) (install the mail packages first). |
## Volume storage for persistent volumes
diff --git a/mie-opensource-landing/docs/admins/email.md b/mie-opensource-landing/docs/admins/email.md
new file mode 100644
index 00000000..bb5b926d
--- /dev/null
+++ b/mie-opensource-landing/docs/admins/email.md
@@ -0,0 +1,53 @@
+# Email Service
+
+Authenticated SMTP submission + IMAP hosted by an agent (Postfix + Dovecot + OpenDKIM), with DKIM signing, per-domain enablement, and RFC 8058 one-click unsubscribe. User-facing usage is covered in [Email Accounts](../users/email-accounts.md); per-domain DNS setup in [External Domains](core-concepts/external-domains.md#mail).
+
+## The Mail Host
+
+Exactly one agent runs the mail stack at a time (mailboxes are local Maildir). The claim goes to the first agent that checks in with the `mail` service group enabled and all packages installed; other agents show **Standby** on the Agents page. Release the claim from the Agents page (**Release mail host**) — the next qualifying check-in takes it.
+
+The mail IP is the `externalIp` of the mail host's site. Dovecot and Postfix read accounts **live** from the manager's database through three read-only views (`mail_accounts_v`, `mail_senders_v`, `mail_suppressions_v`), so disabling an account or domain takes effect immediately (bounded by Dovecot's 1-minute auth cache).
+
+## Settings
+
+Settings → Mail service:
+
+| Setting | Purpose |
+|---------|---------|
+| Mail hostname | HELO name and MX target. Set to the forward-confirmed PTR of the mail IP — use **Suggest from PTR**. |
+| Relayhost (+ credentials) | Optional smarthost. Requires **SPF include**, since mail then leaves from the relay's IPs |
+| SPF include | `include:` added to the suggested SPF records |
+| DNS check resolvers | Resolvers used by Check DNS (default `1.1.1.1`, `8.8.8.8`) |
+| Mail DB host | Only when the mail host cannot reach the DB over the local socket |
+| Self-managed site id | Mail IP source when no agent runs mail (option C below) |
+| Default quota / message size limit | `1024` MB and `25` MB when empty |
+
+One-click unsubscribe links (`/u/`) use the mail host agent's `MANAGER_URL` as the base — set it to the manager's public URL.
+
+## Where Mail Runs
+
+**A. Manager (default).** The manager image ships the packages and sets `AGENT_SERVICES=nginx,dnsmasq,mail`. DB access uses Postgres peer auth (`pg_ident` maps the `postfix`/`dovecot`/`opensource-mail` OS users to the `mail_postfix`/`mail_dovecot` roles) — no passwords.
+
+**B. Another agent host.**
+
+1. Install the suggested packages: `apt install opensource-mail-helper postfix postfix-pgsql dovecot-core dovecot-imapd dovecot-lmtpd dovecot-pgsql opendkim` (use the `-mysql` variants on MySQL/MariaDB).
+2. Install the per-domain certs under `/etc/ssl` (same layout as nginx).
+3. Set the **Mail DB host** setting, give the `mail_postfix`/`mail_dovecot` roles passwords (`ALTER ROLE mail_postfix WITH PASSWORD '...'`), and put `MAIL_POSTFIX_DB_PASSWORD` / `MAIL_DOVECOT_DB_PASSWORD` in the agent's `/etc/environment`.
+4. Make sure the agent checks in over HTTPS — the manager refuses to send the mail snapshot (DKIM keys, relay credentials) over plaintext (`insecure_transport`).
+5. Remove `mail` from the manager's `AGENT_SERVICES`, add it on the new host (see [Deploying Agents](deploying-agents.md)). It takes the claim.
+6. `rsync /var/vmail` from the old host.
+7. Repoint the port forwards (25, 143, 465, 587, 993) and re-run Check DNS on each domain — the mail IP follows the host's site automatically.
+
+**C. Self-managed MTA (no agent runs `mail`).**
+
+1. Set **Self-managed site id** to the site whose `externalIp` sends mail.
+2. Point Dovecot at `mail_accounts_v` and Postfix at `mail_senders_v` + `mail_recipients_v`, with Dovecot's `quota-status` guarding RCPT. The views are a documented, stable contract — see [Database Schema](../developers/database-schema.md#mail-views).
+3. Export DKIM keys per domain (`GET /api/v1/external-domains/{id}/mail/dkim` — admin-only, audited in the manager log) and use the agent templates under `agent/templates/mail/` as a reference. Run the `opensource-mail-helper` milter yourself for suppressions + unsubscribe headers (it only needs the DB views and the unsubscribe keys in its `config.local.json`), or skip them.
+
+## DB Roles
+
+Migration `20261012000000` creates the read-only `mail_dovecot` and `mail_postfix` roles and grants SELECT on the views (the manager grants its app user `CREATEROLE` at first boot; elsewhere the migration logs the SQL for a DBA). Local mail hosts authenticate over the socket (Postgres peer via `pg_ident`); remote hosts need operator-set passwords (`ALTER ROLE … WITH PASSWORD`).
+
+## Backups
+
+Everything except mailbox content lives in the manager database (accounts, DKIM keys, suppressions, unsubscribe keys) — covered by your normal DB backup. Mailboxes are Maildirs under `/var/vmail/` on the mail host; back that directory up separately. Deleted accounts' Maildirs are garbage-collected 30 days after deletion.
diff --git a/mie-opensource-landing/docs/admins/installation.md b/mie-opensource-landing/docs/admins/installation.md
index 28287920..2f960c87 100644
--- a/mie-opensource-landing/docs/admins/installation.md
+++ b/mie-opensource-landing/docs/admins/installation.md
@@ -16,6 +16,10 @@ The following instructions make several assumptions for clarity. You may have to
4. The domains `example.org` and `*.example.org` have public DNS entries pointing to the firewall in front of this Proxmox cluster.
5. At least one Proxmox node is accessible at `https://example.org:8006` with a valid HTTPS certificate.
+## Mail Ports and PTR
+
+To use the built-in [email service](email.md), additionally forward these TCP ports to the Manager container: `25` (SMTP), `587`/`465` (submission), `993`/`143` (IMAP). Deliverability requires a **PTR record** on the public mail IP that forward-confirms to the `Mail hostname` setting — arrange this with the IP's owner (ISP/hosting provider) before enabling mail on a domain.
+
## Air-Gapped Installation
The standard installation flow assumes that the Proxmox host can access GitHub Container Registry (GHCR). For an air-gapped deployment, container images must instead be downloaded on an Internet-connected machine and transferred into the isolated Proxmox network.
diff --git a/mie-opensource-landing/docs/users/email-accounts.md b/mie-opensource-landing/docs/users/email-accounts.md
new file mode 100644
index 00000000..a0aa3fd7
--- /dev/null
+++ b/mie-opensource-landing/docs/users/email-accounts.md
@@ -0,0 +1,41 @@
+# Email Accounts
+
+Send mail over authenticated SMTP and read replies over IMAP from any app in the environment — no external mail provider needed. Manage accounts under **Email** in the sidebar.
+
+## Creating an Account
+
+1. **Email → New email account**, pick a domain (only domains an admin has [enabled for mail](../admins/core-concepts/external-domains.md#mail) appear) and a local part, e.g. `my-app@example.com`.
+2. Copy the generated password — it is shown **once**. Use **Rotate password** to get a new one at any time (the old one stops working immediately).
+
+Role addresses (`postmaster`, `abuse`, `noreply`, `admin`, …) are reserved for administrators.
+
+## Client Settings
+
+| | |
+|---|---|
+| Username | the full address (e.g. `my-app@example.com`) |
+| SMTP | port `587` (STARTTLS) or `465` (implicit TLS) — authentication required, TLS always |
+| IMAP | port `993` (TLS) or `143` |
+
+```bash
+# Quick smoke test
+curl --ssl-reqd --url smtp://example.com:587 \
+ --user 'my-app@example.com:' \
+ --mail-from my-app@example.com --mail-rcpt you@example.net \
+ --upload-file message.eml
+```
+
+Mail is DKIM-signed automatically. You can only send from your own address — the envelope sender must match your login.
+
+## Unsubscribe Headers and Suppressions
+
+Outbound mail gets RFC 8058 `List-Unsubscribe` headers by default, so recipients can one-click unsubscribe (Gmail and others surface this as an "Unsubscribe" button). Per account:
+
+- **Unsubscribe headers** toggle — turn the headers off for purely transactional accounts.
+- **Suppressions** — recipients who unsubscribed. Further mail from this account to them is rejected synchronously at submission (`5.7.1`), so your app sees the error instead of silently dropped mail. Only admins can remove a suppression.
+
+## Limits
+
+- Sending works only while the domain can send (admin-controlled, DNS-verified). If the domain is disabled, SMTP login is refused but IMAP stays readable.
+- Mailboxes have a quota (shown in the list); over-quota delivery is rejected at RCPT.
+- Disabled or deleted accounts stop authenticating immediately. A deleted account's mailbox is kept for 30 days.
diff --git a/mie-opensource-landing/zensical.toml b/mie-opensource-landing/zensical.toml
index 33100bf8..20056936 100644
--- a/mie-opensource-landing/zensical.toml
+++ b/mie-opensource-landing/zensical.toml
@@ -21,6 +21,7 @@ nav = [
{ "API Keys" = "users/creating-containers/api-keys.md" },
] },
{ "Monitoring Containers" = "users/monitoring-container.md" },
+ { "Email Accounts" = "users/email-accounts.md" },
{ "Adding Authentication" = "users/consuming-auth.md" },
{ "Launchpad" = [
{ "What is Launchpad" = "users/proxmox-launchpad/what-is-proxmox-launchpad.md" },
@@ -43,6 +44,7 @@ nav = [
{ "Volumes" = "admins/core-concepts/volumes.md" },
] },
{ "Settings" = "admins/settings.md" },
+ { "Email Service" = "admins/email.md" },
{ "OIDC Single Sign-On" = "admins/oidc.md" },
{ "LDAP Authentication" = "admins/ldap-servers.md" },
{ "Deploying Agents" = "admins/deploying-agents.md" },