Self-contained, persistent mail stack:
- Postfix SMTP (25, 587) with DKIM signing
- Dovecot IMAP/IMAPS and SASL auth
- Dovecot LMTP for final delivery to Maildir
- Shared TLS certificates (read-only) for Postfix and Dovecot
- Catch‑all routing to your submission user by default
- Multi‑domain sending/signing with simple Make targets
- Docker and Docker Compose
- GNU Make
Install GNU Make:
- Debian/Ubuntu:
sudo apt update && sudo apt install make - Fedora/RHEL:
sudo dnf install make(orsudo yum install make) - Arch:
sudo pacman -S make - macOS:
brew install make(usegmakeif installed as GNU Make) - Windows: use WSL (then follow Linux), or MSYS2 (
pacman -S make)
Verify: make --version
# Clone and enter
git clone https://github.com/tayyebi/mailserver mailserver
cd mailserver
# Create config from template
cp .env.example .env
# Edit: MAIL_DOMAIN, MAIL_HOST, TZ
# One‑shot bootstrap (idempotent)
make installmake install will:
- Ensure data directories exist
- Generate self‑signed TLS certs if missing
- Start opendkim, dovecot, and postfix
- Run health checks
- A: MAIL_HOST → server public IP
- MX: your domain(s) → MAIL_HOST
- PTR: reverse DNS → MAIL_HOST
- SPF (TXT at domain):
v=spf1 a mx ~all - DKIM (TXT at default._domainkey.domain): value from
data/opendkim/keys/<domain>/default.txt - DMARC (TXT at _dmarc.domain):
v=DMARC1; p=quarantine; rua=mailto:dmarc@domain; fo=1
- data/ssl — TLS certs/keys (shared read‑only by Postfix and Dovecot)
- data/postfix — Postfix configs and maps (virtual, virtual_domains)
- data/spool — Postfix queue
- data/opendkim/keys — DKIM keys (per domain)
- data/opendkim/conf — DKIM tables and config
- data/dovecot — Dovecot state/indexes
- data/dovecot-conf — Dovecot config (dovecot.conf, users)
- data/mail — Maildir storage: data/mail///{cur,new,tmp}
- data/pixel — Pixel tracking data and socket directory
The mailserver includes pixel tracking functionality that injects tracking pixels and domain-wide footers into HTML emails to track opens.
Pixel tracking can be configured via environment variables in your .env file:
TRACKING_REQUIRES_OPT_IN(default:false): Iffalse, tracking is enabled by default for all HTML emails. Iftrue, tracking only occurs when emails include an opt-in header (seeOPT_IN_HEADER).OPT_IN_HEADER(default:X-Track-Open): Header name to check for opt-in whenTRACKING_REQUIRES_OPT_IN=true.PIXEL_BASE_URL(default:https://${MAIL_HOST}:8443/pixel?id=): Base URL for tracking pixels.DISCLOSURE_HEADER(default:X-Tracking-Notice): Header name for privacy disclosure.INJECT_DISCLOSURE(default:true): Whether to inject disclosure header into tracked emails.PIXEL_MILTER_ADDRESS(default:0.0.0.0:8892): Address and port for the milter service.
Example .env configuration for domain-wide tracking (default):
TRACKING_REQUIRES_OPT_IN=false
PIXEL_BASE_URL=https://mail.gordarg.com:8443/pixel?id=Example .env configuration for opt-in only tracking:
TRACKING_REQUIRES_OPT_IN=true
OPT_IN_HEADER=X-Track-OpenTo ensure pixelmilter is correctly applied to the whole project:
make verify-pixelmilterThis command checks:
- pixelmilter container is running
- Socket file exists and is accessible
- Postfix configuration includes pixelmilter
- Postfix can communicate with pixelmilter
Postfix configuration files (main.cf, master.cf) are generated from templates (.tmpl files) when the container starts. To ensure configuration files are updated after editing templates:
Option 1: Use the update-config target (recommended)
make update-configThis will:
- Rebuild the Postfix container to apply template changes
- Restart Postfix to load the new configuration
- Verify the configuration is valid
- Reload Postfix to apply changes
Option 2: Manual restart
# Rebuild and restart Postfix
docker-compose build postfix
docker-compose restart postfix
# Or restart all services
make restartOption 3: Reload only (if no template changes)
make reload- Templates:
postfix/main.cf.tmpl,postfix/master.cf.tmpl - Rendered configs: Generated inside the Postfix container at
/etc/postfix/main.cf,/etc/postfix/master.cf - Pixelmilter connection: TCP port 8892 (configurable via
PIXEL_MILTER_ADDRESSenvironment variable)
Pixelmilter is configured in postfix/main.cf.tmpl:
smtpd_miltersincludesinet:${PIXEL_MILTER_IP}:8892for incoming mail- Pixelmilter listens on TCP port 8892 (configurable via
PIXEL_MILTER_ADDRESSenvironment variable) - Postfix connects to pixelmilter via the Docker network using the
PIXEL_MILTER_IPaddress
After modifying main.cf.tmpl or master.cf.tmpl, always run make update-config to apply changes.
- Replace self‑signed TLS cert with a real one when ready (overwrite in data/ssl and
docker compose restart mail dovecot) - Never commit .env, keys, or mail data
- Consider firewalling 25/587/993 as appropriate
For detailed troubleshooting steps, diagnostic commands, and common issues, please refer to TROUBLESHOOTING.md.