How to create a mailserver selfhosted in 2022 . Tutorial Google GMAIL alternative

In this post we'll make our own IMAP mail server via selfhosting. Yeah I know it's boring and won't receive a prize to do that.

The drawing how the Stable Diffusion AI see the current email situation

WHY A SELF HOSTED MAIL SERVER ?

  • Is still usefull an email in 2022? Yes, maybe not for casual chatting but as identity to register in online services. The phone number shuld be avoided.
  • decentralization, mail is a protocol not a service. And it shouldn't be only in the hands of a few players. Anti SPAM rules, DKIM, DMARC, SPF, reverse DNS, TLS/SSL and blacklists can be an issue.
  • Chat Control, CSS (Client Side Scanning and Server Side Scanning), CSAM,.. In EU are trying to force the service provider to scan the user data to fight the children abuses, opening a huge backdoor for the user privacy. Like the GreenPass it won't solve the issue but it will limit the people lives.
  • Third because I can :) protonmail, tutanota and all the over "super secure privacy-oriented VPN easy TOR anonymous things" are just buzzwords. You can't really trust a service that manages your unencrypted data. End-to-End encryption can mitigate it but other data are still exposed

At the end you'll get a working IMAP mail server mail.greatreset.com and klaus@greatreset.com and of course greatreset.com it'just an example domain.

PREREQUISITES

  • Knowledge: linux terminal, docker, docker-compose, DNS
  • $ A domain like greatreset.com
  • $ Public IP (It means a VPS with 2Gb RAM (512Mb minimun)

Lets start: an easy mailserver to deploy docker-mailserver

There are tons of mailservers webmail 1-click solutions, but this one is the most complete and flexible to deploy. It plays nice with reverse proxies and it doesn't take the full control of your machine.

Installation

Please check the official docker-mailserver docs for the updated commands.

The following commands will provide the docker-compose.yml and a script helper setup.sh, very useful to avoid to change some configs manually.

DMS_GITHUB_URL='https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master'
wget "${DMS_GITHUB_URL}/docker-compose.yml"
wget "${DMS_GITHUB_URL}/mailserver.env"
wget "${DMS_GITHUB_URL}/setup.sh"
chmod a+x ./setup.sh

My current docker-compose.yml looks like this:

# docker-compose.yml

version: '3'
services:
  mailserver:
    image: docker.io/mailserver/docker-mailserver:latest
    container_name: mailserver
    # If the FQDN for your mail-server is only two labels (eg: example.com),
    # you can assign this entirely to `hostname` and remove `domainname`.
    hostname: mail
    domainname: greatreset.com
    env_file: mailserver.env
    # More information about the mail-server ports:
    # https://docker-mailserver.github.io/docker-mailserver/edge/config/security/understanding-the-ports/
    # To avoid conflicts with yaml base-60 float, DO NOT remove the quotation marks.
    ports:
      - "25:25"    # SMTP  (explicit TLS => STARTTLS)
      - "143:143"  # IMAP4 (explicit TLS => STARTTLS)
      - "465:465"  # ESMTP (implicit TLS)
      - "587:587"  # ESMTP (explicit TLS => STARTTLS)
      - "993:993"  # IMAP4 (implicit TLS)
    volumes:
      - ./docker-data/dms/mail-data/:/var/mail/
      - ./docker-data/dms/mail-state/:/var/mail-state/
      - ./docker-data/dms/mail-logs/:/var/log/mail/
      - ./docker-data/dms/config/:/tmp/docker-mailserver/
      - /etc/localtime:/etc/localtime:ro
      # !! traefix 1.7 no buono
      # - /apps/traefik/config/acme.json:/etc/letsencrypt/acme.json:ro
      # - ./docker-data/certbot/certs/:/etc/letsencrypt
      # Traefik acme
      - /apps/traefik/letsencrypt/acme.json:/etc/letsencrypt/acme.json:ro
    restart: always
    stop_grace_period: 1m
    environment:
      - SSL_TYPE=letsencrypt
      - SSL_DOMAIN=mail.greatreset.com
      - ENABLE_SPAMASSASSIN=1
      - SPAMASSASSIN_SPAM_TO_INBOX=1
      - ENABLE_CLAMAV=1
      - ENABLE_FAIL2BAN=1
      - ENABLE_POSTGREY=1
      - ENABLE_SASLAUTHD=0
      - ONE_DIR=1
    cap_add:
      - NET_ADMIN
    healthcheck:
      test: "ss --listening --tcp | grep -P 'LISTEN.+:smtp' || exit 1"
      timeout: 3s
      retries: 0
  
  mailacme:
    image: docker.io/traefik/whoami:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.mailacme.rule=Host(`mail.greatreset.com`)"
      - "traefik.http.routers.mailacme.entrypoints=websecure"
      - "traefik.http.routers.mailacme.tls.certresolver=myresolver"
    networks:
      - web

networks:
  web:
    external: true

You can specify other configurations in mailserver.env but I've left it as is.

docker-compose up -d

Will start the environment and with docker compose logs -f you can see what is going on. Some errors, for different reasons, aren't available there.

My first email address selfhosted

./setup.sh email add klaus@greatreset.com

# or if you prefer postmaster@greatreset.com

It will be asked to pick a password for the email account you are creating.

This is the most tricky part, hard to debug and you also need to wait the DNS propagation.

Some DNS configurations: DKIM SPF DMARC

These steps aren't mandatory but if you don't do them your messages will go 100% in the Spam folder! So don't skip this part

Check the official doc for more details.

Now you need to add some records to your DNS, usually it's done via the domain provider web interface.

DKIM

You can also specify multiple domains comma separated.

./setup.sh config dkim domain greatreset.com

Then you need to copy the DKIM key from your config and clean it by removing the " char.

sudo cat docker-data/dms/config/opendkim/keys/greatreset.com/mail.txt

Now you are ready to fill the form, just choose the right type like this:

Wait some minutes and check if the values are propagated via DNS

$ dig mail._domainkey.greatreset.com MX | grep MX
; <<>> DiG 9.18.7 <<>> mail._domainkey.greatreset.com MX
;mail._domainkey.greatreset.com.	IN	MX
..
$ dig mail._domainkey.greatreset.com TXT
...
mail._domainkey.greatreset.com. 1787	IN	TXT	"v=DKIM1; k=rsa; p=..."
...
$ dig _dmarc.greatreset.com TXT
..
_dmarc.greatreset.com.	1699	IN	TXT	"v=DMARC1; p=none"
..

HTTPS SSL TLS

Email communication, like HTTP, is unencrypted by default, so you need some valid certificates to obtain the HTTPS but for IMAP.

This can be complicated if your ports 80 443 are busy with a reverse proxy like nginx, caddy or traefik.

My advice is to use the traefik v2 way (here) if you can otherwise the letsencrypt way (here) is standalone but you need to stop the reverse proxy, change the docker-compose.yml during the verification to avoid the ports conflict.

rDNS Reverse DNS

This is optional, but it helps to get the maximun score. It means that your VPS public IP will be resolved to your mail.greatreset.com server

Congrats, now your mailserver is ready! But it's not over!

Configure IMAP parameters in the Mail Client

user: klaus@greatreset.com
password: xxx

IMAP
mail.greatreset.com SSL/TLS port 993

SMTP
mail.greatreset.com SSL/TLS port 465

You can pick a software like Thunderbird (desktop Linux, Mac, Windows) or K-9 Mail (Android) or a Webmail like Mail app in Nextcloud.

You can a more GMail -like experience enabling the "conversation extension" in Thunderbird.

Test your Configuration

If you did everything correctly you should have at least 10/10 score in www.mail-tester.com, you can send a email message to it to see your score and debug if you missed some parts, you can open each section to see the details. Another tool is the MX and DKIM checker online (here)

Congratulation redpilled, now you are outsite the Matrix and you need to mantain it by yourself.

Be ready to do:

  • updates (the official project will send a message every time a new version is released.
  • backups
  • and security brute force mitigations

This IP from Vietnam 103.133.107.152 is already trying a brute force attack to find an OpenRelay. Good luck with the firewall!

Oct 13 11:52:13 mail postfix/submission/smtpd[420557]: connect from unknown[103.133.107.152]
Oct 13 11:52:13 mail postfix/submission/smtpd[420557]: SSL_accept error from unknown[103.133.107.152]: lost connection
Oct 13 11:52:13 mail postfix/submission/smtpd[420557]: lost connection after STARTTLS from unknown[103.133.107.152]
Oct 13 11:52:13 mail postfix/submission/smtpd[420557]: disconnect from unknown[103.133.107.152] ehlo=1 starttls=0/1 commands=1/2
Oct 13 11:52:14 mail postfix/postscreen[420688]: CONNECT from [103.133.107.152]:30261 to [172.31.0.2]:25
Oct 13 11:52:14 mail postfix/postscreen[420688]: HANGUP after 0 from [103.133.107.152]:30261 in tests before SMTP handshake
Oct 13 11:52:14 mail postfix/postscreen[420688]: DISCONNECT [103.133.107.152]:30261
Oct 13 11:52:14 mail postfix/postscreen[420688]: CONNECT from [103.133.107.152]:30284 to [172.31.0.2]:25
Oct 13 11:52:20 mail postfix/postscreen[420688]: PASS NEW [103.133.107.152]:30284
Oct 13 11:52:20 mail postfix/smtpd[420709]: connect from unknown[103.133.107.152]
Oct 13 11:52:20 mail postfix/smtpd[420709]: NOQUEUE: reject: RCPT from unknown[103.133.107.152]: 554 5.7.1 <irdi33@yahoo.com>: Relay access denied; from=<irdi33@yahoo.com> to=<irdi33@yahoo.com> proto=SMTP helo=<win-oy0k2ahb8s6.domain>

But the fail2ban provided already is doing a great job

root@mail:/# zgrep 'Ban' /var/log/fail2ban.log*        
2022-10-12 18:58:10,978 fail2ban.actions        [729]: NOTICE  [dovecot] Ban 2.56.58.89
2022-10-12 18:58:12,580 fail2ban.actions        [729]: NOTICE  [postfix-sasl] Ban 2.56.58.89
2022-10-12 23:34:20,228 fail2ban.actions        [729]: NOTICE  [dovecot] Ban 58.246.96.36
2022-10-12 23:34:21,391 fail2ban.actions        [729]: NOTICE  [postfix-sasl] Ban 58.246.96.36
2022-10-13 11:52:41,469 fail2ban.actions        [729]: NOTICE  [dovecot] Ban 103.133.107.152
2022-10-13 11:52:42,876 fail2ban.actions        [729]: NOTICE  [postfix-sasl] Ban 103.133.107.152

Se sei interessato al tema del selfhosting, di come farti il cloud in casa, su Techonsapevole Academy altri video di approfondimento.