Maddy Composable Mail Server Installation

Maddy is a lightweight, composable mail server delivered as a single binary with built-in SMTP, IMAP, TLS automation, and DKIM signing. This guide covers installing Maddy on Linux, configuring SMTP/IMAP, automating TLS certificates, setting up DKIM, and managing users.

Prerequisites

  • Ubuntu 20.04+/Debian 11+ or CentOS 8+/Rocky Linux 8+
  • A domain name pointing to your server
  • Ports 25, 465, 587, 993, 143 open in your firewall
  • Reverse DNS (PTR) set to match your hostname
  • Root or sudo access
  • No existing mail server running

Installing Maddy

# Method 1: Download the pre-built binary
VERSION=$(curl -s https://api.github.com/repos/foxcpp/maddy/releases/latest \
  | grep '"tag_name"' | cut -d'"' -f4)

wget "https://github.com/foxcpp/maddy/releases/download/${VERSION}/maddy-${VERSION#v}-linux-amd64.tar.zst"
tar -I zstd -xf maddy-*.tar.zst

# Install binary and man pages
sudo install -m 755 maddy /usr/local/bin/maddy
sudo install -m 644 maddy.1 /usr/local/share/man/man1/

# Method 2: Build from source (requires Go 1.21+)
git clone https://github.com/foxcpp/maddy.git
cd maddy
go build ./cmd/maddy
sudo mv maddy /usr/local/bin/

Create a dedicated user and directories:

sudo useradd -r -s /sbin/nologin maddy
sudo mkdir -p /etc/maddy /var/lib/maddy /var/log/maddy
sudo chown maddy:maddy /var/lib/maddy /var/log/maddy
sudo chmod 750 /var/lib/maddy

Configuration Overview

Maddy uses a single configuration file at /etc/maddy/maddy.conf. Configuration uses a block-based syntax where modules are composed together:

# Install the default config template
wget -O /etc/maddy/maddy.conf \
  https://raw.githubusercontent.com/foxcpp/maddy/master/maddy.conf

sudo chown maddy:maddy /etc/maddy/maddy.conf
sudo chmod 640 /etc/maddy/maddy.conf

Edit the two key lines at the top of the config:

sudo nano /etc/maddy/maddy.conf
# Replace these with your actual values
$(hostname) = mail.yourdomain.com
$(primary_domain) = yourdomain.com
$(local_domains) = $(primary_domain)

SMTP Configuration

The default config includes SMTP with common settings. Key sections to verify:

# /etc/maddy/maddy.conf - SMTP submission (port 587)
smtp tcp://0.0.0.0:587 {
    tls off   # STARTTLS is used; TLS upgrade happens after EHLO

    auth.plain inbound_sasl

    source $(local_domains) {
        destination $(local_domains) {
            deliver_to &local_mailboxes
        }
        destination postmaster $(local_domains) {
            deliver_to &local_mailboxes
        }
        default_destination {
            modify {
                dkim sign {
                    domains $(primary_domain)
                    selector default
                    key_path /var/lib/maddy/dkim_keys/{domain}-{selector}.key
                }
            }
            deliver_to &remote_queue
        }
    }
    default_source {
        reject 501 5.1.8 "Source domain not accepted"
    }
}

# Incoming SMTP (port 25)
smtp tcp://0.0.0.0:25 {
    tls off

    destination postmaster $(local_domains) {
        deliver_to &local_mailboxes
    }
    default_destination {
        reject 550 5.1.1 "User not found"
    }
}

IMAP Configuration

# IMAP4 (port 143 with STARTTLS)
imap tcp://0.0.0.0:143 {
    tls off  # STARTTLS

    auth.plain inbound_sasl

    storage &local_mailboxes
}

# IMAPS (port 993, implicit TLS)
imap tls://0.0.0.0:993 {
    storage &local_mailboxes
    auth.plain inbound_sasl
}

TLS Automation

Maddy handles Let's Encrypt automatically:

# In maddy.conf, configure TLS
tls file /etc/maddy/tls/fullchain.pem /etc/maddy/tls/privkey.pem

# Or use automatic ACME (Let's Encrypt)
tls {
    loader acme {
        email [email protected]
        agreed
        ca https://acme-v02.api.letsencrypt.org/directory
    }
}

For manual certificate management:

# Install Certbot
sudo apt install -y certbot

# Obtain certificates (stop maddy first if it uses port 80)
sudo certbot certonly --standalone -d mail.yourdomain.com \
  --non-interactive --agree-tos -m [email protected]

# Create symlinks for maddy
sudo mkdir -p /etc/maddy/tls
sudo ln -s /etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem \
  /etc/maddy/tls/fullchain.pem
sudo ln -s /etc/letsencrypt/live/mail.yourdomain.com/privkey.pem \
  /etc/maddy/tls/privkey.pem
sudo chown -h maddy:maddy /etc/maddy/tls/*.pem

DKIM Signing

Generate DKIM keys:

# Create keys directory
sudo mkdir -p /var/lib/maddy/dkim_keys
sudo chown maddy:maddy /var/lib/maddy/dkim_keys

# Generate a 2048-bit RSA key
sudo -u maddy maddy dkim gen \
  --domain yourdomain.com \
  --selector default \
  --directory /var/lib/maddy/dkim_keys

# View the public key for DNS
cat /var/lib/maddy/dkim_keys/yourdomain.com-default.dns

Add the output as a DNS TXT record:

Name: default._domainkey.yourdomain.com
Value: v=DKIM1; k=rsa; p=MIIBIjAN...

DKIM signing configuration in maddy.conf:

modify.dkim default_dkim {
    domains yourdomain.com
    selector default
    key_path /var/lib/maddy/dkim_keys/{domain}-{selector}.key
    sign_all_headers true
}

User Management

Maddy uses a credential store for authentication:

# Start Maddy first to initialize the database
sudo systemctl start maddy

# Add a user
sudo maddy creds create [email protected]
# Prompts for password

# Or specify password directly (be careful with shell history)
echo "SecurePassword123!" | sudo maddy creds create [email protected] --stdin

# Create the mailbox
sudo maddy imap-acct create [email protected]

# List users
sudo maddy creds list

# Change a password
sudo maddy creds password [email protected]

# Remove a user
sudo maddy imap-acct remove [email protected]
sudo maddy creds remove [email protected]

Storage Backends

Maddy uses its own storage format by default. Configure in maddy.conf:

# Default local storage
storage.imapsql local_mailboxes {
    driver sqlite3
    dsn /var/lib/maddy/imapsql.db
}

# Or use PostgreSQL for larger deployments
storage.imapsql local_mailboxes {
    driver postgres
    dsn "host=127.0.0.1 port=5432 dbname=maddy user=maddy password=maddy_pass sslmode=disable"
}

Create systemd service:

sudo tee /etc/systemd/system/maddy.service << 'EOF'
[Unit]
Description=Maddy Mail Server
After=network.target

[Service]
Type=notify
User=maddy
Group=maddy
ExecStart=/usr/local/bin/maddy -config /etc/maddy/maddy.conf run
Restart=on-failure
RestartSec=5

# Allow binding to privileged ports
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now maddy
sudo systemctl status maddy

Troubleshooting

Maddy fails to start - "address already in use":

# Check what's using the ports
ss -tlnp | grep -E ':25|:587|:993|:143'
systemctl stop postfix exim 2>/dev/null

TLS certificate errors:

# Verify certificate files are readable by maddy user
ls -la /etc/maddy/tls/
sudo -u maddy openssl x509 -in /etc/maddy/tls/fullchain.pem -noout -dates

Users can't authenticate:

# Check credential store
sudo maddy creds list
sudo journalctl -u maddy -f | grep -i auth

# Reset a password
sudo maddy creds password [email protected]

Mail not being delivered externally:

# Check mail queue
sudo maddy queue list

# Retry stuck messages
sudo maddy queue retry --all

# Check DKIM signature
sudo journalctl -u maddy | grep -i dkim

Port 25 connection refused by destination:

# Verify your IP isn't blacklisted
host -t A $(dig -x $(curl -s ifconfig.me) +short). zen.spamhaus.org

# Check outbound connectivity
telnet gmail-smtp-in.l.google.com 25

Conclusion

Maddy's single-binary design with composable modules makes it one of the easiest complete mail servers to deploy and maintain. Automatic TLS, built-in DKIM signing, and SQLite-backed storage eliminate most of the complexity found in traditional Postfix/Dovecot stacks. For small to medium deployments with a single domain, Maddy delivers full functionality with minimal operational overhead.