This is the most boring but also most important page of the entire tutorial. 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 to 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. Ready? Good.
Postfix is the software component that speaks SMTP and sends and receives emails from the internet. It distinguishes three kinds of domains:
- Local domains
- Virtual mailbox domains
- Virtual alias domains
Let us look at them in detail.
Local domains
In simple cases Postfix will just use a local domain and local users. “Local” refers to the server. A local user is just a normal system user – one that is listed in the /etc/passwd file. All system users will be able to receive email for any local domain. Let us assume that you told Postfix that its local mail domain is “example.org” and you have a system user called “john”. Postfix would accept email for john@example.org and store the received email into /var/mail/john. This may sound like a decent solution but it has drawbacks:
- You would have to create system accounts for all users who want to receive email. Every system user is a potential security risk because it may allow shell logins. 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
The most important thing to keep in mind is that you never list any domain in mydestination that will be a virtual domain. You will get a warning in your log file and Postfix will behave strangely. Avoid that mistake!
If you don’t feel creative and do not really need a local domain then “mydestination = localhost” is a good choice.
Virtual mailbox domains
This type of domain is what we will use most. A virtual mailbox domain is used to receive email. But you do not use system users (/etc/passwd) to specify valid email addresses as with local domains. Instead you freely tell Postfix which addresses you consider valid. Postfix provides several ways to get that information. You can use text files or tell it to fetch that information from a database. But the principle is always the same.
Consider the following table that shows which email addresses you have and where their location on the disk is:
Virtual user | 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 |
You have two domains: example.org and example.com. So first you will have to tell Postfix that you are ready to receive emails for these domains. This is done by setting
virtual_mailbox_domains = example.org, example.com
in your Postfix configuration. Next you would need to tell Postfix which email addresses you want to receive email for and where to store the received emails on disk. The respective text file would look like this:
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
As you can see the file has two entries per line. The email address is specified in the left column. And the place of the mailbox on disk is specified in the right column. In Postfix documentation you may also find the acronym LHS for “left hand side” – this means the left column. Equally the RHS is the “right hand side” – the right column. Such a table with two columns is very common when dealing with Postfix. It is called a mapping.
In the above example I have just hardcoded the virtual domains in the Postfix configuration file (“virtual_mailbox_domains = example.org, example.com”). Obviously with many domains this is not comfortable to manage. That is why you can store that information in other places like a database or text file. But if Postfix always expect two-column mappings then how would you specify a list of domains? Postfix still expects two columns – but it does not care what you put into the second column. A text file listing domains may look like this:
example.org OK example.com OK
The “OK” does not mean anything special. You can write anything there as long as there is a second column.
We will not actually use text files – we will use a MySQL database. But if you decided to use text files then be aware that Postfix cannot access text files directly. You need to compile these files by running the “postmap” command on them. Example:
postmap /etc/postfix/virtual_mailbox_domains postmap /etc/postfix/virtual_mailbox_users
That will create create new files based on the above names but with a “.db” suffix. Postfix will not do that conversion automatically – this is a common caveat. But Postfix will only read the *.db files. So do not forget to run postmap after you changed a mapping file.
To make these mappings known to Postfix you would add these lines to your main.cf configuration file:
virtual_mailbox_domains = hash:/etc/postfix/virtual_mailbox_domains virtual_mailbox_maps = hash:/etc/postfix/virtual_mailbox_users
(No, you don’t use the “.db” name here. Postfix will add the “.db” suffix automatically.)
Are you still with me? Good. Let us forget about text files now. I promised that we will store our control data in a MySQL database. So let’s figure out how that works. Postfix will still expect a two-column mapping like above. But instead of putting that information into text files we have to tell Postfix where to find these two columns in the database. This is done using configuration files.
Example virtual_mailbox_maps.cf file:
# Information on how to connect to your MySQL server user = someone password = some_password hosts = 127.0.0.1 # The database name on the MySQL server dbname = mailserver # The SQL query string query = SELECT mailbox_path FROM virtual_users WHERE email_address='%s'
Imagine that you have a database table for virtual users with two columns. The left-hand side is the “email_address” column in that table. And the right-hand side is the “mailbox_path” column in that table. Just like we did when we used the text files. So this SQL query gets the right-hand side (mailbox path) for a given email address (email_address). The “%s” is the placeholder for the left-hand side and is filled by Postfix on every lookup.
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 as shown in the next section.)
To use the above configuration file you have to configure it in Postfix’s main.cf file:
virtual_mailbox_maps = 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: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 ISPmail guide we will use a slight variation of the SQL query:
query = SELECT 1 FROM virtual_users WHERE email='%s'
Postfix can store incoming email directly to disk. But we let Postfix forward the email to the Dovecot software that has additional features. So we are not actually interested in the path of the mailbox. It is sufficient to know if an email address is valid. We would get “1” as a result which is enough to make Postfix consider the email address valid and accept the incoming email for the recipient.
Virtual alias domains
In a larger organisation you will be asked to create email addresses that forward incoming emails to another address. Such forwarding addresses are called aliases. If you tell Postfix that a certain domain is a virtual alias domain you define that all email addresses in this domain are aliases that forward incoming email to other addresses. Email for that domain will not be stored to disk. This makes virtual alias domains pretty inflexible. I am sure you will need aliases but do not want to waste an entire domain for that purpose. That is why we will not use this kind of domain on our mail server.
Fortunately mailbox addresses and alias addresses can still be mixed within the same domain. For example you can receive email for john@example.org on your disk and make webmaster@example.org an alias that forwards all email to John. In this case we will declare “example.org” as a virtual mailbox domain and not a virtual alias domain. Aliases will work for mailbox domains.
As usual you will need a (two-column) mapping for such forwarding addresses. And the syntax is pretty simple:
alias-address1 recipient-address1 alias-address2 recipient-address2 alias-address3 recipient-address3
That looks simple, right? This is a typical mapping that we define as “virtual_alias_maps” in Postfix. This mapping is more flexible though. Assume that you want to forward an alias address to multiple recipients. Postfix supports that if your mapping returns multiple right-hand side destinations (to forward to) for one left-hand side source (the original recipient). Some examples:
Example 1: redirect all email for john@example.org to jeff@example.com
john@example.org jeff@example.com
This one is simple example. You have the source (john@example.org) and the destination (jeff@example.com) of the forwarding. John will never get the email.
Example 2: redirect all email for john@example.org to jeff@example.com but also receive a copy
john@example.org john@example.org john@example.org jeff@example.com
This is a bit trickier. If Postfix queries this mapping for john@example.org it will get two results. (Postfix is smart enough not to create a loop but to understand that you want to get a copy of the emaill.) This is the same as one row with the recipients seperated by commas:
john@example.org john@example.org,jeff@example.com
Example 3: redirect all email for any user in the example.org domain to joe@example.com (“catch-all”)
@example.org joe@example.com
This is called a catch-all alias. It will accept email for any user in the example.org domain and forward it to joe@example.com. If jill@example.org would not be an explicitly defined virtual user then her email would be caught by the catch-all alias and forwarded to joe@example.com.
Beware: Catch-all aliases catch spam. Lots of spam. They may look comfortable because they forward all email to one person without the need for creating aliases. But spammers often try to guess email addresses at a known domain. And with a catch-all alias you will receive spam for any of those guessed email addresses. Avoid them and rather define the existing email addresses. Even if it is more work.
I am not able to use the “relative” synatax mysql:virtual_mailbox_maps.cf. For example when I try
$> postmap -q john@example.org mysql:virtual_mailbox_maps.cf
I get
postmap: fatal: bad string length 0 < 1: mysql-virtual-mailbox-maps.cf_dbname =
If I use the full path like mysql:/etc/postfix/virtual_mailbox_maps.cf everything works fine
PS: I have functioning mailserver setup (using absolute paths everywhere)
Yes, that’s normal behavior. Not sure where Postfix is looking by default.
Hi and thanks for this fantastic manual, it really works! 🙂
I do have a question though: We have a couple of domains and some are really nothing but virtual alias domains. Is there an elegant way to configure a redirect that would send @example.org to @example.com and not to a single address in example.com? Best would of course be something integrated seamlessly into this manual, but if not, then something that will not break anything.
In virtual_aliases table set
source: @example.org
destination: @example.com
That’ll redirect domain example.org to example.com.
For example:
admin@example.org will be redirected to admin@example.com (if admin@example.com exists).
Would it be possible to use a regex to specifiy a “catch-all” alias, for example “john.(anything)@example.com”? I know it’s possible when using “regexp:/” alias map, but I was wondering if this could be accomplished using MySQL as well.
(Relevant: http://superuser.com/questions/353488/regex-multiple-catch-all-setup-in-postfix)