diff --git a/.github/workflows/docs-preview.yaml b/.github/workflows/docs-preview.yaml
new file mode 100644
index 00000000..f60032ee
--- /dev/null
+++ b/.github/workflows/docs-preview.yaml
@@ -0,0 +1,80 @@
+name: documentation preview
+
+on:
+ pull_request:
+ paths:
+ - 'doc/**'
+ - 'scripts/build-docs.sh'
+ - '.github/workflows/docs-preview.yaml'
+
+jobs:
+ scripts:
+ name: build
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: initenv
+ run: scripts/initenv.sh
+
+ - name: append venv/bin to PATH
+ run: echo `pwd`/venv/bin >>$GITHUB_PATH
+
+ - name: build documentation
+ working-directory: doc
+ run: sphinx-build source build
+
+ - name: build documentation second time (for TOC)
+ working-directory: doc
+ run: sphinx-build source build
+
+ - name: Get Pullrequest ID
+ id: prepare
+ run: |
+ export PULLREQUEST_ID=$(echo "${{ github.ref }}" | cut -d "/" -f3)
+ echo "prid=$PULLREQUEST_ID" >> $GITHUB_OUTPUT
+ if [ $(expr length "${{ secrets.USERNAME }}") -gt "1" ]; then echo "uploadtoserver=true" >> $GITHUB_OUTPUT; fi
+ - run: |
+ echo "baseurl: /${{ steps.prepare.outputs.prid }}" >> _config.yml
+
+ - name: Upload preview
+ run: |
+ mkdir -p "$HOME/.ssh"
+ echo "${{ secrets.CHATMAIL_STAGING_SSHKEY }}" > "$HOME/.ssh/key"
+ chmod 600 "$HOME/.ssh/key"
+ rsync -rILvh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/doc/build/ "${{ secrets.USERNAME }}@chatmail.at:/var/www/html/staging.chatmail.at/doc/relay/${{ steps.prepare.outputs.prid }}/"
+
+ - name: "Post links to details"
+ id: details
+ if: steps.prepare.outputs.uploadtoserver
+ run: |
+ # URLs for API connection and uploads
+ export GITHUB_API_URL="https://api.github.com/repos/chatmail/relay/statuses/${{ github.event.after }}"
+ export PREVIEW_LINK="https://staging.chatmail.at/doc/relay/${{ steps.prepare.outputs.prid }}/"
+ export STATUS_DATA="{\"state\": \"success\", \
+ \"description\": \"Preview the changed documentation here:\", \
+ \"context\": \"Documentation Preview\", \
+ \"target_url\": \"${PREVIEW_LINK}\"}"
+ curl -X POST --header "Accept: application/vnd.github+json" --header "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" --url "$GITHUB_API_URL" --header "content-type: application/json" --data "$STATUS_DATA"
+
+ #check if comment already exists, if not post it
+ export GITHUB_API_URL="https://api.github.com/repos/chatmail/relay/issues/${{ steps.prepare.outputs.prid }}/comments"
+ export RESPONSE=$(curl -L --header "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" --url "$GITHUB_API_URL" --header "content-type: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28")
+ echo $RESPONSE > response
+ grep -v '"Check out the page preview at https://staging.chatmail.at/doc/relay' response && echo "comment=true" >> $GITHUB_OUTPUT || true
+ - name: "Post link to comments"
+ if: steps.details.outputs.comment
+ uses: actions/github-script@v7
+ with:
+ script: |
+ github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: "Check out the page preview at https://staging.chatmail.at/doc/relay/${{ steps.prepare.outputs.prid }}/"
+ })
+
+ - name: check links
+ working-directory: doc
+ run: sphinx-build --builder linkcheck source build
+
diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
new file mode 100644
index 00000000..0bb861ac
--- /dev/null
+++ b/.github/workflows/docs.yaml
@@ -0,0 +1,44 @@
+name: build and upload documentation
+
+on:
+ push:
+ branches:
+ - main
+ - 'missytake/docs-ci'
+ paths:
+ - 'doc/**'
+ - 'scripts/build-docs.sh'
+ - '.github/workflows/docs.yaml'
+
+jobs:
+ scripts:
+ name: build
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: initenv
+ run: scripts/initenv.sh
+
+ - name: append venv/bin to PATH
+ run: echo `pwd`/venv/bin >>$GITHUB_PATH
+
+ - name: build documentation
+ working-directory: doc
+ run: sphinx-build source build
+
+ - name: build documentation second time (for TOC)
+ working-directory: doc
+ run: sphinx-build source build
+
+ - name: check links
+ working-directory: doc
+ run: sphinx-build --builder linkcheck source build
+
+ - name: upload documentation
+ run: |
+ mkdir -p "$HOME/.ssh"
+ echo "${{ secrets.CHATMAIL_STAGING_SSHKEY }}" > "$HOME/.ssh/key"
+ chmod 600 "$HOME/.ssh/key"
+ rsync -rILvh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/doc/build/ "${{ secrets.USERNAME }}@chatmail.at:/var/www/html/chatmail.at/doc/relay/"
+
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
deleted file mode 100644
index 9e42c007..00000000
--- a/ARCHITECTURE.md
+++ /dev/null
@@ -1,50 +0,0 @@
-This diagram shows components of the chatmail server; this is a draft
-overview as of mid-August 2025:
-
-```mermaid
-graph LR;
- cmdeploy --- sshd;
- letsencrypt --- |80|acmetool-redirector;
- acmetool-redirector --- |443|nginx-right(["`nginx
- (external)`"]);
- nginx-external --- |465|postfix;
- nginx-external(["`nginx
- (external)`"]) --- |8443|nginx-internal["`nginx
- (internal)`"];
- nginx-internal --- website["`Website
- /var/www/html`"];
- nginx-internal --- newemail.py;
- nginx-internal --- autoconfig.xml;
- certs-nginx[("`TLS certs
- /var/lib/acme`")] --> nginx-internal;
- cron --- chatmail-metrics;
- cron --- acmetool;
- chatmail-metrics --- website;
- acmetool --> certs[("`TLS certs
- /var/lib/acme`")];
- nginx-external --- |993|dovecot;
- autoconfig.xml --- postfix;
- autoconfig.xml --- dovecot;
- postfix --- echobot;
- postfix --- |10080,10081|filtermail;
- postfix --- users["`User data
- home/vmail/mail`"];
- postfix --- |doveauth.socket|doveauth;
- dovecot --- |doveauth.socket|doveauth;
- dovecot --- users;
- dovecot --- |metadata.socket|chatmail-metadata;
- doveauth --- users;
- chatmail-expire-daily --- users;
- chatmail-fsreport-daily --- users;
- chatmail-metadata --- iroh-relay;
- certs-nginx --> postfix;
- certs-nginx --> dovecot;
- style certs fill:#ff6;
- style certs-nginx fill:#ff6;
- style nginx-external fill:#fc9;
- style nginx-right fill:#fc9;
-```
-
-The edges in this graph should not be taken too literally; they
-reflect some sort of communication path or dependency relationship
-between components of the chatmail server.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7854a382..12c08ce5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,9 @@
## untagged
+- docs: move readme.md docs to sphinx documentation rendered at https://chatmail.at/doc/relay
+ ([#711](https://github.com/chatmail/relay/pull/711))
+
- acmetool: replace cronjob with a systemd timer
([#719](https://github.com/chatmail/relay/pull/719))
diff --git a/README.md b/README.md
index 28cf92ee..f9a70620 100644
--- a/README.md
+++ b/README.md
@@ -1,564 +1,20 @@
-
-
-# Chatmail relays for end-to-end encrypted e-mail
+# Chatmail relays for end-to-end encrypted email
Chatmail relay servers are interoperable Mail Transport Agents (MTAs) designed for:
-- **Convenience:** Low friction instant onboarding
-
-- **Privacy:** No name, phone numbers, email required or collected
-
-- **End-to-End Encryption enforced**: only OpenPGP messages with metadata minimization allowed
-
-- **Instant:** Privacy-preserving Push Notifications for Apple, Google, and Huawei
-
-- **Speed:** Message delivery in half a second, with optional P2P realtime connections
-
-- **Transport Security:** Strict TLS and DKIM enforced
-
-- **Reliability:** No spam or IP reputation checks; rate-limits are suitable for realtime chats
-
-- **Efficiency:** Messages are only stored for transit and removed automatically
-
-This repository contains everything needed to setup a ready-to-use chatmail relay
-comprised of a minimal setup of the battle-tested
-[Postfix SMTP](https://www.postfix.org) and [Dovecot IMAP](https://www.dovecot.org) MTAs/MDAs.
-
-The automated setup is designed and optimized for providing chatmail addresses
-for immediate permission-free onboarding through chat apps and bots.
-Chatmail addresses are automatically created at first login,
-after which the initially specified password is required
-for sending and receiving messages through them.
-
-Please see [this list of known apps and client projects](https://chatmail.at/clients.html)
-and [this list of known public 3rd party chatmail relay servers](https://chatmail.at/relays).
-
-
-## Minimal requirements, Prerequisites
-
-You will need the following:
-
-- Control over a domain through a DNS provider of your choice.
-
-- A Debian 12 server with reachable SMTP/SUBMISSIONS/IMAPS/HTTPS ports.
- IPv6 is encouraged if available.
- Chatmail relay servers only require 1GB RAM, one CPU, and perhaps 10GB storage for a
- few thousand active chatmail addresses.
-
-- Key-based SSH authentication to the root user.
- You must add a passphrase-protected private key to your local ssh-agent
- because you can't type in your passphrase during deployment.
- (An ed25519 private key is required due to an [upstream bug in paramiko](https://github.com/paramiko/paramiko/issues/2191))
-
-
-## Getting started
-
-We use `chat.example.org` as the chatmail domain in the following steps.
-Please substitute it with your own domain.
-
-1. Setup the initial DNS records.
- The following is an example in the familiar BIND zone file format with
- a TTL of 1 hour (3600 seconds).
- Please substitute your domain and IP addresses.
-
- ```
- chat.example.com. 3600 IN A 198.51.100.5
- chat.example.com. 3600 IN AAAA 2001:db8::5
- www.chat.example.com. 3600 IN CNAME chat.example.com.
- mta-sts.chat.example.com. 3600 IN CNAME chat.example.com.
- ```
-
-2. On your local PC, clone the repository and bootstrap the Python virtualenv.
-
- ```
- git clone https://github.com/chatmail/relay
- cd relay
- scripts/initenv.sh
- ```
-
-3. On your local PC, create chatmail configuration file `chatmail.ini`:
-
- ```
- scripts/cmdeploy init chat.example.org # <-- use your domain
- ```
-
-4. Verify that SSH root login to your remote server works:
-
- ```
- ssh root@chat.example.org # <-- use your domain
- ```
-
-5. From your local PC, deploy the remote chatmail relay server:
-
- ```
- scripts/cmdeploy run
- ```
- This script will also check that you have all necessary DNS records.
- If DNS records are missing, it will recommend
- which you should configure at your DNS provider
- (it can take some time until they are public).
-
-### Other helpful commands
-
-To check the status of your remotely running chatmail service:
-
-```
-scripts/cmdeploy status
-```
-
-To display and check all recommended DNS records:
-
-```
-scripts/cmdeploy dns
-```
-
-To test whether your chatmail service is working correctly:
-
-```
-scripts/cmdeploy test
-```
-
-To measure the performance of your chatmail service:
-
-```
-scripts/cmdeploy bench
-```
-
-## Overview of this repository
-
-This repository has four directories:
-
-- [cmdeploy](https://github.com/chatmail/relay/tree/main/cmdeploy)
- is a collection of configuration files
- and a [pyinfra](https://pyinfra.com)-based deployment script.
-
-- [chatmaild](https://github.com/chatmail/relay/tree/main/chatmaild)
- is a Python package containing several small services
- which handle authentication,
- trigger push notifications on new messages,
- ensure that outbound mails are encrypted,
- delete inactive users,
- and some other minor things.
- chatmaild can also be installed as a stand-alone Python package.
-
-- [www](https://github.com/chatmail/relay/tree/main/www)
- contains the html, css, and markdown files
- which make up a chatmail relay's web page.
- Edit them before deploying to make your chatmail relay stand out.
-
-- [scripts](https://github.com/chatmail/relay/tree/main/scripts)
- offers two convenience tools for beginners;
- `initenv.sh` installs the necessary dependencies to a local virtual environment,
- and the `scripts/cmdeploy` script enables you
- to run the `cmdeploy` command line tool in the local virtual environment.
-
-### cmdeploy
-
-The `cmdeploy/src/cmdeploy/cmdeploy.py` command line tool
-helps with setting up and managing the chatmail service.
-`cmdeploy init` creates the `chatmail.ini` config file.
-`cmdeploy run` uses a [pyinfra](https://pyinfra.com/)-based [`script`](cmdeploy/src/cmdeploy/__init__.py)
-to automatically install or upgrade all chatmail components on a relay,
-according to the `chatmail.ini` config.
-
-The components of chatmail are:
-
-- [Postfix SMTP MTA](https://www.postfix.org) accepts and relays messages
- (both from your users and from the wider e-mail MTA network)
-
-- [Dovecot IMAP MDA](https://www.dovecot.org) stores messages for your users until they download them
-
-- [Nginx](https://nginx.org/) shows the web page with your privacy policy and additional information
-
-- [acmetool](https://hlandau.github.io/acmetool/) manages TLS certificates for Dovecot, Postfix, and Nginx
-
-- [OpenDKIM](http://www.opendkim.org/) for signing messages with DKIM and rejecting inbound messages without DKIM
-
-- [mtail](https://google.github.io/mtail/) for collecting anonymized metrics in case you have monitoring
-
-- [Iroh relay](https://www.iroh.computer/docs/concepts/relay)
- which helps client devices to establish Peer-to-Peer connections
-
-- [TURN](https://github.com/chatmail/chatmail-turn)
- to enable relay users to start webRTC calls
- even if a p2p connection can't be established
-
-- and the chatmaild services, explained in the next section:
-
-### chatmaild
-
-`chatmaild` implements various systemd-controlled services
-that integrate with Dovecot and Postfix to achieve instant-onboarding and
-only relaying OpenPGP end-to-end messages encrypted messages.
-A short overview of `chatmaild` services:
-
-- [`doveauth`](https://github.com/chatmail/relay/blob/main/chatmaild/src/chatmaild/doveauth.py)
- implements create-on-login address semantics and is used
- by Dovecot during IMAP login and by Postfix during SMTP/SUBMISSION login
- which in turn uses [Dovecot SASL](https://doc.dovecot.org/configuration_manual/authentication/dict/#complete-example-for-authenticating-via-a-unix-socket)
- to authenticate logins.
-
-- [`filtermail`](https://github.com/chatmail/relay/blob/main/chatmaild/src/chatmaild/filtermail.py)
- prevents unencrypted email from leaving or entering the chatmail service
- and is integrated into Postfix's outbound and inbound mail pipelines.
-
-- [`chatmail-metadata`](https://github.com/chatmail/relay/blob/main/chatmaild/src/chatmaild/metadata.py) is contacted by a
- [Dovecot lua script](https://github.com/chatmail/relay/blob/main/cmdeploy/src/cmdeploy/dovecot/push_notification.lua)
- to store user-specific relay-side config.
- On new messages,
- it [passes the user's push notification token](https://github.com/chatmail/relay/blob/main/chatmaild/src/chatmaild/notifier.py)
- to [notifications.delta.chat](https://delta.chat/help#instant-delivery)
- so the push notifications on the user's phone can be triggered
- by Apple/Google/Huawei.
-
-- [`delete_inactive_users`](https://github.com/chatmail/relay/blob/main/chatmaild/src/chatmaild/delete_inactive_users.py)
- deletes users if they have not logged in for a very long time.
- The timeframe can be configured in `chatmail.ini`.
-
-- [`lastlogin`](https://github.com/chatmail/relay/blob/main/chatmaild/src/chatmaild/lastlogin.py)
- is contacted by Dovecot when a user logs in
- and stores the date of the login.
-
-- [`echobot`](https://github.com/chatmail/relay/blob/main/chatmaild/src/chatmaild/echo.py)
- is a small bot for test purposes.
- It simply echoes back messages from users.
-
-- [`chatmail-metrics`](https://github.com/chatmail/relay/blob/main/chatmaild/src/chatmaild/metrics.py)
- collects some metrics and displays them at `https://example.org/metrics`.
-
-### Home page and getting started for users
-
-`cmdeploy run` also creates default static web pages and deploys them
-to a Nginx web server with:
-
-- a default `index.html` along with a QR code that users can click to
- create an address on your chatmail relay
-
-- a default `info.html` that is linked from the home page
-
-- a default `policy.html` that is linked from the home page
-
-All `.html` files are generated
-by the according markdown `.md` file in the `www/src` directory.
-
-
-### Refining the web pages
-
-```
-scripts/cmdeploy webdev
-```
-
-This starts a local live development cycle for chatmail web pages:
-
-- uses the `www/src/page-layout.html` file for producing static
- HTML pages from `www/src/*.md` files
-
-- continously builds the web presence reading files from `www/src` directory
- and generating HTML files and copying assets to the `www/build` directory.
-
-- Starts a browser window automatically where you can "refresh" as needed.
-
-#### Custom web pages
-
-You can skip uploading a web page
-by setting `www_folder=disabled` in `chatmail.ini`.
-
-If you want to manage your web pages outside this git repository,
-you can set `www_folder` in `chatmail.ini` to a custom directory on your computer.
-`cmdeploy run` will upload it as the server's home page,
-and if it contains a `src/index.md` file,
-will build it with hugo.
-
-
-## Mailbox directory layout
-
-Fresh chatmail addresses have a mailbox directory that contains:
-
-- a `password` file with the salted password required for authenticating
- whether a login may use the address to send/receive messages.
- If you modify the password file manually, you effectively block the user.
-
-- `enforceE2EEincoming` is a default-created file with each address.
- If present the file indicates that this chatmail address rejects incoming cleartext messages.
- If absent the address accepts incoming cleartext messages.
-
-- `dovecot*`, `cur`, `new` and `tmp` represent IMAP/mailbox state.
- If the address is only used by one device, the Maildir directories
- will typically be empty unless the user of that address hasn't been online
- for a while.
-
-
-## Emergency Commands to disable automatic address creation
-
-If you need to stop address creation,
-e.g. because some script is wildly creating addresses,
-login with ssh and run:
-
-```
- touch /etc/chatmail-nocreate
-```
-
-Chatmail address creation will be denied while this file is present.
-
-### Ports
-
-[Postfix](http://www.postfix.org/) listens on ports 25 (SMTP) and 587 (SUBMISSION) and 465 (SUBMISSIONS).
-[Dovecot](https://www.dovecot.org/) listens on ports 143 (IMAP) and 993 (IMAPS).
-[Nginx](https://www.nginx.com/) listens on port 8443 (HTTPS-ALT) and 443 (HTTPS).
-Port 443 multiplexes HTTPS, IMAP and SMTP using ALPN to redirect connections to ports 8443, 465 or 993.
-[acmetool](https://hlandau.github.io/acmetool/) listens on port 80 (HTTP).
-[chatmail-turn](https://github.com/chatmail/chatmail-turn) listens on UDP port 3478 (STUN/TURN),
-and temporarily opens UDP ports when users request them. UDP port range is not restricted, any free port may be allocated.
-
-chatmail-core based apps will, however, discover all ports and configurations
-automatically by reading the [autoconfig XML file](https://www.ietf.org/archive/id/draft-bucksch-autoconfig-00.html) from the chatmail relay server.
-
-## Email authentication
-
-Chatmail relays enforce [DKIM](https://www.rfc-editor.org/rfc/rfc6376)
-to authenticate incoming emails.
-Incoming emails must have a valid DKIM signature with
-Signing Domain Identifier (SDID, `d=` parameter in the DKIM-Signature header)
-equal to the `From:` header domain.
-This property is checked by OpenDKIM screen policy script
-before validating the signatures.
-This correpsonds to strict [DMARC](https://www.rfc-editor.org/rfc/rfc7489) alignment (`adkim=s`),
-but chatmail does not rely on DMARC and does not consult the sender policy published in DMARC records.
-Other legacy authentication mechanisms such as [iprev](https://www.rfc-editor.org/rfc/rfc8601#section-2.7.3)
-and [SPF](https://www.rfc-editor.org/rfc/rfc7208) are also not taken into account.
-If there is no valid DKIM signature on the incoming email,
-the sender receives a "5.7.1 No valid DKIM signature found" error.
-
-Outgoing emails must be sent over authenticated connection
-with envelope MAIL FROM (return path) corresponding to the login.
-This is ensured by Postfix which maps login username
-to MAIL FROM with
-[`smtpd_sender_login_maps`](https://www.postfix.org/postconf.5.html#smtpd_sender_login_maps)
-and rejects incorrectly authenticated emails with [`reject_sender_login_mismatch`](reject_sender_login_mismatch) policy.
-`From:` header must correspond to envelope MAIL FROM,
-this is ensured by `filtermail` proxy.
-
-## TLS requirements
-
-Postfix is configured to require valid TLS
-by setting [`smtp_tls_security_level`](https://www.postfix.org/postconf.5.html#smtp_tls_security_level) to `verify`.
-If emails don't arrive at your chatmail relay server,
-the problem is likely that your relay does not have a valid TLS certificate.
-
-You can test it by resolving `MX` records of your relay domain
-and then connecting to MX relays (e.g `mx.example.org`) with
-`openssl s_client -connect mx.example.org:25 -verify_hostname mx.example.org -verify_return_error -starttls smtp`
-from the host that has open port 25 to verify that certificate is valid.
-
-When providing a TLS certificate to your chatmail relay server,
-make sure to provide the full certificate chain
-and not just the last certificate.
-
-If you are running an Exim server and don't see incoming connections
-from a chatmail relay server in the logs,
-make sure `smtp_no_mail` log item is enabled in the config
-with `log_selector = +smtp_no_mail`.
-By default Exim does not log sessions that are closed
-before sending the `MAIL` command.
-This happens if certificate is not recognized as valid by Postfix,
-so you might think that connection is not established
-while actually it is a problem with your TLS certificate.
-
-## Migrating a chatmail relay to a new host
-
-If you want to migrate chatmail relay from an old machine
-to a new machine,
-you can use these steps.
-They were tested with a Linux laptop;
-you might need to adjust some of the steps to your environment.
-
-Let's assume that your `mail_domain` is `mail.example.org`,
-all involved machines run Debian 12,
-your old site's IP address is `13.37.13.37`,
-and your new site's IP address is `13.12.23.42`.
-
-Note, you should lower the TTLs of your DNS records to a value
-such as 300 (5 minutes) so the migration happens as smoothly as possible.
-
-During the guide you might get a warning about changed SSH Host keys;
-in this case, just run `ssh-keygen -R "mail.example.org"` as recommended.
-
-1. First, disable mail services on the old site.
-
- ```
- cmdeploy run --disable-mail --ssh-host 13.37.13.37
- ```
-
- Now your users will notice the migration
- and will not be able to send or receive messages
- until the migration is completed.
-
-2. Now we want to copy `/home/vmail`, `/var/lib/acme`, `/etc/dkimkeys`, `/run/echobot`, and `/var/spool/postfix` to the new site.
- Login to the old site while forwarding your SSH agent
- so you can copy directly from the old to the new site with your SSH key:
- ```
- ssh -A root@13.37.13.37
- tar c - /home/vmail/mail /var/lib/acme /etc/dkimkeys /run/echobot /var/spool/postfix | ssh root@13.12.23.42 "tar x -C /"
- ```
-
- This transfers all addresses, the TLS certificate, DKIM keys (so DKIM DNS record remains valid), and the echobot's password so it continues to function.
- It also preserves the Postfix mail spool so any messages pending delivery will still be delivered.
-
-3. Install chatmail on the new machine:
-
- ```
- cmdeploy run --disable-mail --ssh-host 13.12.23.42
- ```
- Postfix and Dovecot are disabled for now; we will enable them later.
- We first need to make the new site fully operational.
-
-3. On the new site, run the following to ensure the ownership is correct in case UIDs/GIDs changed:
-
- ```
- chown root: -R /var/lib/acme
- chown opendkim: -R /etc/dkimkeys
- chown vmail: -R /home/vmail/mail
- chown echobot: -R /run/echobot
- ```
-
-4. Now, update DNS entries.
-
- If other MTAs try to deliver messages to your chatmail domain they may fail intermittently,
- as DNS catches up with the new site settings
- but normally will retry delivering messages
- for at least a week, so messages will not be lost.
-
-5. Finally, you can execute `cmdeploy run --ssh-host 13.12.23.42` to turn on chatmail on the new relay.
- Your users will be able to use the chatmail relay as soon as the DNS changes have propagated.
- Voilà!
-
-## Setting up a reverse proxy
-
-A chatmail relay MTA does not track or depend on the client IP address
-for its operation, so it can be run behind a reverse proxy.
-This will not even affect incoming mail authentication
-as DKIM only checks the cryptographic signature
-of the message and does not use the IP address as the input.
-
-For example, you may want to self-host your chatmail relay
-and only use hosted VPS to provide a public IP address
-for client connections and incoming mail.
-You can connect chatmail relay to VPS
-using a tunnel protocol
-such as [WireGuard](https://www.wireguard.com/)
-and setup a reverse proxy on a VPS
-to forward connections to the chatmail relay
-over the tunnel.
-You can also setup multiple reverse proxies
-for your chatmail relay in different networks
-to ensure your relay is reachable even when
-one of the IPs becomes inaccessible due to
-hosting or routing problems.
-
-Note that your chatmail relay still needs
-to be able to make outgoing connections on port 25
-to send messages outside.
-
-To setup a reverse proxy
-(or rather Destination NAT, DNAT)
-for your chatmail relay,
-put the following configuration in `/etc/nftables.conf`:
-```
-#!/usr/sbin/nft -f
-
-flush ruleset
-
-define wan = eth0
-
-# Which ports to proxy.
-#
-# Note that SSH is not proxied
-# so it is possible to log into the proxy server
-# and not the original one.
-define ports = { smtp, http, https, imap, imaps, submission, submissions }
-
-# The host we want to proxy to.
-define ipv4_address = AAA.BBB.CCC.DDD
-define ipv6_address = [XXX::1]
-
-table ip nat {
- chain prerouting {
- type nat hook prerouting priority dstnat; policy accept;
- iif $wan tcp dport $ports dnat to $ipv4_address
- }
-
- chain postrouting {
- type nat hook postrouting priority 0;
-
- oifname $wan masquerade
- }
-}
-
-table ip6 nat {
- chain prerouting {
- type nat hook prerouting priority dstnat; policy accept;
- iif $wan tcp dport $ports dnat to $ipv6_address
- }
-
- chain postrouting {
- type nat hook postrouting priority 0;
-
- oifname $wan masquerade
- }
-}
-
-table inet filter {
- chain input {
- type filter hook input priority filter; policy drop;
-
- # Accept ICMP.
- # It is especially important to accept ICMPv6 ND messages,
- # otherwise IPv6 connectivity breaks.
- icmp type { echo-request } accept
- icmpv6 type { echo-request, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept
-
- # Allow incoming SSH connections.
- tcp dport { ssh } accept
-
- ct state established accept
- }
- chain forward {
- type filter hook forward priority filter; policy drop;
-
- ct state established accept
- ip daddr $ipv4_address counter accept
- ip6 daddr $ipv6_address counter accept
- }
- chain output {
- type filter hook output priority filter;
- }
-}
-```
-
-Run `systemctl enable nftables.service`
-to ensure configuration is reloaded when the proxy relay reboots.
-
-Uncomment in `/etc/sysctl.conf` the following two lines:
+- **Zero State:** no private data or metadata collected, messages are auto-deleted, low disk usage
-```
-net.ipv4.ip_forward=1
-net.ipv6.conf.all.forwarding=1
-```
+- **Instant/Realtime:** sub-second message delivery, realtime P2P
+ streaming, privacy-preserving Push Notifications for Apple, Google, and Huawei;
-Then reboot the relay or do `sysctl -p` and `nft -f /etc/nftables.conf`.
+- **Security Enforcement**: only strict TLS, DKIM and OpenPGP with minimized metadata accepted
-Once proxy relay is set up,
-you can add its IP address to the DNS.
+- **Reliable Federation and Decentralization:** No spam or IP reputation checks, federating
+ depends on established IETF standards and protocols.
-## Neighbors and Acquaintances
+This repository contains everything needed to setup a ready-to-use chatmail relay on an ssh-reachable host.
+For getting started and more information please refer to the web version of this repositories' documentation at
-Here are some related projects that you may be interested in:
+[https://chatmail.at/doc/relay](https://chatmail.at/doc/relay)
-- [Mox](https://github.com/mjl-/mox): A Golang email server. [Work is in
- progress](https://github.com/mjl-/mox/issues/251) to modify it to support all
- of the features and configuration settings required to operate as a chatmail
- relay.
-- [Maddy-Chatmail](https://github.com/sadraiiali/maddy_chatmail): a plugin for the
- [Maddy email server](https://maddy.email/) which aims to implement the
- chatmail relay features and configuration options.
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 00000000..c960b735
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,24 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = source
+BUILDDIR = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+auto:
+ sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile auto
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
diff --git a/doc/README.md b/doc/README.md
new file mode 100644
index 00000000..4c21f5db
--- /dev/null
+++ b/doc/README.md
@@ -0,0 +1,17 @@
+
+
+## Building the documentation
+
+You can use the `make` command and `make html` to build web pages.
+
+You need a Python environment where the following install was excuted:
+
+ pip install sphinx-build furo sphinx-autobuild
+
+To develop/change documentation, you can then do:
+
+ make auto
+
+A page will open at https://127.0.0.1:8000/ serving the docs and it will
+react to changes to source files pretty fast.
+
diff --git a/doc/source/_static/chatmail.svg b/doc/source/_static/chatmail.svg
new file mode 100644
index 00000000..f9e0ff1d
--- /dev/null
+++ b/doc/source/_static/chatmail.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/doc/source/_static/custom.css b/doc/source/_static/custom.css
new file mode 100644
index 00000000..bc9eef45
--- /dev/null
+++ b/doc/source/_static/custom.css
@@ -0,0 +1,21 @@
+/* Tweak how the sidebar logo is presented */
+.sidebar-logo {
+ width: 70%;
+}
+.sidebar-brand {
+ padding: 0;
+}
+
+/* The landing pages' sidebar-in-content highlights */
+#features ul {
+ padding-left: 1rem;
+ list-style: none;
+}
+#features ul li {
+ margin-bottom: 0;
+}
+@media (min-width: 46em) {
+ #features {
+ width: 50%;
+ }
+}
diff --git a/doc/source/conf.py b/doc/source/conf.py
new file mode 100644
index 00000000..24422df4
--- /dev/null
+++ b/doc/source/conf.py
@@ -0,0 +1,41 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+project = 'chatmail relay documentation'
+copyright = '2025, chatmail collective'
+author = 'chatmail collective'
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+extensions = [
+ #'sphinx.ext.autodoc',
+ #'sphinx.ext.viewdoc',
+ 'sphinxcontrib.mermaid',
+]
+
+templates_path = ['_templates']
+exclude_patterns = []
+
+
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+html_theme = 'furo'
+html_static_path = ['_static']
+html_css_files = [
+ "custom.css",
+]
+
+html_title = "chatmail relay documentation"
+#html_short_title = f"chatmail-{release}"
+
+html_logo = "_static/chatmail.svg"
+
+
diff --git a/doc/source/faq.rst b/doc/source/faq.rst
new file mode 100644
index 00000000..3fd36998
--- /dev/null
+++ b/doc/source/faq.rst
@@ -0,0 +1,61 @@
+
+
+Frequently asked questions
+===========================
+
+What is the difference between chatmail relays and classic email servers?
+--------------------------------------------------------------------------
+
+A chatmail relay is a minimal Mail Transport Agent (MTA) setup that
+goes beyond what classic email servers offer:
+
+- **Zero State:** no private data or metadata collected, messages are auto-deleted, low disk usage
+
+- **Instant/Realtime:** sub-second message delivery, realtime P2P
+ streaming, privacy-preserving Push Notifications for Apple, Google, and `Ubuntu Touch `_;
+
+- **Security Enforcement**: only strict TLS, DKIM and OpenPGP with minimized metadata accepted
+
+- **Reliable Federation and Decentralization:** No spam or IP reputation checks, federating
+ depends on established IETF standards and protocols.
+
+
+How about interoperability with classic email servers?
+-------------------------------------------------------
+
+Generally, chatmail relays interoperate well with classic email servers.
+However, some chatmail relays may be blocked by Big-Tech email
+providers that use intransparent and proprietary techniques for scanning
+and looking at cleartext email messages between users, or because they
+use questionable IP-reputation systems that break interoperability.
+
+**Chatmail relays instead use and require strong cryptography, allowing
+anyone to participate, without having to submit to Big-Tech
+restrictions.**
+
+.. _selfhosted:
+
+How are chatmail relays run? Can I run one myself?
+--------------------------------------------------
+
+Chatmail relays are designed to be very cheap to run, and are generally
+self-funded by respective operators. All chatmail relays are
+automatically deployed and updated using `the chatmail relay
+repository `__. Chatmail relays are
+composed of proven standard email server components, Postfix and
+Dovecot, and are configured to run unattended without much maintenance
+effort. Chatmail relays happily run on low-end hardware like a Raspberry
+Pi.
+
+
+How trustable are chatmail relays?
+----------------------------------
+
+Chatmail relays enforce end-to-end encryption,
+and chatmail clients like `Delta Chat `_
+enforce end-to-end encryption on their own.
+
+The end-to-end encryption protection includes attached media, user
+display names, avatars and group names. What is visible to operators is:
+message date, sender and receiver addresses.
+Please see the `Delta Chat FAQ on encryption and security `_ for further info.
diff --git a/doc/source/getting_started.rst b/doc/source/getting_started.rst
new file mode 100644
index 00000000..005390ad
--- /dev/null
+++ b/doc/source/getting_started.rst
@@ -0,0 +1,169 @@
+Setting up a chatmail relay
+===========================
+
+This section contains everything needed to setup a ready-to-use chatmail relay.
+The automated setup is designed and optimized for providing chatmail
+addresses for immediate permission-free onboarding through chat apps and bots.
+Chatmail addresses are automatically created at first login,
+after which the initially specified password is required
+for sending and receiving messages through them.
+
+
+Minimal requirements and prerequisites
+--------------------------------------
+
+You will need the following:
+
+- Control over a domain through a DNS provider of your choice.
+
+- A Debian 12 server with reachable SMTP/SUBMISSIONS/IMAPS/HTTPS ports.
+ IPv6 is encouraged if available. Chatmail relay servers only require
+ 1GB RAM, one CPU, and perhaps 10GB storage for a few thousand active
+ chatmail addresses.
+
+- Key-based SSH authentication to the root user. You must add a
+ passphrase-protected private key to your local ssh-agent because you
+ can’t type in your passphrase during deployment. (An ed25519 private
+ key is required due to an `upstream bug in
+ paramiko `_)
+
+
+Setup with ``scripts/cmdeploy``
+-------------------------------------
+
+We use ``chat.example.org`` as the chatmail domain in the following
+steps. Please substitute it with your own domain.
+
+1. Setup the initial DNS records. The following is an example in the
+ familiar BIND zone file format with a TTL of 1 hour (3600 seconds).
+ Please substitute your domain and IP addresses.
+
+ ::
+
+ chat.example.com. 3600 IN A 198.51.100.5
+ chat.example.com. 3600 IN AAAA 2001:db8::5
+ www.chat.example.com. 3600 IN CNAME chat.example.com.
+ mta-sts.chat.example.com. 3600 IN CNAME chat.example.com.
+
+2. On your local PC, clone the repository and bootstrap the Python
+ virtualenv.
+
+ ::
+
+ git clone https://github.com/chatmail/relay
+ cd relay
+ scripts/initenv.sh
+
+3. On your local PC, create chatmail configuration file
+ ``chatmail.ini``:
+
+ ::
+
+ scripts/cmdeploy init chat.example.org # <-- use your domain
+
+4. Verify that SSH root login to your remote server works:
+
+ ::
+
+ ssh root@chat.example.org # <-- use your domain
+
+5. From your local PC, deploy the remote chatmail relay server:
+
+ ::
+
+ scripts/cmdeploy run
+
+ This script will also check that you have all necessary DNS records.
+ If DNS records are missing, it will recommend which you should
+ configure at your DNS provider (it can take some time until they are
+ public).
+
+Other helpful commands
+----------------------
+
+To check the status of your remotely running chatmail service:
+
+::
+
+ scripts/cmdeploy status
+
+To display and check all recommended DNS records:
+
+::
+
+ scripts/cmdeploy dns
+
+To test whether your chatmail service is working correctly:
+
+::
+
+ scripts/cmdeploy test
+
+To measure the performance of your chatmail service:
+
+::
+
+ scripts/cmdeploy bench
+
+
+
+Modifying the home page
+-----------------------
+
+``cmdeploy run`` also creates default static web pages and deploys them
+to a Nginx web server with:
+
+- a default ``index.html`` along with a QR code that users can click to
+ create an address on your chatmail relay
+
+- a default ``info.html`` that is linked from the home page
+
+- a default ``policy.html`` that is linked from the home page
+
+All ``.html`` files are generated by the according markdown ``.md`` file
+in the ``www/src`` directory.
+
+Refining the web pages
+----------------------
+
+::
+
+ scripts/cmdeploy webdev
+
+This starts a local live development cycle for chatmail web pages:
+
+- uses the ``www/src/page-layout.html`` file for producing static HTML
+ pages from ``www/src/*.md`` files
+
+- continously builds the web presence reading files from ``www/src``
+ directory and generating HTML files and copying assets to the
+ ``www/build`` directory.
+
+- Starts a browser window automatically where you can “refresh” as
+ needed.
+
+Custom web pages
+----------------
+
+You can skip uploading a web page by setting ``www_folder=disabled`` in
+``chatmail.ini``.
+
+If you want to manage your web pages outside this git repository, you
+can set ``www_folder`` in ``chatmail.ini`` to a custom directory on your
+computer. ``cmdeploy run`` will upload it as the server’s home page, and
+if it contains a ``src/index.md`` file, will build it with hugo.
+
+
+Disable automatic address creation
+--------------------------------------------------------
+
+If you need to stop address creation, e.g. because some script is wildly
+creating addresses, login with ssh and run:
+
+::
+
+ touch /etc/chatmail-nocreate
+
+Chatmail address creation will be denied while this file is present.
+
+
diff --git a/doc/source/index.rst b/doc/source/index.rst
new file mode 100644
index 00000000..d37a10f6
--- /dev/null
+++ b/doc/source/index.rst
@@ -0,0 +1,20 @@
+*******************************************
+chatmail relay documentation
+*******************************************
+
+.. image:: ../../www/src/collage-top.png
+ :target: https://testrun.org
+
+This documentation details how to setup, maintain and understand `chatmail `_ relays.
+
+Contributions and feedback welcome through the https://github.com/chatmail/relay repository.
+
+.. toctree::
+ :maxdepth: 5
+
+ getting_started
+ proxy
+ migrate
+ overview
+ related
+ faq
diff --git a/doc/source/migrate.rst b/doc/source/migrate.rst
new file mode 100644
index 00000000..4086744f
--- /dev/null
+++ b/doc/source/migrate.rst
@@ -0,0 +1,73 @@
+
+Migrating to a new host
+-----------------------
+
+If you want to migrate chatmail relay from an old machine to a new
+machine, you can use these steps. They were tested with a Linux laptop;
+you might need to adjust some of the steps to your environment.
+
+Let’s assume that your ``mail_domain`` is ``mail.example.org``, all
+involved machines run Debian 12, your old site’s IP address is
+``13.37.13.37``, and your new site’s IP address is ``13.12.23.42``.
+
+Note, you should lower the TTLs of your DNS records to a value such as
+300 (5 minutes) so the migration happens as smoothly as possible.
+
+During the guide you might get a warning about changed SSH Host keys; in
+this case, just run ``ssh-keygen -R "mail.example.org"`` as recommended.
+
+1. First, disable mail services on the old site.
+
+ ::
+
+ cmdeploy run --disable-mail --ssh-host 13.37.13.37
+
+ Now your users will notice the migration and will not be able to send
+ or receive messages until the migration is completed.
+
+2. Now we want to copy ``/home/vmail``, ``/var/lib/acme``,
+ ``/etc/dkimkeys``, ``/run/echobot``, and ``/var/spool/postfix`` to
+ the new site. Login to the old site while forwarding your SSH agent
+ so you can copy directly from the old to the new site with your SSH
+ key:
+
+ ::
+
+ ssh -A root@13.37.13.37
+ tar c - /home/vmail/mail /var/lib/acme /etc/dkimkeys /run/echobot /var/spool/postfix | ssh root@13.12.23.42 "tar x -C /"
+
+ This transfers all addresses, the TLS certificate, DKIM keys (so DKIM
+ DNS record remains valid), and the echobot’s password so it continues
+ to function. It also preserves the Postfix mail spool so any messages
+ pending delivery will still be delivered.
+
+3. Install chatmail on the new machine:
+
+ ::
+
+ cmdeploy run --disable-mail --ssh-host 13.12.23.42
+
+ Postfix and Dovecot are disabled for now; we will enable them later.
+ We first need to make the new site fully operational.
+
+4. On the new site, run the following to ensure the ownership is correct
+ in case UIDs/GIDs changed:
+
+ ::
+
+ chown root: -R /var/lib/acme
+ chown opendkim: -R /etc/dkimkeys
+ chown vmail: -R /home/vmail/mail
+ chown echobot: -R /run/echobot
+
+5. Now, update DNS entries.
+
+ If other MTAs try to deliver messages to your chatmail domain they
+ may fail intermittently, as DNS catches up with the new site settings
+ but normally will retry delivering messages for at least a week, so
+ messages will not be lost.
+
+6. Finally, you can execute ``cmdeploy run --ssh-host 13.12.23.42`` to
+ turn on chatmail on the new relay. Your users will be able to use the
+ chatmail relay as soon as the DNS changes have propagated. Voilà!
+
diff --git a/doc/source/overview.rst b/doc/source/overview.rst
new file mode 100644
index 00000000..c04394ba
--- /dev/null
+++ b/doc/source/overview.rst
@@ -0,0 +1,299 @@
+
+Technical overview
+======================
+
+
+Directories of the relay repository
+-----------------------------------
+
+The `chatmail relay repository `_
+has four main directories.
+
+``scripts/``
+~~~~~~~~~~~~~
+
+`scripts `_
+offers two convenience tools for beginners:
+
+- ``initenv.sh`` installs a local virtualenv Python environment and
+ installs necessary dependencies
+
+- ``scripts/cmdeploy`` script enables you to run the ``cmdeploy``
+ command line tool in the local Python virtual environment.
+
+
+``cmdeploy/``
+~~~~~~~~~~~~~
+
+The ``cmdeploy`` directory contains the Python package and command line tool
+to setup a chatmail relay remotely via SSH:
+
+- ``cmdeploy init`` creates the ``chatmail.ini`` config file locally.
+
+- ``cmdeploy run`` under the hood uses pyinfra_
+ to automatically install or upgrade all chatmail components on a relay,
+ according to the local ``chatmail.ini`` config.
+
+The deployed system components of a chatmail relay are:
+
+- Postfix_ is the Mail Transport Agent (MTA) and
+ accepts messages from, and sends messages to, the wider email MTA network
+
+- Dovecot_ is the Mail Delivery Agent (MDA) and
+ stores messages for users until they download them
+
+- Nginx_ shows the web page with privacy policy and additional information
+
+- `acmetool `_ manages TLS
+ certificates for Dovecot, Postfix, and Nginx
+
+- `OpenDKIM `_ for signing messages with
+ DKIM and rejecting inbound messages without DKIM
+
+- `mtail `_ for collecting anonymized
+ metrics in case you have monitoring
+
+- `Iroh relay `_ which
+ helps client devices to establish Peer-to-Peer connections
+
+- `TURN `_ to enable relay
+ users to start webRTC calls even if a p2p connection can’t be
+ established
+
+- and the chatmaild services, explained in the next section:
+
+
+``chatmaild/``
+~~~~~~~~~~~~~~
+
+`chatmaild `_
+is a Python package containing several small services which handle
+authentication, trigger push notifications on new messages, ensure
+that outbound mails are encrypted, delete inactive users, and some
+other minor things. chatmaild can also be installed as a stand-alone
+Python package.
+
+``chatmaild`` implements various systemd-controlled services
+that integrate with Dovecot and Postfix to achieve instant-onboarding
+and only relaying OpenPGP end-to-end messages encrypted messages. A
+short overview of ``chatmaild`` services:
+
+- `doveauth `_
+ implements create-on-login address semantics and is used by Dovecot
+ during IMAP login and by Postfix during SMTP/SUBMISSION login which
+ in turn uses `Dovecot SASL
+ `_
+ to authenticate logins.
+
+- `filtermail `_
+ prevents unencrypted email from leaving or entering the chatmail
+ service and is integrated into Postfix’s outbound and inbound mail
+ pipelines.
+
+- `chatmail-metadata `_
+ is contacted by a `Dovecot lua
+ script `_
+ to store user-specific relay-side config. On new messages, it `passes
+ the user’s push notification
+ token `_
+ to
+ `notifications.delta.chat `_
+ so the push notifications on the user’s phone can be triggered by
+ Apple/Google/Huawei.
+
+- `chatmail-expire `_
+ deletes users if they have not logged in for a longer while.
+ The timeframe can be configured in ``chatmail.ini``.
+
+- `lastlogin `_
+ is contacted by Dovecot when a user logs in and stores the date of
+ the login.
+
+- `echobot `_
+ is a small bot for test purposes. It simply echoes back messages from
+ users.
+
+- `metrics `_
+ collects some metrics and displays them at
+ ``https://example.org/metrics``.
+
+``www/``
+~~~~~~~~~
+
+`www `_ contains
+the html, css, and markdown files which make up a chatmail relay’s
+web page. Edit them before deploying to make your chatmail relay
+stand out.
+
+
+Component dependency diagram
+--------------------------------------
+
+.. mermaid::
+ :caption: This diagram shows relay components and dependencies/communication paths.
+
+ graph LR;
+ cmdeploy --- sshd;
+ letsencrypt --- |80|acmetool-redirector;
+ acmetool-redirector --- |443|nginx-right(["`nginx
+ (external)`"]);
+ nginx-external --- |465|postfix;
+ nginx-external(["`nginx
+ (external)`"]) --- |8443|nginx-internal["`nginx
+ (internal)`"];
+ nginx-internal --- website["`Website
+ /var/www/html`"];
+ nginx-internal --- newemail.py;
+ nginx-internal --- autoconfig.xml;
+ certs-nginx[("`TLS certs
+ /var/lib/acme`")] --> nginx-internal;
+ cron --- chatmail-metrics;
+ cron --- acmetool;
+ chatmail-metrics --- website;
+ acmetool --> certs[("`TLS certs
+ /var/lib/acme`")];
+ nginx-external --- |993|dovecot;
+ autoconfig.xml --- postfix;
+ autoconfig.xml --- dovecot;
+ postfix --- echobot;
+ postfix --- |10080,10081|filtermail;
+ postfix --- users["`User data
+ home/vmail/mail`"];
+ postfix --- |doveauth.socket|doveauth;
+ dovecot --- |doveauth.socket|doveauth;
+ dovecot --- users;
+ dovecot --- |metadata.socket|chatmail-metadata;
+ doveauth --- users;
+ chatmail-expire-daily --- users;
+ chatmail-fsreport-daily --- users;
+ chatmail-metadata --- iroh-relay;
+ certs-nginx --> postfix;
+ certs-nginx --> dovecot;
+ style certs fill:#ff6;
+ style certs-nginx fill:#ff6;
+ style nginx-external fill:#fc9;
+ style nginx-right fill:#fc9;
+
+
+Operational details of a chatmail relay
+----------------------------------------
+
+Mailbox directory layout
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Fresh chatmail addresses have a mailbox directory that contains:
+
+- a ``password`` file with the salted password required for
+ authenticating whether a login may use the address to send/receive
+ messages. If you modify the password file manually, you effectively
+ block the user.
+
+- ``enforceE2EEincoming`` is a default-created file with each address.
+ If present the file indicates that this chatmail address rejects
+ incoming cleartext messages. If absent the address accepts incoming
+ cleartext messages.
+
+- ``dovecot*``, ``cur``, ``new`` and ``tmp`` represent IMAP/mailbox
+ state. If the address is only used by one device, the Maildir
+ directories will typically be empty unless the user of that address
+ hasn’t been online for a while.
+
+Active ports
+~~~~~~~~~~~~
+
+Postfix_ listens on ports
+
+- 25 (SMTP)
+
+- 587 (SUBMISSION) and
+
+- 465 (SUBMISSIONS)
+
+Dovecot_ listens on ports
+
+- 143 (IMAP) and
+
+- 993 (IMAPS)
+
+Nginx_ listens on port
+
+- 8443 (HTTPS-ALT) and
+
+- 443 (HTTPS) which multiplexes HTTPS, IMAP and SMTP using ALPN
+ to redirect connections to ports 8443, 465 or 993.
+
+`acmetool `_ listens on port:
+
+- 80 (HTTP).
+
+`chatmail-turn `_ listens on port
+
+- 3478 UDP (STUN/TURN), and temporarily opens further UDP ports
+ when users request them. UDP port range is not restricted, any free port
+ may be allocated.
+
+chatmail-core based apps will, however, discover all ports and
+configurations automatically by reading the `autoconfig XML
+file `_
+from the chatmail relay server.
+
+Email domain authentication (DKIM)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Chatmail relays enforce :rfc:`DKIM <6376>` to authenticate incoming emails.
+Incoming emails must have a valid DKIM signature with
+Signing Domain Identifier (SDID, ``d=`` parameter in the DKIM-Signature
+header) equal to the ``From:`` header domain. This property is checked
+by OpenDKIM screen policy script before validating the signatures. This
+correpsonds to strict :rfc:`DMARC <7489>` alignment (``adkim=s``).
+If there is no valid DKIM signature on the incoming email, the
+sender receives a “5.7.1 No valid DKIM signature found” error.
+
+Note that chatmail relays
+
+- do **not** rely on DMARC and do not consult the sender policy published in DMARC records;
+
+- do **not** rely on legacy authentication mechanisms such as
+ :rfc:`iprev <8601#section-2.7.3>` and :rfc:`SPF <7208>`.
+ Any IP address is accepted if the DKIM signature was valid.
+
+Outgoing emails must be sent over authenticated connection with envelope
+``MAIL FROM`` (return path) corresponding to the login.
+This is ensured by Postfix which maps login username to ``MAIL FROM`` with
+`smtpd_sender_login_maps `_
+and rejects incorrectly authenticated emails with
+`reject_sender_login_mismatch `_ policy.
+``From:`` header must correspond to envelope ``MAIL FROM``, this is
+ensured by ``filtermail`` proxy.
+
+TLS requirements
+~~~~~~~~~~~~~~~~
+
+Postfix is configured to require valid TLS by setting
+`smtp_tls_security_level `_
+to ``verify``. If emails don’t arrive at your chatmail relay server, the
+problem is likely that your relay does not have a valid TLS certificate.
+
+You can test it by resolving ``MX`` records of your relay domain and
+then connecting to MX relays (e.g ``mx.example.org``) with
+``openssl s_client -connect mx.example.org:25 -verify_hostname mx.example.org -verify_return_error -starttls smtp``
+from the host that has open port 25 to verify that certificate is valid.
+
+When providing a TLS certificate to your chatmail relay server, make
+sure to provide the full certificate chain and not just the last
+certificate.
+
+If you are running an Exim server and don’t see incoming connections
+from a chatmail relay server in the logs, make sure ``smtp_no_mail`` log
+item is enabled in the config with ``log_selector = +smtp_no_mail``. By
+default Exim does not log sessions that are closed before sending the
+``MAIL`` command. This happens if certificate is not recognized as valid
+by Postfix, so you might think that connection is not established while
+actually it is a problem with your TLS certificate.
+
+
+.. _dovecot: https://dovecot.org
+.. _postfix: https://www.postfix.org
+.. _nginx: https://nginx.org
+.. _pyinfra: https://pyinfra.com
+
diff --git a/doc/source/proxy.rst b/doc/source/proxy.rst
new file mode 100644
index 00000000..3de0f732
--- /dev/null
+++ b/doc/source/proxy.rst
@@ -0,0 +1,114 @@
+
+Setting up a reverse proxy
+--------------------------
+
+A chatmail relay MTA does not track or depend on the client IP address
+for its operation, so it can be run behind a reverse proxy. This will
+not even affect incoming mail authentication as DKIM only checks the
+cryptographic signature of the message and does not use the IP address
+as the input.
+
+For example, you may want to self-host your chatmail relay and only use
+hosted VPS to provide a public IP address for client connections and
+incoming mail. You can connect chatmail relay to VPS using a tunnel
+protocol such as `WireGuard `_ and setup a
+reverse proxy on a VPS to forward connections to the chatmail relay over
+the tunnel. You can also setup multiple reverse proxies for your
+chatmail relay in different networks to ensure your relay is reachable
+even when one of the IPs becomes inaccessible due to hosting or routing
+problems.
+
+Note that your chatmail relay still needs to be able to make outgoing
+connections on port 25 to send messages outside.
+
+To setup a reverse proxy (or rather Destination NAT, DNAT) for your
+chatmail relay, put the following configuration in
+``/etc/nftables.conf``:
+
+::
+
+ #!/usr/sbin/nft -f
+
+ flush ruleset
+
+ define wan = eth0
+
+ # Which ports to proxy.
+ #
+ # Note that SSH is not proxied
+ # so it is possible to log into the proxy server
+ # and not the original one.
+ define ports = { smtp, http, https, imap, imaps, submission, submissions }
+
+ # The host we want to proxy to.
+ define ipv4_address = AAA.BBB.CCC.DDD
+ define ipv6_address = [XXX::1]
+
+ table ip nat {
+ chain prerouting {
+ type nat hook prerouting priority dstnat; policy accept;
+ iif $wan tcp dport $ports dnat to $ipv4_address
+ }
+
+ chain postrouting {
+ type nat hook postrouting priority 0;
+
+ oifname $wan masquerade
+ }
+ }
+
+ table ip6 nat {
+ chain prerouting {
+ type nat hook prerouting priority dstnat; policy accept;
+ iif $wan tcp dport $ports dnat to $ipv6_address
+ }
+
+ chain postrouting {
+ type nat hook postrouting priority 0;
+
+ oifname $wan masquerade
+ }
+ }
+
+ table inet filter {
+ chain input {
+ type filter hook input priority filter; policy drop;
+
+ # Accept ICMP.
+ # It is especially important to accept ICMPv6 ND messages,
+ # otherwise IPv6 connectivity breaks.
+ icmp type { echo-request } accept
+ icmpv6 type { echo-request, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept
+
+ # Allow incoming SSH connections.
+ tcp dport { ssh } accept
+
+ ct state established accept
+ }
+ chain forward {
+ type filter hook forward priority filter; policy drop;
+
+ ct state established accept
+ ip daddr $ipv4_address counter accept
+ ip6 daddr $ipv6_address counter accept
+ }
+ chain output {
+ type filter hook output priority filter;
+ }
+ }
+
+Run ``systemctl enable nftables.service`` to ensure configuration is
+reloaded when the proxy relay reboots.
+
+Uncomment in ``/etc/sysctl.conf`` the following two lines:
+
+::
+
+ net.ipv4.ip_forward=1
+ net.ipv6.conf.all.forwarding=1
+
+Then reboot the relay or do ``sysctl -p`` and
+``nft -f /etc/nftables.conf``.
+
+Once proxy relay is set up, you can add its IP address to the DNS.
+
diff --git a/doc/source/related.rst b/doc/source/related.rst
new file mode 100644
index 00000000..14f1288b
--- /dev/null
+++ b/doc/source/related.rst
@@ -0,0 +1,20 @@
+
+Community developments
+======================
+
+Active development takes place in the `chatmail/relay github repository `_.
+
+You can check out the `'chatmail' tag in the support.delta.chat forum `_
+and ask to get added to a non-public support chat for debugging issues.
+
+We know of two work-in-progress alternative implementation efforts:
+
+- `Mox `_: A Golang email server. `Work
+ is in progress `_ to modify
+ it to support all of the features and configuration settings required
+ to operate as a chatmail relay.
+
+- `Maddy-Chatmail `_: a
+ plugin for the `Maddy email server `_ which
+ aims to implement the chatmail relay features and configuration
+ options.
diff --git a/scripts/build-docs.sh b/scripts/build-docs.sh
new file mode 100644
index 00000000..ac066d4a
--- /dev/null
+++ b/scripts/build-docs.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+#
+# Wrapper for building the docs
+set -e
+. venv/bin/activate
+cd doc/
+make html
diff --git a/scripts/initenv.sh b/scripts/initenv.sh
index db70250a..d0e40a54 100755
--- a/scripts/initenv.sh
+++ b/scripts/initenv.sh
@@ -22,3 +22,4 @@ python3 -m venv --upgrade-deps venv
venv/bin/pip install -e chatmaild
venv/bin/pip install -e cmdeploy
+venv/bin/pip install sphinx sphinxcontrib-mermaid sphinx-autobuild furo # for building the docs