A Mail Server for Small Organisations

This explanation assumes that you've already read and understood A web server for small organisations.

Once again, whenever you see "example.com" below, replace it with the name of your domain.

A mail server needs quite a lot of memory, even if it doesn't have to handle very much traffic. This is because, when an email arrives, the server needs to respond quickly. To do that it has programs like virus scanners running constantly, all using memory.

So you need to run your mail server software on a dedicated machine with at least 2GB of memory. If you're using Digital Ocean, create a droplet using these options:

If you choose the optional backup service, your machine will be backed up once per week, so in the event of a disaster, you lose up to a week of emails. Better than losing everything! You can arrange more frequent backups via one of the many backup services, but a weekly backup gives extra insurance against disaster.

The Domain Name

Back at your domain name service's control web page, create a subdomain called mail (eg mail.example.com) and set the version 4 and version 6 IP addresses to be those of your new droplet. You need to create:

As soon as you create the MX record, any mail sent to your domain will be sent to this server but at present there's no software to set up to deal with it.

Connecting

Start a new command window. Don't log into the droplet yet, just type:

ping mail.example.com

Hit the Enter key to run the command.

The command runs on your local machine. If all is well, the first line of output will contain the version 4 IP address of your droplet, and that will be followed by a series of lines saying "64 bytes from mail.example.com". (You may have to hold down the control key and type a "c" to stop them.) If you get an error, or it hangs, there's something wrong with the A or AAAA record that you set up.

If all is well, you can log into your droplet and start work. Log in from your command window like so:

	ssh root@mail.example.com
or, if necessary, tell it which key to use:
	ssh -i ~/.ssh/id_rsa_root root@mail.example.com

The first time you do that, you will get this scary message:

The authenticity of host 'mail.example.com (178.128.167.235)' can't be established. ECDSA key fingerprint is SHA256:/FljgxBjsN2XYXWfF6ab6BnjrLY64+yXoiyIJZiQJac. Are you sure you want to continue connecting (yes/no/[fingerprint])?

Answer yes.

Now create an ordinary user for doing day to day work (as explained in the web server page.) I called my user mailhandler.

Creating a Digital Certificate

Now that the droplet responds to the right name, you can create a digital certificate for it. There's no web server running on this machine, so the sequence is a little different. As the root user:

	apt install certbot python3-certbot-apache

	certbot -d mail.example.com --manual --preferred-challenges dns certonly
Cerbot asks you to create a TX DNS record called _acme-challenge (underscore and then hyphen) containing a long piece of magic text. This is to prove that you control the domain. At your DNS provider, do that. If you're using Ionos, navigate to your mail subdomain and create a TX record named "_acme-challenge.mail.@" and fill in the bit of text that certbot is looking for. The "@" is replaced by your domain name, so that's a TX record called "_acme-challenge.mail.example.com". (To copy the challenge text from the command window, highlight the text, right click and choose Copy from the dropdown menu that appears.)

Once your TX record is ready, hit enter in the command window. If you got everything right, it will create a certificate for this server, represented by lots of files in the directory /etc/letsencrypt.

Your mail server will be run by the mailhandler user and it needs to be able to read the information in the certificate files. Currently they are owned by root and some are only readable by the owner. So, we change the owner:

    chown -R mailhandler:mailhandler /etc/letsencrypt
			

Ensure that all the ports used by the mail server are open in the firewall:

    iptables -A INPUT -p tcp --dport 25 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
    iptables -A OUTPUT -p tcp --sport 25 -m conntrack --ctstate ESTABLISHED -j ACCEPT

    iptables -A INPUT -p tcp --dport 143 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
    iptables -A OUTPUT -p tcp --sport 143 -m conntrack --ctstate ESTABLISHED -j ACCEPT

    iptables -A INPUT -p tcp --dport 465 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
    iptables -A OUTPUT -p tcp --sport 465 -m conntrack --ctstate ESTABLISHED -j ACCEPT

    iptables -A INPUT -p tcp --dport 587 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
    iptables -A OUTPUT -p tcp --sport 587 -m conntrack --ctstate ESTABLISHED -j ACCEPT

    iptables -A INPUT -p tcp --dport 993 -m conntrack --ctstate NEW,ESTABLISHED -j ACCEPT
    iptables -A OUTPUT -p tcp --sport 993 -m conntrack --ctstate ESTABLISHED -j ACCEPT
		

Install the latest version of docker-compose. The advice here says to install it something like this:

    curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" \
		-o /usr/local/bin/docker-compose
(The version - 1.29.2 will change as time goes on. See the web page.)

Setting up the Mail Server Software

Start another Git Bash window on your local machine and log in to your droplet as your other user. I called mine mailhandler, so that's:

    ssh mailhandler@mail.example.com
The instructions for installing and running the mail server are here. I suggest that you create a directory mailserver and install the software in there:
    mkdir mailserver
    cd mailserver
and then do as the instructions say:
    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
    ./setup.sh help

The beginning of the section "Starting for the first time" is a bit confusing. I suggest you create your first user (<user@domain> with password <password> like so:

    mkdir -p ${HOME}/mailserver/docker-data/dms/config

    docker run --rm -v "${HOME}/mailserver/docker-data/dms/config/:/tmp/docker-mailserver/" \
        docker.io/mailserver/docker-mailserver setup email add <user@domain> <password>
That creates a directory docker-data/dms/config to hold the configuration, brings up the docker container and tells it to use the config directory. It then shuts down, so you have to start it up agan when you've finished the setup (see below).

You need to create a special email address postmster@{yourdomain}. It can be an alias of some other email address:

    ./setup.sh alias add postmaster@example.com <anotheremailaddress>
			
The postmaster will receive emails about problems with the mail server.

Set up a DKIM record:

    ./setup.sh config dkim
			

The instructions mention that you might set up a DNS TXT record for DKIM. It turns out to be very important. The spam detector systems around the world are always very suspicious of new mail servers and if you don't set up a DKIM record, your users will very quickly get blacklisted by gmail and other popular mail systems. You just created a file containing the necessary text. This command displays the contents:

    sudo cat docker-data/dms/config/opendkim/keys/example.com/mail.txt
		
The file contains four lines and begins "mail._domainkey". Back at your DNS provider's control web page, create a TXT record with that name containing the rest of the text, as described here.

You probably need to read the section entitled Configuration Using a Web Interface. When it says "you need to concatenate the values in the TXT record", that simply means join them together into one long line.

There's a brief explanation of DKIM here.

After that, the instruction for the mail server are clearer. To create another email address, make sure you are in the mailserver directory and run this command:

    ./setup.sh email add anotheruser@domain password

To configure the email client software on their own computer, each user needs to know their account name (which is their whole email address) and their password.

Now you can start your mail server up and leave it running. To do that you need to create a docker-compose.yml file. This one should work. (Replace example.com with your domain name!)

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: example.com	# REPLACE THIS WITH YOUR DOMAIN NAME!!!!!
    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
      - /etc/letsencrypt/:/etc/letsencrypt/	# Allow the docker image to see the LetsEncrypt certificates
    environment:
      - ENABLE_SPAMASSASSIN=1
      - SPAMASSASSIN_SPAM_TO_INBOX=1
      - ENABLE_CLAMAV=1
      - ENABLE_FAIL2BAN=0			# Disable fail2ban initially.
      - ENABLE_POSTGREY=1
      - ENABLE_SASLAUTHD=0
      - ONE_DIR=1
      - DMS_DEBUG=0
      - SSL_TYPE=letsencrypt			# Use LetsEncrypt certificates.
    restart: always
    stop_grace_period: 1m
    cap_add:
      - NET_ADMIN
      - SYS_PTRACE
		
The spaces at the start of each line matter.

Start the server running like so:

    docker-compose up -d mailserver
		
You can check that it's running using docker ps:
    docker ps
    CONTAINER ID   IMAGE                                 COMMAND                  CREATED          STATUS          PORTS                                                                                                                                                                                                                       NAMES
    88cd818619aa   mailserver/docker-mailserver:latest   "/usr/bin/dumb-init …"   49 minutes ago   Up 49 minutes   0.0.0.0:25->25/tcp, :::25->25/tcp, 0.0.0.0:143->143/tcp, :::143->143/tcp, 0.0.0.0:465->465/tcp, :::465->465/tcp, 0.0.0.0:587->587/tcp, :::587->587/tcp, 110/tcp, 995/tcp, 0.0.0.0:993->993/tcp, :::993->993/tcp, 4190/tcp   mailserver
		
If you need to bring it down again later, do this (while in the mailserver directory):
    docker-compose down
		

Once your mail server is set up and running, go back at your DNS provider's control web page and set up a Mail eXchange (MX) record pointing to it - for the domain example.com, the record would contain "mail.example.com". Mail will now start flowing in, including all those vitally important notes from Nigerian potentates anxious to give you money.

With that configuration the fail2ban service is turned off. It guards against hackers trying to guess user names and passwords. Unfortunately, while your real users are trying to figure out how to configure their mail clients to connect to the server, they are likely to make mistakes, then fail2ban will get suspicious and ban them. Leave it switched off at first, get your users configured, then turn it on. To do that, edit docker-compose.yml and set the ENABLEFAIL2BAN line to:

      - ENABLE_FAIL2BAN=1
		
"Bounce" the mail server software - bring it down then start it up again as described above. On the way up it reads the new version of the configuration file and runs the fail2ban service.