This is the pretty boring but at the same time very important part of the tutorial. Do not skip it. Most problems that readers have with their mail servers are caused by a misunderstanding of the different types of email domains. There is nothing you need to do on your server right now. Just lean back and relax and make sure that you read and fully understand this page. It will spare you frustration.
As explained on the previous page Postfix is the software component that speaks SMTP. It sends and receives emails from and to the internet. Of course you will only want to receive emails for domains that you are actually responsible for.
Postfix will accept emails for any of these classes of domains:
- Canonical domains (also called local domains)
- Hosted domains
- Virtual mailbox domains
- Virtual alias domains
- Relay domains
Let us look at them in detail.
Canonical domains
Once upon a time users logged into servers using Telnet or SSH to read their email in uncomfortable text-based mail clients. Their email addresses were often just a combination of their login name and the server name. Something like mpauls@server17.biology.example.net.
At least that made things pretty simple for the server administrator. He just accepted emails for any valid login user on that server. The mail server knew what to do. It checked if “mpauls” is found in /etc/passwd and then stored emails in “/var/mail/mpauls”. That means…
- You would need to create system accounts for all users who want to receive email. Every system user is a potential security risk because they can login to the server using a shell. If the user chooses a weak password you are essentially inviting attackers to harm your server.
- If you want to host ten thousand email accounts this becomes impractical.
- Postfix cannot distinguish the local domains. If you have three local domains “example.org”, “example.com” and “example.net” then all these email addresses would lead to the same mailbox: john@example.org, john@example.com and john@example.net. So you cannot use different domains for different purposes.
To tell Postfix which domain you consider local you list them in the “mydestination” configuration setting in your main.cf configuration file. Example:
mydestination = example.org, example.com, example.net
It is common however to use the server’s hostname as a local domain. If your server is called “scully.example.com” you could set “mydestination = scully.example.com”. Or you just set it to “mydestination = localhost” if you rather want to use “scully.example.com” as a virtual domain. Some parts of your system may send emails to root@localhost so this is a sane setting.
Hosted domains (aka virtual domains)
These are the classes of domains that will be our workhorses. Postfix allows us to add an unlimited number of domains that can receive emails. That’s what an ISP does and so will we.
Where do we put the list of domains? Postfix is pretty flexible. We could put it in a huge text file. We might use LDAP if we have some kind of user directory in our organization. But our preferred solution is to use an SQL database in the background.
Postfix handles hosted email addresses by checking two mappings:
- Virtual Aliases (“redirect this email address to another address”)
- Virtual Mailboxes (“receive email for this email address”)
So much terminology. Time for an example, isn’t it? Let’s tell Postfix we want to have three email addresses:
- john@example.org
- jack@example.org
- jack@example.com (note that this is another domain than example.org)
Postfix only understands mappings. These are basically tables that have a left and a right column. Postfix always looks for stuff in the left column (key) to find more information in the right column (value). Like this:
Virtual mailbox | Virtual mailbox location on disk |
---|---|
john@example.org | /var/vmail/example.org/john/Maildir |
jack@example.org | /var/vmail/example.org/jack/Maildir |
jack@example.com | /var/vmail/example.com/jack/Maildir |
So the left column lists the valid email addresses. And the right column is apparently some path on disk. Right, that’s all the magic. If an email for jack@example.org is received then Postfix will find the entry in the left column and can figure out where Jack’s mails are stored on disk.
Before checking if a specific email address is valid Postfix first checks if it is responsible for the domain at all. That’s done by this mapping:
Virtual domain | Whatever |
---|---|
example.org | Kittens |
example.com | Caipirinha |
So there are two domains in the left column. But what is that in the right column? Kittens? Really? Well, the truth is: it doesn’t matter. It can be anything. So why is that column there at all? The reason is that mappings always have two columns. Postfix wants a list a domains but it has no concepts for lists. So it uses this format and ignores the right column.
Essentially these two mappings are all we need. Add a little configuration and you can already receive emails. However Postfix provides another feature we can use: aliases. An alias is a redirection (or forwarding) of one email address to one or more other addresses. Possible uses:
- forward postmaster@… for all your domains to one mailbox
- create an email distribution list for a team or department
- forward copies of all your emails to another address
- redirect emails for a coworker who left the organisation
As usual Postfix expects a mapping for aliases. Time for another example:
Virtual email address | Redirect to | |
---|---|---|
1 | postmaster@example.org | jack@example.com |
2 | abuse@example.org | jack@example.com |
3 | jack@example.com | jack@example.com,jacky@workaround.org |
4 | sophie@example.net | hr@example.net |
5 | hr@example.net | rick@example.net |
hr@example.net | tina@example.net | |
6 | @example.org | jack@example.com (DANGEROUS!) |
Quite a lot happens here. So what will Postfix do?
- Redirect emails for postmaster@example.org to jack@example.org
- Redirect emails for abuse@example.org also to jack@example.org
- Keep a copy of email of jack@example.com in his mailbox and send another copy to jacky@workaround.org. Yes, you can use multiple email addresses seperated by commas.
- Sophie has left the organisation so her address is forwarded to HR.
- The HR team consists of two members: rick@example.net and tina@example.net. Every email to hr@example.net will be forwarded to each of them. This works similar to (3) but this time the target addresses are not seperated by commas but mentioned in different rows.
- Forward any email address of the example.org domain to jack@example.com. This is dangerous for two reasons. First it will send the poor Jack anything that is sent to the example.org domain if there is no user account for that address. (Yes, spammers guess addresses.) And second it disables a security check in Postfix that the destination address jack@example.com actually exists. Postfix may first receive the email and then be confused if the recipient is not reachable. That leads to backscatter and harms your reputation.
So basically this is the way that Postfix handles aliases:
- Is the domain of the email address defined as a virtual domain?
- Get all rows of the table where the email address is found in the left column. Send a copy of the email to everyone mentioned in the right column. If the right column contains multiple email addresses separated by commas then split them first.
- If there was no such row then check again if there is a @domain row.
- Still nothing found? Then reject the email.
Database
Did you get the idea of mappings? Two columns? Keys and values? Good. Now how do those mappings work when we want to put the information into an SQL database?
Basically a relational database works in rows and columns, too. So we can take the format of the tables shown above and put them into database tables. Let’s call these tables…
- virtual_domains (for the list of domains)
- virtual_aliases (for the aliases mapping)
- virtual_users (for the email users and their mailboxes respectively)
I won’t bore you with the SQL stuff right now. Let’s do that in a later chapter. Let’s just briefly cover how Postfix can get data from the database. We provide Postfix with configuration files for that purpose. These files often have a “.cf” suffix (configuration file).
Let’s take the virtual_domains table for example. This is the contents of a file that is located at /etc/postfix/mysql-virtual-mailbox-maps.cf
:
# Information on how to connect to your MySQL server user = someone password = some_password hosts = 127.0.0.1 dbname = mailserver # The SQL query string query = SELECT mailbox_path FROM virtual_users WHERE email_address='%s'
The first four lines describe how the database can be connected to. You provide a database user and database password as well as the IP address or hostname of the SQL server and the name of the database.
A more interesting bit is the query that is defined in the last line. Postfix still thinks in left and right columns. So it will run this query and replace the %s by the email address it is looking for – that’s what is expected in the left column. The database will then get all rows from the database table that match this criterion. If one row matches the query then the “SELECT mailbox_path” will return just the value of what Postfix would expect to be in the right column.
That’s all the magic that Postfix needs to talk to your SQL database. You tell Postfix how to connect to the database and how the data in the database table corresponds to the left and right columns.
Note that a lookup here must only return just one row from the database. Postfix must uniquely know where the mailbox path for a given user is. There are other mappings though where it’s allowed to have multiple right-hand side items for one left-hand side item – for example in virtual aliases.
To use the above configuration file you have to configure it in Postfix’s main.cf file:
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual_mailbox_maps.cf
If you find that this mapping is not working as you expected then the “postmap -q” command is your friend. You can ask Postfix what the right-hand side value for a given left-side value is. Say that you are interested in the mailbox_path for the email_address “john@example.org”:
postmap -q john@example.org mysql:/etc/postfix/mysql-virtual_mailbox_maps.cf
Postfix will then run the above SQL query with your “john@example.org” argument:
SELECT mailbox_path FROM virtual_users WHERE email_address='john@example.org'
The result should be:
/var/vmail/example.org/john/Maildir
In this guide we will use a slight variation of that SQL query:
query = SELECT 1 FROM virtual_users WHERE email='%s'
Postfix does not need to know where the mailbox is located on disk. Instead it just needs to know whether an email address belongs to an existing mailbox user. It will just send the email to the Dovecot process that in turn puts the mail to disk. So we just return “1” because actually the right column is not considered anyway. Postfix just needs to know whether there is a database row or not.
In the callout about mandatory email addresses (local part), you have written postfix instead of postmaster.
Thanks. Fixed.
s/postfix@domain/postmaster@domain/
some typos on file names:
“/etc/postfix/virtual_mailbox_maps.cf” > “/etc/postfix/mysql-virtual-mailbox-maps.cf”
Thanks for the guide!
Oopsie. Thanks.
not fixed everywhere, still left in
virtual_mailbox_maps = mysql:/etc/postfix/virtual_mailbox_maps.cf
and
postmap -q john@example.org mysql:/etc/postfix/virtual_mailbox_maps.cf
BTW: Great guide! Thank You!
Awkward. 🙁 Thanks.
The header for the callout is still incorrect (“postfix and abuse“) and the “em” tags are visible 😉
Oops. Fixed. 🙂
Hi Christoph Haas,
there is a typo on the name of the brazilian drink, it’s “Caipirinha”.
Nice guide btw!
Sorry, that typo was inexcusable. Caipirinhas are holy!
Good evening
I followed the tutorial and I now have a personal mail server with my domain name.
Everything is working very well, a big thank you.
In addition, I have a professional email and various emails (gmail …). Is there a solution because I want to “concentrate everything” in “Roudcube”.
I have thought about fetchmail, but since I use folders to store my pro mails, I want them to be on the pro mail server as well.
I also made a test with mbsync but not succeeded in seeing the mails in roundcube
I hope to have been clear
thank you
Fred
There is a Roundcube addon for using multiple accounts. This may do what you want.
https://github.com/texxasrulez/additional_imap
I advise against duplicating e-mails from another service since anything you do on your server won’t be reflected on the original host (i.e. gmail).
You say in the (seperately )Virtual Mailbox mappings that “postmaster@example.org” and “abuse@example.org” redirects to “jack@example.com” but in the explanation for the mappings you say both of them redirect “jack@example.org”
Anyways, good guide!
Thank you for this great guide! Possible typo in
“Let’s take the virtual_domains table for example. This is the contents of a file that is located at /etc/postfix/mysql-virtual-mailbox-maps.cf:”
“virtual_domains” should be “virtual_users”, right? That way, it would match the SQL query string “query = SELECT mailbox_path FROM virtual_users WHERE email_address=’%s'”
I appreciate your effort. Share the knowledge!
i agree
“mailbox_path” isn’t a column in any of the database tables. The storage location of the mailbox is determined by Dovecot rather than Postfix so this reference is a mystery.
There is also no column named “email_address” so that needs to change too. An email address obviously wouldn’t be part of the virtual_domain table.