Setting up a mail server with Rainloop Webmail and Maddy

Maddy

Maddy Mail Server implements all functionality required to run a e-mail server. It can send messages via SMTP (works as MTA), accept messages via SMTP (works as MX) and store messages while providing access to them via IMAP. In addition to that it implements auxiliary protocols that are mandatory to keep email reasonably secure (DKIM, SPF, DMARC, DANE, MTA-STS). It replaces Postfix, Dovecot, OpenDKIM, OpenSPF, OpenDMARC and more with one daemon with uniform configuration and minimal maintenance cost.

Rainloop Webmail

Modest system requirements, decent performance, simple installation and upgrade,no database required - all these make RainLoop Webmail a perfect choice for your email solution.

📄Preparation

🔧Install Docker and Docker Compose

Here, Docker Compose is used to deploy Rainloop Webmail and Maddy. Instructions for installing Docker and Docker Compose are omitted

Create the configuration for the docker-compose.yml file

1mkdir mail/
2cd mail/
3nano docker-compose.yml

The configuration for the docker-compose.yml file is as follows:

 1services:
 2
 3  maddy:
 4    image: foxcpp/maddy:latest
 5    container_name: maddy
 6    restart: unless-stopped
 7    volumes: #mount point
 8      - ./maddy:/data 
 9      - /etc/letsencrypt/live/mail.example.com/fullchain.pem:/data/local_certs/cert.pem:ro #Mount the email domain certificate into the Docker container
10      - /etc/letsencrypt/live/mail.example.com/privkey.pem:/data/local_certs/key.pem:ro ##Mount the email domain key into the Docker container
11    ports:
12      - 25:25/tcp
13      - 465:465/tcp
14      - 993:993/tcp
15
16  rainloop:
17    image: php:fpm-alpine3.15
18    container_name: rainloop
19    restart: unless-stopped
20    volumes:
21      - ./rainloop:/var/www/html
22    ports:
23      - 127.0.0.1:9000:9000
24    logging:
25      driver: json-file
26      options:
27        max-size: "1m"
28        max-file: "10"

🔎Install RainLoop Webmail

1cd ~/mail/
2mkdir rainloop/
3wget https://www.rainloop.net/repository/webmail/rainloop-latest.zip
4unzip rainloop-latest.zip

Configure Nginx as a reverse proxy for Rainloop Webmail

 1server {
 2    server_name mail.example.com;
 3    listen 443 ssl http2;
 4    listen [::]:443 ssl http2;
 5    ssl_certificate /data/certs/fullchain.pem; #Email domain certificate
 6    ssl_certificate_key /data/certs/cert.pem;  #Email domain key
 7
 8    access_log  /var/log/nginx/access_mail.log;
 9    error_log   /var/log/nginx/error_mail.log;
10
11    root /data/rainloop/;
12    index  index.php;
13
14    location ~ \.php$ {
15        root           /var/www/html;
16        fastcgi_pass   127.0.0.1:9000;
17        fastcgi_index  index.php;
18        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
19        include        fastcgi_params;
20    }
21
22    location ^~ /data {
23                       deny all; #users cannot access the Rainloop directory directly via the Internet
24    }
25}

💡Configure Maddy

Official configuration

1cd ~/mail/
2mkdir maddy/
3nano maddy.conf

The Maddy configuration (maddy.conf) is as follows:

  1# Three variables have been preset for ease of use later
  2$(hostname) = mail.example.com #The external world uses this domain name to locate your mail server
  3$(primary_domain) = example.com #the domain name that follows the '@' in your email address
  4$(local_domains) = $(primary_domain)
  5
  6# If you want to use Nginx as a reverse proxy, you can choose 'tls off' here. However, this will prevent the generation of DKIM key pairs
  7# During subsequent checks, the logs will show security warnings. Therefore, it is recommended to manage directly with Maddy
  8# tls off
  9#tls file /etc/letsencrypt/live/$(local_domains)/fullchain.pem /etc/letsencrypt/live/$(local_domains)/privkey.pem
 10tls file /data/local_certs/cert.pem /data/local_certs/key.pem 
 11
 12# Using SQLite3 for data storage is simpler and more lightweight
 13auth.pass_table local_authdb {
 14    table sql_table {
 15        driver sqlite3
 16        dsn credentials.db
 17        table_name passwords
 18    }
 19}
 20
 21storage.imapsql local_mailboxes {
 22    driver sqlite3
 23    dsn imapsql.db
 24}
 25
 26# ----------------------------------------------------------------------------
 27# SMTP endpoints + message routing
 28
 29hostname $(hostname)
 30
 31table.chain local_rewrites {
 32    optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3"
 33    optional_step static {
 34        entry postmaster postmaster@$(primary_domain)
 35    }
 36    optional_step file /data/aliases
 37}
 38
 39msgpipeline local_routing {
 40    destination postmaster $(local_domains) {
 41        modify {
 42            replace_rcpt &local_rewrites
 43        }
 44
 45        deliver_to &local_mailboxes
 46    }
 47
 48    default_destination {
 49        reject 550 5.1.1 "User doesn't exist"
 50    }
 51}
 52
 53# SMTP uses port 25 for sending emails
 54smtp tcp://[::]:25 {
 55    # tls self_signed
 56    limits {
 57        # Up to 20 msgs/sec across max. 10 SMTP connections.
 58        all rate 20 1s
 59        all concurrency 10
 60    }
 61
 62    dmarc yes
 63    check {
 64        require_mx_record
 65        dkim # If not available, skip the check
 66        spf
 67    }
 68
 69    source $(local_domains) {
 70        reject 501 5.1.8 "Use Submission for outgoing SMTP"
 71    }
 72    default_source {
 73        destination postmaster $(local_domains) {
 74            deliver_to &local_routing
 75        }
 76        default_destination {
 77            reject 550 5.1.1 "User doesn't exist"
 78        }
 79    }
 80}
 81
 82# If using Nginx as a reverse proxy, you only need to listen on the local port here tcp://127.0.0.1:587
 83# If not using this, email clients will access the address directly using SSL/TLS
 84submission tls://[::]:465 {
 85    limits {
 86        # Up to 50 msgs/sec across any amount of SMTP connections.
 87        all rate 50 1s
 88    }
 89
 90    auth &local_authdb
 91
 92    source $(local_domains) {
 93        check {
 94            authorize_sender {
 95                prepare_email &local_rewrites
 96                user_to_email identity
 97            }
 98        }
 99
100        destination postmaster $(local_domains) {
101            deliver_to &local_routing
102        }
103        default_destination {
104            modify {
105                dkim $(primary_domain) $(local_domains) default
106            }
107            deliver_to &remote_queue
108        }
109    }
110    default_source {
111        reject 501 5.1.8 "Non-local sender domain"
112    }
113}
114
115target.remote outbound_delivery {
116    limits {
117        # Up to 20 msgs/sec across max. 10 SMTP connections
118        # for each recipient domain.
119        destination rate 20 1s
120        destination concurrency 10
121    }
122    mx_auth {
123        dane
124        mtasts {
125            cache fs
126            fs_dir mtasts_cache/
127        }
128        local_policy {
129            min_tls_level encrypted
130            min_mx_level none
131        }
132    }
133}
134
135target.queue remote_queue {
136    target &outbound_delivery
137
138    autogenerated_msg_domain $(primary_domain)
139    bounce {
140        destination postmaster $(local_domains) {
141            deliver_to &local_routing
142        }
143        default_destination {
144            reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
145        }
146    }
147}
148
149# ----------------------------------------------------------------------------
150# IMAP endpoints
151# As above, if using Nginx as a reverse proxy, change it to listen on the local port tcp://127.0.0.1:143
152imap tls://[::]:993 {
153    auth &local_authdb
154    storage &local_mailboxes
155}

💻Start Maddy and Rainloop Webmail

1cd ~/mail
2docker compose upgrade -d

Create a user in Maddy

1docker exec -it maddy sh
2maddy creds create [email protected] #create email
3maddy imap-acct create [email protected]

Obtain the DKIM public key pairs

1cat data/dkim_keys/*.dns

Then this code will appear

"v=DKIM1; k=ed25519; p=nAcUUozPlhc4VPhp7hZl+owES7j7OlEv0laaDEDBAqg="

Copy and save it; it will be used for setting up DNS records

Set up DNS records (configure them with your domain registrar)

For example, using mail.example.com as the domain name for the mail server
# A records
mail.example.com   A     10.2.3.4
mail.example.com   AAAA  2001:beef::1

# MX records
example.com   MX    mail.example.com

# CNAME records
smtp.example.com   CNAME    mail.example.com
imap.example.com   CNAME    mail.example.com

# SPF
example.com     TXT   "v=spf1 mx ~all"

# _dmarc
_dmarc.example.com   TXT    "v=DMARC1; p=quarantine; ruf=mailto:[email protected]"

# DKIM DNS records
default._domainkey.example.com  TXT    "v=DKIM1; k=ed25519; p=nAcUUozPlhc4VPhp7hZl+owES7j7OlEv0laaDEDBAqg="

🔋Log in to Rainloop Webmail

Log in to the Rainloop Webmail admin panel using the email domain

mail.example.com?admin
account:admin
password:12345

Connect to the Maddy mail server through the Rainloop Webmail control panel

  1. Click on ‘Domains’
  2. Click ‘Add Domain’
  3. Enter the following details:
  • Name: example.com
  • IMAP/SMTP Server: mail.example.com
  • Encryption: SSL/TLS
  1. Click ‘Add’

Log in to Rainloop Webmai

mail.example.com

Test whether you can send and receive emails

⚠️Note

  • Select SSL/TLS encryption. Ensure that the firewall allows the following ports: 465/tcp and 993/tcp
  • Test the Spammyness of your Emails Make adjustments based on issues with low scores; otherwise, your emails are likely to end up in the spam folder
  • MxtoolBox You can check MX records, DKIM, and other settings to verify proper configuration

📦Reference

Built with Hugo
Theme Stack designed by Jimmy