This guide will help you to setup a chatmail relay using only a couple of docker compose commands.
For general info about the purpose and architecture of chatmail relays, please see this documentation (mirrors: Codeberg, GitHub).
- Public IP address with non-restricted SMTP and IMAP ports
- Registered domain name
- 1 GB of RAM
- 1 CPU core
- ~10 GB of storage for ~a hundred active users
- Docker Compose installed
Please set the following DNS records, assuming
chat.example.com is your domain and
1.2.3.4 is your IP (replace them correspondingly):
chat.example.com. A 1.2.3.4
www.chat.example.com. CNAME chat.example.com
mta-sts.chat.example.com. CNAME chat.example.com
For convenience, let's create a separate directory:
cd
mkdir chatmail
cd chatmailDownload compose.yml and example chatmail.ini:
curl -fsSO https://git.dc09.xyz/chatmail/docker/raw/branch/main/compose.yml
curl -fsS -o chatmail.ini https://git.dc09.xyz/chatmail/docker/raw/branch/main/chatmail.example.iniAlternatively, from mirrors:
Mirrors
Codeberg
curl -fsSO https://codeberg.org/chatmail-alpine/docker/raw/branch/main/compose.yml
curl -fsS -o chatmail.ini https://codeberg.org/chatmail-alpine/docker/raw/branch/main/chatmail.example.iniGitHub
curl -fsSLO https://github.com/chatmail-alpine/docker/raw/refs/heads/main/compose.yml
curl -fsSL -o chatmail.ini https://github.com/chatmail-alpine/docker/raw/refs/heads/main/chatmail.example.iniOpen chatmail.ini and adjust parameters.
You need to change mail_domain to your domain, other options can be left at their defaults.
Create a directory where chatmail relay stores all of its files:
mkdir instanceNote: see "Special cases" if you already have an ACME client and/or an HTTP server running on the host and want to replace the chatmail-provided one with it.
First, launch nginx only:
docker compose up -d nginxNext, run certbot:
docker compose run --rm certbotAnswer a couple of question, and that's it, you received new TLS certificates for your chatmail.
Important:
Restart nginx so it will run with a different config with TLS enabled:
docker compose restart nginxdocker compose up -dCheck logs, you shouldn't see any errors:
docker compose logschat.example.com. MX 10 chat.example.com.
(10 is a mail server priority)
_mta-sts.chat.example.com. TXT "v=STSv1; id=202604012111"
chat.example.com. TXT "v=spf1 a ~all"
_dmarc.chat.example.com. TXT "v=DMARC1;p=reject;adkim=s;aspf=s"
_adsp._domainkey.chat.example.com. TXT "dkim=discardable"
Read a newly generated public DKIM key:
docker compose exec opendkim cat /etc/dkimkeys/opendkim.txtYou'll get something like:
opendkim._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; "
"p=MIIBIj....uMRk"
"r6WwhL....QAB" ) ; ----- DKIM key opendkim for chat.example.com
Copy the whole p= parameter value (it's split in two lines)
and create a DNS record like this, pasting your p= value instead:
opendkim._domainkey.chat.example.com. TXT "v=DKIM1;k=rsa;p=MIIBIj...uMRkr6WwhL...QAB;s=email;t=s"
(also note the ;s=email;t=s in the end)
_submission._tcp.chat.example.com. SRV 0 1 587 chat.example.com.
_submissions._tcp.chat.example.com. SRV 0 1 465 chat.example.com.
_imap._tcp.chat.example.com. SRV 0 1 143 chat.example.com.
_imaps._tcp.chat.example.com. SRV 0 1 993 chat.example.com.
(0 is priority, 1 is weight, the third number is port)
You may want to modify lower-level chatmail components' configuration files and/or web pages templates.
In that case, you need to download the whole chatmail-docker repository, instead of only the two files (compose and ini).
git clone https://git.dc09.xyz/chatmail/docker.git chatmail
cd chatmailMirrors
Codeberg
git clone https://codeberg.org/chatmail-alpine/docker.git chatmailGitHub
git clone https://github.com/chatmail-alpine/docker.git chatmailIn compose.yml, uncomment the two volumes for the generate service
to overlay default templates with your changes.
Components' configs are in src/config.
Web pages are in src/web.
Other files are used only when building images and modifiying them gives no effect
(though some of them can actually still be replaced by mounting volumes).
Basically, you can modify your compose.yml whatever the way you want if you know what you're doing.
Properly configuring TLS certs can be a bit tricky, here's how to do it when you want to manage certs from the outside.
Note: it may be better to issue certs for chatmail separately and leave the provided certbot configuration as is. Please check whether the second option "I already have nginx installed" suits your case.
First, run the generator script to initialize the directory tree:
docker compose run --rm generateYou have to add a deploy hook that your ACME client will run on every cert renewal
to copy certs and reload chatmail services.
For certbot, this can be done by creating and chmod +xing the following script
in /etc/letsencrypt/renewal-hooks/deploy/chatmail.sh:
Script contents
#!/bin/sh
set -eu
cert_dir="$RENEWED_LINEAGE"
# or simply:
#cert_dir="/etc/letsencrypt/live/chat.example.com"
# replace to your instance directory!
target_dir="/home/user/chatmail/instance/config/tls"
tls_cert="$target_dir/cert.pem"
tls_key="$target_dir/key.pem"
echo "Running deploy hook for $cert_dir"
cp "$cert_dir/fullchain.pem" "$tls_cert"
cp "$cert_dir/privkey.pem" "$tls_key"
echo "Private key copied to $tls_key"
chown root: "$tls_cert" "$tls_key"
chmod 644 "$tls_cert"
chmod 600 "$tls_key"
echo "Reloading services"
touch "$target_dir/reload"Run this deploy hook once to initially copy your certs to the required path:
RENEWED_LINEAGE="/etc/letsencrypt/live/chat.example.com" \
/etc/letsencrypt/renewal-hooks/deploy/chatmail.shNow, comment out or remove the certbot: block from your compose.yml.
Restart nginx from chatmail compose if you started it before and proceed to the next steps.
Note: see the provided nginx config as a reference (mirrors: Codeberg, GitHub).
Comment out or remove the nginx: block from your compose.yml.
Configure nginx to serve static files on chat.example.com, mta-sts.chat.example.com
and www.chat.example.com from the webroot directory /home/user/chatmail/instance/web
(assuming /home/user/chatmail is where you put the compose + ini configs
and the instance directory).
Uncomment the ports: block for iroh-relay service in compose.yml
to access iroh from your nginx on the host.
Reverse proxy these paths on chat.example.com:
nginx config snippet
location /new {
if ($request_method = GET) {
return 301 dcaccount:https://chat.example.com/new;
}
proxy_pass http://unix:/home/user/chatmail/instance/socket/newemail/actix.sock;
proxy_http_version 1.1;
}
location /relay {
proxy_pass http://127.0.0.1:3340;
proxy_http_version 1.1;
proxy_set_header Connection "upgrade";
proxy_set_header Upgrade $http_upgrade;
}
location /relay/probe {
proxy_pass http://127.0.0.1:3340;
proxy_http_version 1.1;
}
location /generate_204 {
proxy_pass http://127.0.0.1:3340;
proxy_http_version 1.1;
}If you use the chatmail-provided certbot, i. e. didn't follow the instructions above
to replace it with your own ACME client and decided to issue certs for chatmail separately,
you also need to configure your HTTP server to serve static files for the same hosts
from the certbot webroot directory /home/user/chatmail/instance/data/certbot/web
in case a path under /.well-known/acme-challenge/ is requested.
Note: the certbot webroot contains a .well-known directory,
i. e. you have to set root, not an alias
(alternatively, rewrite + alias, but why would you need such a complexity).
nginx config snippet
http {
server {
# ...
location /.well-known/acme-challenge/ {
root /home/user/chatmail/instance/data/certbot/web;
}
}
}TLS certificate issued by the chatmail certbot is located in
/home/user/chatmail/instance/config/tls/cert.pem and key.pem.
Chatmail multiplexes https and smtps/imaps on the same port so relays remain accessible even when ports other than 443 are blocked on the client's side.
To do so, move all your virtual hosts (server{}s) to a local listen port or a unix socket,
like this:
nginx config snippet
# Before
http {
server {
listen 443 ssl;
server_name example.com;
root /var/www/pages;
}
}
# After
http {
server {
listen unix:/run/nginx/nginx.sock ssl;
# or
#listen 127.0.0.1:8443 ssl;
server_name example.com;
root /var/www/pages;
}
}...and then create a stream server proxying connections based on matched ALPN:
nginx config snippet
stream {
map $ssl_preread_alpn_protocols $proxy {
default unix:/run/nginx/nginx.sock;
# or, if you chose to use a local port instead
#default 127.0.0.1:8443;
~\bsmtp\b 127.0.0.1:465;
~\bimap\b 127.0.0.1:993;
}
server {
listen 443;
listen [::]:443;
ssl_preread on;
proxy_pass $proxy;
}
}Alternatively, if you really don't want to modify listen directives in your nginx config,
you may (but shouldn't) remove the support for 443 multiplexing.
Open src/web/.well-known/autoconfig/mail/config-v1.1.xml and delete one incomingServer
and one outgoingServer blocks where port is set to 443.