📮 Run yourself a mail server
- Start to finish guide
- Configuration notes:
- Webmail
Guide
Make a compose project:
(using acmesh for certs, unbound for DNS, and maddy for the mail server itself)
# compose.yml
name: maddy
services:
# TLS certificates
acmesh-daemon:
image: neilpang/acme.sh
volumes:
- "/data/maddy_acme.sh:/acme.sh"
command: daemon
restart: always
env_file: ./.env
networks:
maddy-network:
aliases:
- acmesh
# dns server
unbound:
image: mvance/unbound:latest
# for arm64:
# image: mvance/unbound-rpi
restart: always
networks:
maddy-network:
ipv4_address: ${IPV4_NETWORK:-10.8.1}.254
aliases:
- unbound
# email server
maddy:
# check https://github.com/foxcpp/maddy/releases
image: ghcr.io/foxcpp/maddy:0.8.1
volumes:
- "/data/maddy_acme.sh:/etc/maddy/certs"
- "/data/maddy:/data"
- type: bind
source: ./maddy.conf
target: /etc/maddy/maddy.conf
ports:
- "25:25"
- "143:143"
- "465:465"
- "587:587"
- "993:993"
entrypoint: /bin/maddy -config /etc/maddy/maddy.conf
command: run
restart: always
networks:
maddy-network:
aliases:
- maddy
dns:
- ${IPV4_NETWORK:-10.8.1}.254
networks:
maddy-network:
driver: bridge
driver_opts:
com.docker.network.bridge.name: br-maddy
ipam:
driver: default
config:
- subnet: ${IPV4_NETWORK:-10.8.1}.0/24
- subnet: ${IPV6_NETWORK:-fd4d:6169:6c63:6f77::/64}
Create configuration files:
.env
: for acmesh, e.g.CF_Token=...
maddy.conf
: download default config from Github
We have to make some changes to the config file:
# maddy.conf
# (1) change the hostname
# change these to where you want your mail server to exist
$(hostname) = example.org
$(primary_domain) = example.org
$(local_domains) = $(primary_domain)
# ^^ you can add more domains to local_domains too
# for example:
$(hostname) = mx1.is.horse
$(primary_domain) = is.horse
$(local_domains) = $(primary_domain) insrt.uk
# this means my mail server will live at mx1.is.horse
# and send/receive mail for is.horse and insrt.uk
# (2) add reverse DNS hostname if you are sending from the machine you're running this on
# ... otherwise scroll down to configuration notes on how to do SMTP forwarding through smth else
smtp tcp://0.0.0.0:25 {
...
dmarc yes
+ hostname fully-qualified-host.example.com
check { }
...
}
target.remote outbound_delivery {
limits { }
+ hostname fully-qualified-host.example.com
mx_auth { }
}
Now let’s make the certificate:
docker compose up -d acmesh-daemon
docker compose exec acmesh-daemon --register-account -m me@insrt.uk
docker compose exec acmesh-daemon mkdir -p /etc/maddy/certs/mx1.is.horse
docker compose exec acmesh-daemon --issue --dns dns_cf -d mx1.is.horse --key-file /etc/maddy/certs/mx1.is.horse/privkey.pem --fullchain-file /etc/maddy/certs/mx1.is.horse/fullchain.pem
Now you need to configure DNS, pls RTFM maddy docs here, it has everything you need. NB. if you’re using mail forwarding (i.e. Scaleway TEM, see configuration notes) then you don’t need to copy the DKIM as you’re not sending any mail from your IP!
Final stretch, bring all services up and create your first mail account:
docker compose up -d
# create credentials
docker compose exec maddy /bin/maddy -config /etc/maddy/maddy.conf creds create postmaster@is.horse
# create imap storage
docker compose exec maddy /bin/maddy -config /etc/maddy/maddy.conf imap-acct create postmaster@is.horse
Make sure to port-forward and open ports!
sudo ufw allow 25 comment 'maddy SMTP'
sudo ufw allow 143 comment 'maddy IMAP'
sudo ufw allow 993 comment 'maddy IMAP'
sudo ufw allow 465 comment 'maddy submission'
sudo ufw allow 587 comment 'maddy submission'
Done! You can now login and hopefully send email.
Configuration Notes
How do I run this at home?
In all likelihood, the ports you need are blocked and your IP has garbage reputation, you have a couple of options:
- Rent a VPS to proxy your mail server (e.g. fast reverse proxy) and use an external mail sender like TEM (see next section)
- Rent a VPS to run this on (and likely still run into the reputation issue, see next section)
- Don’t - out of scope of this article
How do I use a 3rd party SMTP sender so I don’t get blocked?
First, sign up with a provider such as Scaleway and add your domains to TEM (or SES, or whatever), they will give you a username and SMTP host, you have to create an API key which is used as the password, then:
# maddy.conf
# (1) add your delivery route
submission tls://0.0.0.0:465 tcp://0.0.0.0:587 {
...
source $(local_domains) {
...
default_destination {
- modify {
- dkim $(primary_domain) $(local_domains) default
- }
- deliver_to &remote_queue
+ deliver_to smtp tcp://smtp.tem.scaleway.com:587 {
+ auth plain USERNAME PASSWORD
+ }
}
}
}
# (2) remove the old queue blocks
- target.remote outbound_delivery {
- ...
- }
- target.queue remote_queue {
- ...
- }
I want to alias or wildcard my email?
Read https://maddy.email/reference/modifiers/envelope/#envelope-sender-recipient-rewriting.
e.g. you could:
msgpipeline local_routing {
destination postmaster $(local_domains) {
modify {
replace_rcpt &local_rewrites
+ replace_rcpt regexp "(.+)@is.horse" "me@insrt.uk"
+ replace_rcpt regexp "(.+)@insrt.uk" "me@insrt.uk"
}
}
}
How do I make my email client pick up config automatically?
Add this to your configuration:
# compose.yml
services:
mail-autodiscover-autoconfig:
image: wdes/mail-autodiscover-autoconfig:latest
restart: always
ports:
- "8088:80"
environment:
ROCKET_PROFILE: production
ROCKET_ADDRESS: "0.0.0.0"
ROCKET_PORT: "80"
# set to where your mail server lives
IMAP_HOSTNAME: mx1.is.horse
SMTP_HOSTNAME: mx1.is.horse
POP_HOSTNAME: mx1.is.horse
# regenerate both UUIDs using https://uuidgenerator.net
APPLE_MAIL_UUID: 3b88a342-66fd-4b7e-9f6a-d9199f7536e5
APPLE_PROFILE_UUID: ae7ffc8c-dc1b-4fa3-abbe-78048dc58f42
Configure reverse proxy of port 8088 to all your relevant domains: autoconfig.your.domain
Webmail
Roundcube
Add the following configuration:
# compose.yml
services:
webmail:
image: roundcube/roundcubemail
ports:
- "8080:80"
volumes:
- "./config.inc.php:/var/www/html/config/config.inc.php"
- "/data/maddy_roundcube_db:/var/roundcube/db"
environment:
ROUNDCUBEMAIL_DB_TYPE: sqlite
ROUNDCUBEMAIL_DEFAULT_HOST: tls://mx.your.mail
ROUNDCUBEMAIL_SMTP_SERVER: tls://mx.your.mail
ROUNDCUBEMAIL_INSTALL_PLUGINS: 1
ROUNDCUBEMAIL_PLUGINS: archive,zipdownload
depends_on:
- maddy
restart: always
networks:
maddy-network:
aliases:
- webmail
Configure reverse proxy of port 8080 to some HTTPS endpoint like mail.your.domain
.
# config.inc.php
<?php
$config['log_driver'] = 'stdout';
$config['support_url'] = 'https://your.website';
$config['product_name'] = 'Cool Mail Server';
$config['skin_logo'] = 'https://path.to/logo.svg';
// change to random 24 character string!
$config['des_key'] = 'AAAAAAAAAAAAAAAAAAAAAAAA';
$config['plugins'] = [
'archive',
'zipdownload',
];
$config['zipdownload_selection'] = true;
$config['enable_spellcheck'] = true;
$config['spellcheck_engine'] = 'pspell';
$config['skin'] = 'elastic';
include(__DIR__ . '/config.docker.inc.php');