This quickstart gets the docker compose stack from this repository up and running on your public fmsg server.
TLS provisioning is included and an HTTPS API is exposed so you can start sending and receiving fmsg messages for your domain. TCP port 4930 is also exposed for fmsg host-to-host communication.
| Name | Description |
|---|---|
| README.md | Full README for this code repository. |
| README_LOCAL_DEV.md | Run the stack locally for development purposes. |
- A domain you control, e.g
example.com - A server with a public IP and
- TCP port
4930open to the internet (fmsg TLS) - TCP port
443open to the internet (fmsg-webapi HTTPS) - TCP port
80open to the internet (only first start - required for initial Let's Encrypt certificate issuance)
- TCP port
- Docker and Docker Compose, or Podman and podman-compose
NOTE This quickstart uses docker compose throughout. If you are using Podman, replace each occurrence with podman compose.
Make sure Docker is installed and running. Create a dedicated non-root fmsg operator account, create its checkout location, and shallow-clone the repository's default branch:
sudo useradd --create-home --shell /bin/bash fmsg
sudo install -d --owner=fmsg --group=fmsg /opt/fmsg-docker
sudo -u fmsg git clone --depth 1 https://github.com/markmnl/fmsg-docker.git /opt/fmsg-docker
sudo -u fmsg -H bashCreate A (or AAAA if your public IP is IPv6) DNS records to resolve to your server IP for:
fmsg.<your-domain>fmsgapi.<your-domain>
NOTE Ensure DNS is kept up-to-date with your server's IP so you can send and receive messages! e.g. using dig, nslookup or gent hosts:
getent hosts fmsg.<your-domain>
getent hosts fmsgapi.<your-domain>As the fmsg user, configure the checkout in /opt/fmsg-docker. Copy the example env file:
cd /opt/fmsg-docker
cp .env.example compose/.envEdit compose/.env and set at least:
FMSG_DOMAIN=example.com
CERTBOT_EMAIL=
FMSG_API_TOKEN_ED25519_PRIVATE_KEY=<base64-ed25519-seed>
FMSGD_READER_PGPASSWORD=<strong-password>
FMSGD_WRITER_PGPASSWORD=<strong-password>
FMSGID_READER_PGPASSWORD=<strong-password>
FMSGID_WRITER_PGPASSWORD=<strong-password>NOTE
- FMSG_DOMAIN is the domain part of fmsg addresses e.g. in
@user@example.comwould beexample.com. This server you are setting up is located at the subdomainfmsg.<your-domain>but addresses will be at<your-domain>, you should only specify<your-domain>for FMSG_DOMAIN here. - CERTBOT_EMAIL is an email address supplied to Let's Encrypt for e.g. TLS expiry warnings.
- Generate
FMSG_API_TOKEN_ED25519_PRIVATE_KEYwithopenssl rand -base64 32. - For all secrets and passwords env vars create your own.
Exit the fmsg login shell and start the stack for the first time by changing into /opt/fmsg-docker/compose and passing the one-time postgres super user password on the command line. (Generate and keep PGPASSWORD yourself, this will only be needed first time running compose up).
exitcd /opt/fmsg-docker/compose
sudo env PGPASSWORD='<postgres-password>' docker compose up -dFirst time will take a few minutes to pull docker images and initalise the database. After than check everything started with:
sudo docker compose ps
If fmsgd is running and port 4930 is reachable on fmsg.<your domain>, the host is up.
On first start, certbot will request Let's Encrypt TLS certificates for fmsg.<your-domain> and fmsgapi.<your-domain>. If certificate issuance fails (e.g. the domains do not resolve to the server or port 80 is blocked by firewall, or already in use), the stack will not start. Certificates are persisted in a Docker volume and reused on subsequent starts. Once certificates are issued port 80 is no longer needed until certificates need to be renewed - usually 90 days.
Create users (message stores, analoguous to mailboxes) by placing a CSV file in the fmsgid_data volume at /opt/fmsgid/data/addresses.csv. The format is:
address,display_name,accepting_new,limit_recv_size_total,limit_recv_size_per_msg,limit_recv_size_per_1d,limit_recv_count_per_1d,limit_send_size_total,limit_send_size_per_msg,limit_send_size_per_1d,limit_send_count_per_1d
@alice@<your-domain>,Alice,true,102400000,10240,102400,1000,102400000,10240,102400,1000Replace <your-domain> with the value of your domain.
You can copy it into the volume with (file changes will sync automatically):
sudo docker compose -f /opt/fmsg-docker/compose/docker-compose.yml cp /path/to/your/addresses.csv fmsgid:/opt/fmsgid/data/addresses.csvCreate an API key for a user, then use it with fmsg-cli or access the fmsg-webapi API directly at https://fmsgapi.<your-domain>.
To create an API key for @alice@<your-domain>:
sudo docker compose exec fmsg-webapi /opt/fmsg-webapi/fmsg-webapi api-key create-delegation \
-owner @alice@<your-domain> \
-agent cli \
-addr @alice@<your-domain> \
-cidr 0.0.0.0/0,::/0 \
-expires 2027-12-31T00:00:00Z
- The command prints the plaintext API key only once. Store it securely.
ownerandaddrare the same, so the key authenticates as@alice@example.comin the above example.- CIDR values:
-cidr 0.0.0.0/0,::/0permit connections from any IPv4 or IPv6 address, restrict per your requirements.
Then use it from fmsg-cli:
# save this export line to your ~/.bash_profile for future use
export FMSG_API_URL=https://fmsgapi.<your-domain>
fmsg login <fmsg-key>
fmsg list
fmsg send @recipient@example.com "Hello, world!"
fmsg send @recipient@example.com ./message.txt
echo "Hello via stdin" | fmsg send @recipient@example.com -To use the API directly instead of fmsg-cli (which uses the API under the hood), exchange the API key for a short-lived JWT:
export FMSG_API_URL=https://fmsgapi.<your-domain>
export FMSG_API_KEY=fmsgk_<key_id>_<secret>
curl --fail --silent --show-error \
-X POST \
-H "Authorization: Bearer $FMSG_API_KEY" \
"$FMSG_API_URL/fmsg/token"The response contains an access_token. Send it as a Bearer token with API requests:
curl --fail --silent --show-error \
-H "Authorization: Bearer <access_token>" \
"$FMSG_API_URL/fmsg"