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
- ✅ A VPS that supports port 25 and rDNS (I am using Crunchbits’ $1.89/month [WA] 4.5GB Yearly SSD VPS [PROMO])
- ✅ A domain name that supports managing A/AAAA records, MX records, TXT records, and more
- ✅ Certificate and key for the email domain (can be obtained using Certbot)
🔧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
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
- Click on ‘Domains’
- Click ‘Add Domain’
- Enter the following details:
- Name: example.com
- IMAP/SMTP Server: mail.example.com
- Encryption: SSL/TLS
- 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