| Tag | Description |
|---|---|
latest |
Latest stable version from the main branch |
X.Y.Z |
Specific version (e.g., 0.18.0) |
You need a docker-compose.yml and a maddy.conf file.
In this mode, self-signed TLS certificates are automatically generated.
services:
madmail:
image: ghcr.io/themadorg/madmail:latest
restart: always
ports:
- "25:25" # SMTP
- "143:143" # IMAP (STARTTLS)
- "465:465" # Submission (TLS/SSL)
- "587:587" # Submission (STARTTLS)
- "993:993" # IMAPS (TLS/SSL)
- "80:80" # HTTP
- "443:443" # HTTPS
- "1080:1080" # Shadowsocks
volumes:
- ./maddy.conf:/data/maddy.conf
- ./data:/data
- ./tls:/data/tls
environment:
- MADDY_HOSTNAME=SERVER_IP
- MADDY_DOMAIN=[SERVER_IP]
- MADDY_PUBLIC_IP=SERVER_IP
- MADDY_SQLITE_UNSAFE_SYNC_OFF=1
$(hostname) = {env:MADDY_HOSTNAME}
$(primary_domain) = {env:MADDY_DOMAIN}
$(local_domains) = $(primary_domain) $(hostname)
$(public_ip) = {env:MADDY_PUBLIC_IP}
tls file /data/tls/fullchain.pem /data/tls/privkey.pem
state_dir /data
auth.pass_table local_authdb {
auto_create yes
table sql_table {
driver sqlite3
dsn /data/credentials.db
table_name passwords
}
}
storage.imapsql local_mailboxes {
auto_create yes
driver sqlite3
dsn /data/imapsql.db
}
hostname $(hostname)
table.chain local_rewrites {
optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3"
optional_step static {
entry postmaster postmaster@$(primary_domain)
}
}
msgpipeline local_routing {
destination postmaster $(local_domains) {
modify {
replace_rcpt &local_rewrites
}
deliver_to &local_mailboxes
}
default_destination {
reject 550 5.1.1 "User doesn't exist"
}
}
smtp tcp://0.0.0.0:25 {
dmarc yes
check {
require_mx_record
dkim
spf
}
source $(local_domains) {
reject 501 5.1.8 "Use Submission for outgoing SMTP"
}
default_source {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.1.1 "User doesn't exist"
}
}
}
submission tls://0.0.0.0:465 tcp://0.0.0.0:587 {
auth &local_authdb
tls file /data/tls/fullchain.pem /data/tls/privkey.pem
source $(local_domains) {
check {
authorize_sender {
prepare_email &local_rewrites
user_to_email identity
}
}
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
modify {
dkim $(primary_domain) $(local_domains) default
}
deliver_to &remote_queue
}
}
default_source {
reject 501 5.1.8 "Non-local sender domain"
}
}
target.remote outbound_delivery {
mx_auth {
dane
mtasts {
cache fs
fs_dir mtasts_cache/
}
local_policy {
min_tls_level encrypted
min_mx_level none
}
}
}
target.queue remote_queue {
target &outbound_delivery
autogenerated_msg_domain $(primary_domain)
bounce {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
}
}
}
imap tls://0.0.0.0:993 tcp://0.0.0.0:143 {
auth &local_authdb
storage &local_mailboxes
tls file /data/tls/fullchain.pem /data/tls/privkey.pem
}
chatmail tls://0.0.0.0:443 tcp://0.0.0.0:80 {
mail_domain $(primary_domain)
mx_domain $(hostname)
web_domain $(hostname)
public_ip $(public_ip)
auth_db local_authdb
storage local_mailboxes
tls file /data/tls/fullchain.pem /data/tls/privkey.pem
ss_addr 0.0.0.0:1080
}
MADDY_DOMAIN: [203.0.113.50]
The database is locked error in SQLite is resolved by using PostgreSQL.
Also, pub/sub for real-time IMAP updates becomes available.
services:
madmail:
image: ghcr.io/themadorg/madmail:latest
restart: always
ports:
- "25:25"
- "143:143"
- "465:465"
- "587:587"
- "993:993"
- "80:80"
- "443:443"
- "1080:1080"
volumes:
- ./maddy.conf:/data/maddy.conf
- ./data:/data
- ./tls:/data/tls
environment:
- MADDY_HOSTNAME=SERVER_IP
- MADDY_DOMAIN=[SERVER_IP]
- MADDY_PUBLIC_IP=SERVER_IP
- POSTGRES_USER=madmail
- POSTGRES_PASSWORD=madmail_pass
- POSTGRES_DB=madmail
depends_on:
- db
db:
image: postgres:alpine
restart: always
environment:
- POSTGRES_USER=madmail
- POSTGRES_PASSWORD=madmail_pass
- POSTGRES_DB=madmail
volumes:
- ./pgdata:/var/lib/postgresql/data
services:
madmail:
image: ghcr.io/themadorg/madmail:latest
restart: always
ports:
- "25:25"
- "143:143"
- "465:465"
- "587:587"
- "993:993"
- "80:80"
- "443:443"
- "1080:1080"
volumes:
- ./maddy.conf:/data/maddy.conf
- ./data:/data
environment:
- MADDY_HOSTNAME=chat.example.org
- MADDY_DOMAIN=chat.example.org
- MADDY_PUBLIC_IP=203.0.113.50
- MADDY_ACME_EMAIL=admin@example.org
/data/autocert/ and auto-renewed| Port | Protocol | Description |
|---|---|---|
| 25 | SMTP | Incoming/Outgoing email |
| 143 | IMAP | Reading email (STARTTLS) |
| 465 | Submission | Outgoing email (TLS/SSL) |
| 587 | Submission | Outgoing email (STARTTLS) |
| 993 | IMAPS | Reading email (TLS/SSL) |
| 80 | HTTP | Registration + ACME challenge |
| 443 | HTTPS | Registration + ALPN mux |
| 1080 | SOCKS5 | Shadowsocks proxy |
| Container Path | Description |
|---|---|
/data/maddy.conf | Server configuration file |
/data | State directory (databases, keys, certs) |
/data/tls | TLS certificate files |
docker build -t madmail .
docker compose up -d --build
Docker images are automatically built and pushed to GHCR on every push to the main branch.