Your own mail server with Postfix leveraging Gmail as storage

Your own mail server with Postfix leveraging Gmail as storage

Table of contents

  1. What do we need
    1. Preparing the database
    2. Configure firewall
    3. SPF TXT record
  2. Postfix config
  3. Dovecot config
  4. Configure SSL
  5. Add new email to Gmail
  6. Conclusion

Usually when you open your first domain and want to play around with your own setup you quickly figure out that you need/want to send emails and create email accounts using the very same domain. There are lots of providers that will handle this task for a small monthly fee or maybe your hosting provider offers that in the price you are already paying, but... if you want to learn how to do it by yourself or want to save few bucks with a little bit of extra effort this little guide might save you some time and show how to create secure and reliable SMTP mail server with low maintenance effort.

The setup I am about to show, step by step, will help you to setup your own Postfix mail server that will handle all incoming and outgoing emails for your domain and will keep those emails stored on your Gmail account (no don't need to worry about email storage). At the end you will use Gmail to send and receive emails for your domain email e.g. info@example.com.

So let's see what we want to achieve:

  1. Emails that we receive for our new email address(s) will be forwarded to our Gmail account so that they are stored there.
  2. From our Gmail account be able to reply using our new mail address (e.g. nik@example.com).

As described, the idea is to use Gmail as our mail client to operate our new domain emails.

My assumption before we begin is that you already registered your domain, know how to manage DNS records (MX, A and TXT) and have basic Linux skills.

For this setup I am going to do it using AWS EC2, so some additional steps will be required.

What do we need

  • Ubuntu 20.04 LTS (no worries setup is almost the same on any Linux)
  • Postfix
  • Dovecot
  • MySQL
  • Certbot

Assuming you have your instance up and running let's install our packages

sudo apt-get update
sudo apt-get install mysql-server
sudo apt-get install postfix postfix-mysql
sudo apt-get install dovecot-mysql dovecot-common

To install Certbot follow the instructions here https://certbot.eff.org/lets-encrypt/ubuntufocal-other.

Preparing the database

Let's create our database and our user for the database. First login to your database as root:

sudo mysql

Then let's create our mail database and a user, so we can connect to that database.

CREATE DATABASE mail_server;
CREATE USER 'mail_user'@'127.0.0.1' IDENTIFIED BY 'ChangeMe';
GRANT ALL PRIVILEGES on mail_server.* TO 'mail_user'@'127.0.0.1';

USE mail_server; 

Next we need our basic tables so we can define domain(s) that this email server will manage, tables for our users (who will be allowed to send emails) and forwarding rules.

CREATE TABLE IF NOT EXISTS virtual_domains (
	vd_id int(11) NOT NULL auto_increment,
	vd_name varchar(50) NOT NULL,
	UNIQUE KEY uq_virtual_domains_name (vd_name),
	PRIMARY KEY (vd_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
In this table we will store domains that you will use as virtual_mailbox_domains in Postfix
CREATE TABLE IF NOT EXISTS virtual_users (
	vu_id int(11) NOT NULL auto_increment,
	vu_vd_id int(11) NOT NULL,
	vu_email varchar(100) NOT NULL,
	vu_password varchar(100) NOT NULL,
	PRIMARY KEY (vu_id),
	UNIQUE KEY uq_virtual_users_email (vu_email),
	FOREIGN KEY fk_virtual_users_vd_id (vu_vd_id) REFERENCES virtual_domains(vd_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Table contains information about your users.
CREATE TABLE IF NOT EXISTS virtual_aliases (
	va_id int(11) NOT NULL auto_increment,
	va_vd_id int(11) NOT NULL,
	va_source varchar(100) NOT NULL,
	va_destination varchar(1000) NOT NULL,
	PRIMARY KEY (va_id),
	FOREIGN KEY fk_virtual_aliases_vd_id (va_vd_id) REFERENCES virtual_domains(vd_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Table contains forwarding from one email address to other email addresses.

And now let's insert our first configuration. For this article I will assume that our domain is example.com and our user who can send emails is nik@example.com. Also we will configure one extra email address info@example.com that will be forwarded to our gmail address.

INSERT INTO virtual_domains(vd_name) VALUES('example.com');

INSERT INTO virtual_users(vu_vd_id, vu_email, vu_password) VALUES ((SELECT vd_id from virtual_domains where vd_name = 'example.com'), 'nik@example.com', '{SHA256-CRYPT}$5$e/QfbHmnc55Xr4N1$rTRS7DCQIuNlaDTz/e31SVuHiAz7fnHPyiOgV9eXLm/');

INSERT INTO virtual_aliases(va_vd_id, va_source, va_destination) VALUES ((SELECT vd_id from virtual_domains where vd_name = 'example.com'), 'info@example.com', 'your_gmail_address@gmail.com');

INSERT INTO virtual_aliases(va_vd_id, va_source, va_destination) VALUES ((SELECT vd_id from virtual_domains where vd_name = 'example.com'), 'nik@example.com', 'your_gmail_address@gmail.com');
Table contains forwarding from one email address to other email addresses.

For the password we are using SHA256-CRYPT and this is how you can generated the hash for password:

sudo dovecot pw -s SHA256-CRYPT

In the INSERT statement above we used password youMustChangeMe.

Configure firewall

This is the last step of preparation before we begin configuration for Postfix and Dovecot. Since we are using EC2 instance we need to allow incoming traffic on these ports:

  • Port 25 - Simple Mail Transfer (SMTP-MTA). Used by MTA to MTA communication (mail server to mail server) also known as SMTP relaying. Described in RFC 5321.
  • Port 465 - Deprecated but... Historically, port 465 was initially planned for the SMTPS encryption and authentication “wrapper” over SMTP, but it was quickly deprecated in favour of STARTTLS over SMTP. Because of old systems that are still using it I would recommend still keeping it.
  • Port 587 - Message submission (SMTP-MSA), a service that accepts submission of email from email clients (MUAs). When a mail client or server is submitting an email to be routed by a proper mail server, it should always use this port. Described in RFC 6409.

Since we are using AWS EC2 there is one more step we need to do (actually two). By default AWS block all outgoing traffic on port 25 and you cannot enable this on your own. Basically you need to request a support ticket so they enable it for your account. More details and link to support ticket can be found here https://aws.amazon.com/premiumsupport/knowledge-center/ec2-port-25-throttle/. I would recommend (as AWS does in the upper link) when you create a ticket to also tell them to configure reverse DNS (rDNS) records back to your IP addresses. When you send emails, it's a best practice to set up an rDNS record to help prevent outbound emails from being flagged as spam. So basically in our example rDNS for our elastic IP should be mail.example.com (AWS needs to configure this) and our A record should point to the same elastic IP address.

Keep in mind that you could wait several days until AWS allows outbound Port 25 and up to a week until rDNS changes are propagated.

SPF TXT record

The Sender Policy Framework (SPF) is an email-authentication technique which is used to prevent spammers from sending messages on behalf of your domain.

And why do we need this? Well, if you don't set it up most probably your mails will be marked as spam by most other mail servers. It is very easy to set it up since it is just a TXT record in your DNS zone config. An SPF record lists all authorized hostnames / IP addresses that are permitted to send email on behalf of your domain.

So go ahead and create TXT record for your mail domain with the following content.

v=spf1 a mx ~all

This will tell that our A and MX records are allowed to send email for this domain. If you want to know more or have a bit more complex setup I recommend reading here https://www.dmarcanalyzer.com/spf/how-to-create-an-spf-txt-record/.

After your configuration is done you can validate it using a lot of online tools, e.g. https://www.dmarcanalyzer.com/spf/checker/.

Postfix

Awesome, we are done with all pre-setup and we are ready to jump to configuring our mail server. So what is Postfix? Postfix is a free and open-source mail transfer agent that routes and delivers electronic mail. Check more details here.

Let's start by telling Postfix how to fetch information from our database.

virtual_mailbox_domains

Let's create a file called /etc/postfix/mysql-virtual-mailbox-domains.cf for our virtual_mailbox_domains.  

user = mail_user
password = ChangeMe
hosts = 127.0.0.1
dbname = mail_server
query = SELECT 1 FROM virtual_domains WHERE vd_name='%s'

When Postfix receives an email it will first check if does this virtual mailbox domain belong to this server by running the upper query. It is not important what is the result of the query as long as it return something.

Now we need to tell Postfix about our new config. For that we can use this command:

sudo postconf virtual_mailbox_domains=mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf

or you can go ahead and directly edit /etc/postfix/main.cf. If you use the command you don't need to reload Postfix. Now we can test our new config:

sudo postmap -q example.com mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf

If everything is ok, you will get '1' printed in your terminal.

virtual_mailbox_maps

We need to tell Postfix about our email addresses that can (are allowed to) receive emails. So let's create /etc/postfix/mysql-virtual-mailbox-maps.cf.

user = mail_user
password = ChangeMe
hosts = 127.0.0.1
dbname = mail_server
query = SELECT 1 FROM virtual_users WHERE vu_email='%s'
/etc/postfix/mysql-virtual-mailbox-maps.cf

Make Postfix aware about our config:

sudo postconf virtual_mailbox_maps=mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf

And we can test the new config:

sudo postmap -q nik@example.com mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf

We expect the above command to print '1' if everything is working fine. If you are wondering why we don't use the password here it is because Postfix is just checking if the email address exists and should it receive emails. The password will be needed to send emails from that account and Dovecot will be dealing with this part later on.

virtual_alias_maps

Next up is configuring virtual_alias_maps that is used to for forwarding emails from one email address to others. In our case we will forward emails to our gmail address. You can also forward to multiple email addresses in two ways, comma separated email addresses or a new row in the database. If you use multiple email addresses you can actually create a mailing list.

We need to create another config file /etc/postfix/mysql-virtual-alias-maps.cf.

user = mail_user
password = ChangeMe
hosts = 127.0.0.1
dbname = mail_server
query = SELECT va_destination FROM virtual_aliases WHERE va_source='%s'

Tell Postfix about it:

sudo postconf virtual_alias_maps=mysql:/etc/postfix/mysql-virtual-alias-maps.cf

And we can test our new config:

sudo postmap -q info@example.com mysql:/etc/postfix/mysql-virtual-alias-maps.cf

The result should be your gmail address where you want to forward the emails received on info@example.com printed in the terminal.

We should also protect our database password and allow only postfix to read the file by executing the following:

sudo chgrp postfix /etc/postfix/mysql-*.cf
sudo chmod u=rw,g=r,o= /etc/postfix/mysql-*.cf

Wow, actually that is now enough for you to receive emails directed to info@example.com and get them delivered to your gmail address. You can now go and test it. I would recommend not to send emails from gmail account that is about to receive the email but rather use some other email address.

Enable SMTP Submission port (587) and SMTP SSL port (465)

By default Postfix will only use SMTP Port 25 but we also want to enable SMTP Submission & SSL ports. To do this edit /etc/postfix/master.cf:

# Uncomment/add the following
submission inet n       -       y       -       -       smtpd
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_sasl_type=dovecot
  -o smtpd_sasl_path=private/auth
  
 smtps     inet  n       -       y       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
/etc/postfix/master.cf

And we need to restart Postfix after above changes.

sudo service postfix restart

But what about sending emails? Let's go ahead and add configuration for that as well...

Dovecot

Normally you use Dovecot to stores emails on your hard disk, applies filters and lets your users fetch their emails using the POP3 and IMAP protocols but in this tutorial we are only using it to provide SASL authentication. Why do we need that?

Well, Postfix SMTP servers need to decide whether an SMTP client is authorized to send mail to remote destinations, or only to destinations that the server itself is responsible for. Usually, Postfix accept mail to remote destinations when the client's IP address is in the "same network" as the server's IP address.

SMTP clients outside the SMTP server's network need a different way to get "same network" privileges. To address this need, Postfix supports SASL authentication. Postfix does not implement SASL itself, but instead uses existing implementations as building blocks. You can use two implementations, Cyrus SASL library or Dovecot. In this article I will show you how to do it using Dovecot.

Let's enable authentication service so that Postfix can use Devecot. We need to edit the file /etc/dovecot/conf.d/10-master.conf, look for section "service auth" and edit to match:

# Postfix smtp-auth
unix_listener /var/spool/postfix/private/auth {
  mode = 0660
  user = postfix
  group = postfix
}
/etc/dovecot/conf.d/10-master.conf

What did we do here? Postfix runs in a chroot environment located at /var/spool/postfix so it can’t access anything outside of that directory. This actually creates socket file communication between Dovecot and Postfix.

Now we need to change the following file /etc/dovecot/conf.d/10-auth.conf. This will enable sql lookup.

# Edit and add login
auth_mechanisms = plain login

# Change the following
#!include auth-system.conf.ext
!include auth-sql.conf.ext
#!include auth-ldap.conf.ext
#!include auth-passwdfile.conf.ext
#!include auth-checkpassword.conf.ext
#!include auth-vpopmail.conf.ext
#!include auth-static.conf.ext
/etc/dovecot/conf.d/10-auth.conf

Next we need to tell Dovecot how to read our user and password information from the db. For this we will edit /etc/dovecot/dovecot-sql.conf.ext and add the following at the end:

driver = mysql
connect = host=127.0.0.1 dbname=mail_server user=mail_user password=ChangeMe  # use your database access password instead
default_pass_scheme = SHA256-CRYPT
password_query = SELECT vu_email as user, vu_password as password FROM virtual_users WHERE vu_email='%u';
/etc/dovecot/dovecot-sql.conf.ext

In order to protect the file so you don't leak your password we can change the file access to root:

sudo chown root:root /etc/dovecot/dovecot-sql.conf.ext 
sudo chmod go= /etc/dovecot/dovecot-sql.conf.ext
Make root the owner and the only one that has access to file (rw)

All that is left now is to restart Dovecot.

sudo service dovecot restart

Finally we can also quickly test this using

sudo doveadm auth login nik@example.com

After that if everything works you should see something like "passdb: nik@example.com auth succeeded". Don't mind the errors "userdb lookup" since we are not using local storage so it is not important in this setup.

Configure SSL

First we need to generate SSL certificates. Use the following command. Before you do it you need to make sure that the A record for your mail domain is pointing to the server you are executing this command from.

sudo certbot certonly --standalone -d mail.example.com

Now let's tell Postfix about it and set our mail hostname.

sudo postconf -e 'smtpd_tls_cert_file = /etc/letsencrypt/live/mail.example.com/fullchain.pem'
sudo postconf -e 'smtpd_tls_key_file = /etc/letsencrypt/live/mail.example.com/privkey.pem'
sudo postconf -e 'myhostname = mail.example.com'

Test your setup

There are a various online tools to test your setup to make sure your new mail server is secure. Here is some sites that can help you:

Setup another address in your Gmail account

Ok, so we are receiving our emails, but let's configure the sending part as well. For this last step we will jump to your Gmail account and open "Settings" -> "Accounts and Import" and then click "Add another email address". Follow the steps and input your mail server details for your virtual user.

Add another email address in Gmail

And after this screen you will get a verification email from Gmail and that is it! You can now send email, in our case, as nik@example.com When you compose new email in Gmail you will now have a dropdown in "From" field where you can select your new email.

Conclusion

Hope you had fun along the way and learned that is is actually quite easy to setup your own email server for you domain. Postfix is a very powerful, secure and easy to operate SMTP server. Dovecot is an amazing POP3/IMAP server, but in our use case we used it only for authentication so I encourage you to explore it more.

So... at the end we managed to using Gmail as our email client to send and receive emails from our new domain email address :)


Follow-ups:

  • How to host multiple domains on one mail server (multiple SSL configs)
  • Limit the users to certain email addresses
  • Turn on DKIM signing for your messages
  • Publish a DMARC record for your domain
  • Create Docker image or EC2 template for the whole setup
  • Store emails on your own server (instead of gmail forward) and configure POP3/IMAP so users can get retrieve their emails
Cover photo by Miguel Á. Padriñán from Pexels

Comments