Ansible role and package build system for Maddy Mail Server - a modern mail server that combines SMTP (MTA), IMAP, DKIM, SPF, DMARC, and ACME in a single daemon.
- Automatic .deb and .rpm package builds via GitHub Actions
- Ansible role for Maddy installation and configuration
- ACME (Let's Encrypt) support for automatic TLS certificates
- Automatic DKIM key generation
- Fail2ban integration for brute-force protection
- Firewall configuration (UFW/firewalld)
- Ansible 2.14+
- Target OS: Debian 11/12, Ubuntu 20.04/22.04/24.04
Before installation, configure DNS records:
- A/AAAA record for the mail server
- PTR (reverse DNS) record
git clone --recursive https://github.com/foxzi/maddy-setup.git
cd maddy-setupansible-galaxy collection install -r requirements.ymlcp inventory/example.yml inventory/production.yml
# Edit inventory/production.ymlansible-playbook -i inventory/production.yml site.yml# Maddy version
maddy_version: "0.8.2"
# Domain and host
maddy_hostname: "mx1.example.org"
maddy_primary_domain: "example.org"
maddy_local_domains:
- "example.org"
# TLS (ACME)
maddy_tls_mode: "acme"
maddy_acme_email: "admin@example.org"
# DKIM
maddy_dkim_selector: "default"
maddy_dkim_key_type: "ed25519" # or rsa2048maddy_users:
- email: "user@example.org"
password: "secure_password"maddy_aliases:
postmaster: "admin@example.org"
abuse: "admin@example.org"Enable Prometheus/OpenMetrics endpoint:
maddy_metrics_enabled: true
maddy_metrics_address: "127.0.0.1" # or "0.0.0.0" for external access
maddy_metrics_port: 9749Scrape endpoint: http://127.0.0.1:9749/metrics
Protect metrics endpoint with TLS and authentication:
maddy_metrics_enabled: true
maddy_caddy_enabled: true
maddy_caddy_metrics_port: 8443
# Option 1: Basic auth
maddy_caddy_auth_mode: "basic"
maddy_caddy_basic_user: "prometheus"
maddy_caddy_basic_password: "secure_password"
# Option 2: IP whitelist
maddy_caddy_auth_mode: "ip"
maddy_caddy_allowed_ips:
- "10.0.0.0/8"
- "192.168.1.100"Secure endpoint: https://mail-server:8443/metrics (self-signed TLS)
Available metrics:
maddy_smtp_failed_logins- AUTH failuresmaddy_smtp_started_transactions- Started SMTP transactionsmaddy_smtp_completed_transactions- Completed transactionsmaddy_queue_length- Queued messagesmaddy_check_reject- Rejected by checksmaddy_remote_conns_tls_level- Outbound TLS security level
Allow trusted IPs to send mail through this server without authentication:
maddy_relay_enabled: true
maddy_relay_port: 2525
# Trusted networks (CIDR)
maddy_relay_trusted_networks:
- "10.0.0.0/8"
- "192.168.1.0/24"
# Optional: limit relay to specific domains
maddy_relay_allowed_domains:
- "gmail.com"
- "company.com"Firewall rules are automatically configured to allow access only from trusted networks.
See roles/maddy/defaults/main.yml for the full list of variables.
After installation, configure the following DNS records:
example.org. IN MX 10 mx1.example.org.
example.org. IN TXT "v=spf1 mx ~all"
_dmarc.example.org. IN TXT "v=DMARC1; p=quarantine; ruf=mailto:postmaster@example.org"
The DKIM public key is generated automatically on first run. After installation, find it in:
/etc/maddy/dkim_dns_records.txt
Or run:
maddyctl creds dkim-key -S default example.orgPre-built packages for amd64 and arm64 are available on GitHub Releases.
Note: amd64 packages are built with CGO for full SQLite support in auth.pass_table. arm64 packages are built without CGO and use the transpiled SQLite driver (works for storage.imapsql only).
# Install dependencies (Debian/Ubuntu)
sudo apt-get install -y golang scdoc libsqlite3-dev
# Install nfpm
go install github.com/goreleaser/nfpm/v2/cmd/nfpm@latest
# Build packages
./build/scripts/build.sh --version 0.8.2Packages are built automatically when a tag is created:
git tag v0.8.3
git push origin v0.8.3Built packages are published to GitHub Releases.
# List users
maddyctl creds list
# Create user
maddyctl creds create user@example.org
# Change password
maddyctl creds password user@example.org
# Delete user
maddyctl creds remove user@example.org
# Show DKIM key
maddyctl creds dkim-key -S default example.orgsystemctl status maddy
journalctl -u maddy -fRun automated tests with Molecule and Docker:
# Install dependencies
pip install molecule molecule-plugins[docker] ansible docker
# Run tests
cd roles/maddy
molecule testTests verify:
- Binary installation
- Configuration syntax
- Service startup
- Port availability (SMTP, IMAP)
- Metrics endpoint
ansible-playbook -i inventory/production.yml site.yml --check --diff- mail-tester.com - comprehensive check
- mxtoolbox.com - DNS record verification
- dkimvalidator.com - DKIM verification
# Install swaks
apt install swaks
# Send test email
swaks --to test@gmail.com --from user@example.org --server mx1.example.orgmaddy-setup/
├── .github/workflows/ # CI/CD for package builds
├── build/
│ ├── nfpm.yaml # Package build configuration
│ └── scripts/ # Build scripts
├── maddy/ # Git submodule with Maddy sources
├── roles/maddy/ # Ansible role
│ ├── defaults/ # Default variables
│ ├── handlers/ # Handlers
│ ├── tasks/ # Tasks
│ └── templates/ # Config templates
├── inventory/ # Inventory examples
├── site.yml # Main playbook
└── requirements.yml # Ansible Galaxy dependencies
- All passwords are passed with
no_log: true - Using Ansible Vault for password storage is recommended
- Fail2ban protects against brute-force attacks
- Systemd unit includes strict sandboxing
# Create encrypted variables file
ansible-vault create inventory/vault.yml
# Run playbook with vault
ansible-playbook -i inventory/production.yml site.yml --ask-vault-pass- Update the submodule:
cd maddy
git fetch --tags
git checkout v0.8.3
cd ..- Build new packages:
git tag v0.8.3
git push origin v0.8.3- Update
maddy_versionin inventory and run the playbook.
# Check configuration
maddy -config /etc/maddy/maddy.conf verify
# View logs
journalctl -u maddy -n 100- Ensure port 443 is open
- Verify DNS records
- Try staging CA:
maddy_acme_staging: true
Keys are generated on first email send. Send a test email.
MIT