Requirements
- A Linux host with Docker Engine (including the build plugin), Git, Python 3 and OpenSSL.
- For light use, 2 CPU cores, 2 GB RAM and 20 GB of free disk. Builds, especially the Next.js app, need extra memory or swap.
- An IP address or hostname that both your browser and the containers can reach. Do not use
localhost. - Free ports for the app, API and storage. The defaults are
3000,9001and9000. - Outbound HTTPS for downloads, and access to your SMTP provider for sending.
Install
http://192.168.1.10:3000 for the app, http://192.168.1.10:9001 for the API and http://192.168.1.10:9000 for storage.
For an unattended install, write a config file:
config.json
First use
1
Create your account
Open
<app_url>/auth/register. There is no default admin password.2
Connect a sender
In Settings → SMTP senders, add your SMTP provider. To use Amazon SES, follow Send with Amazon SES.
3
Authenticate your domain
Set up SPF, DKIM and DMARC with your provider before sending.
4
Test
Import contacts, pick a template and send a test to an address you control.
License
Campaigns, automations, forms, the CRM and analytics are free forever. A Xem license, $49 once from xem.email/pricing, adds newsletters, the shared inbox and Cloudflare mailboxes and removes the “Sent with Xem” footer from your emails. Dodo Payments emails the key after you buy. Add it to your install:"license_key" to config.json. The API checks the key when it starts and once a day. If the check can’t reach Dodo, a key that was valid stays valid.
HTTPS and public servers
The default HTTP addresses are for a trusted network. For a public server, put the app, API and storage behind a TLS reverse proxy, with addresses such ashttps://app.example.com, https://api.example.com and https://files.example.com. Storage requests must keep their original host, path and query so S3 signatures still match.
Restrict the published ports with a firewall that applies to Docker traffic, so outside requests go through the proxy. The installer does not set up DNS, certificates, a proxy or firewall rules.
Check status and logs
xem_storage-init service exits after creating the storage bucket, so 0/1 is expected for it.
Update
Back up first, because the API applies database migrations on start. Then run the install command again as the same user, with the same state directory. It rebuilds from the latest revision and updates the services, which can briefly interrupt service. To pin a reviewed revision:Back up
Keep an encrypted copy of the state directory, thexem_storage volume and a PostgreSQL dump:
