Virtual Domains

Postfix distinguishes between three kinds of domains. This is a very important concept that you need to understand. Probably half of the support request of desperate readers is caused by misunderstandings here. This page is just for learning - there is nothing to be done on your server yet. See also the documentation on virtual domains on the Postfix website.

Local domains

Postfix is the software component that speaks SMTP and sends and receives emails from the internet. Typically Postfix knows about local domains and local users. A local user is just a normal system user - one that is listed in the /etc/passwd file. This means that all system users will get emails for any local domain. The "mydestination" configuration setting lists all local domains. Example:

mydestination = example.org, example.com, example.net

Let's say you created a system user "johndoe" (e.g. using the "adduser" command). This simple setup will make Postfix receive emails for

  • johndoe@example.org
  • johndoe@example.com
  • johndoe@example.net

You can't make johndoe's account just work in one domain. So this is not feasible for different users in different domains. Neither will it work well with many users as you had to create system accounts for each of them. Is it still a good idea to set up at least one local domain in case of configuration or operation problems with other types of domains. If you don't feel creative then "mydestination = localhost" is a safe choice. Postfix automatically receives emails for these users and saves them under /var/mail/USERNAME.

Virtual mailbox domains

This type of domains is the most important type in this tutorial. A virtual mailbox domain is also a domain used to receive email. But you do not use system users (/etc/passwd) to specify valid email addresses in that domain. Instead you explicitly tell Postfix which addresses are valid in a domain. A simple way to configure such domains and users is using text files. Consider the following mapping of recipient email addresses to mailboxes on the disk:

Virtual user Virtual mailbox location on disk
john@example.org /var/mail/example.org/john/Maildir
jack@example.org /var/mail/example.org/jack/Maildir
jack@example.com /var/mail/example.com/jack/Maildir

You have two domains: foo.org and bar.org. So first you will have to tell Postfix about these domains. This is done by setting

virtual_mailbox_domains = example.org example.com

in your Postfix configuration. Next you need to tell Postfix which email addresses you are ready to receive email for and where to store the received emails on disk. The respective text file could be stored in /etc/postfix/virtual_mailbox_users and 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 valid email addresses are specified in the left column. And the place on disk where the emails for each recipient address are stored is specified in the right column. In most Postfix literature 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 also called a mapping or hash table.

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 feasable any more. So you can also use a mapping file to configure the domains. Let's assume you saved it to /etc/postfix/virtual_mailbox_domains and it looked like this:

example.org  OK
example.com  OK

You may wonder why we can' t just list the domains one-per-line in this file. The reason is that a mapping file always has two columns. In such a "one-dimensional" mapping (list of domains) Postfix does not care about your second column. It does not even have to be "OK" - it can be any string.

If you decided that such text files are okay for you then you will still have to compile these files by running the "postmap" command on them. Example:

postmap /etc/postfix/virtual_mailbox_domains
postmap /etc/postfix/virtual_mailbox_users

postmap will create create additional files based on the above file names but with a ".db" suffix. Postfix will not do that automatically - this is a common caveat. And it will only obey 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

Now you have the tools you need to set up thousands of domains and email accounts in two text files. That's nice. But actually I promised that we will store such data in a MySQL database. Fortunately this is not much harder than using the above text files. Remember: a mapping is simply a way to assign one value to another . So all you have to do is tell Postfix how to access the two columns of a mapping from a database table. This is done using '.cf' configuration files (see also http://www.postfix.org/MYSQL_README.html or run "man 5 mysql_table" in the shell).

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. 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 later you find that this mapping is not doing what you intended then the "postmap -q" command can be used to ask Postfix what the right-hand side value for a given left-side value would be. Say that you are interested in the mailbox_path for the email_address "john@foo.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/mail/example.org/john/Maildir

(Note: In this tutorial we will not let Postfix deliver the email directly. Rather it hands over incoming email to Dovecot. So we won't use the above virtual_mailbox_maps in this tutorial. It is still important to understand how Postfix deals with virtual users.)

Virtual alias domains

Virtual alias domains are used for forwarding email from an email address to one or more other email addresses. Virtual alias domains can't receive email though. They only forward mail somewhere else. The virtual_alias_maps mapping contains forwardings (source, destination) of users or domains to other email addresses or entire domains. Incidentally virtual_alias_maps are obeyed for any received email. So in most cases you do not really need virtual alias domains as you can declare all domains as virtual mailbox domains and use virtual alias maps for forwarding purposes. Technically defining a domain as a virtual alias domain makes Postfix accept email for that domain but you still need an entry in the virtual_alias_maps mapping to tell Postfix where to forward the email.

A note on the virtual_alias_maps: they can return multiple right-hand side destinations (to forward to) for one left-hand side source (the original recipient). You can use that to forward an email to several recipients and to control whether you want to keep a copy.

Example 1: forward all email for john@example.org to jeff@example.com

john@example.org   jeff@example.com

This one is simple. You have the source (john@example.org) and the destination (jeff@example.com) or the forwarding. John will never see the email.

Example 2: forward 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@foo.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: forward all email for any domain in the example.org domain to joe@example.com

@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@baz.org.

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. Try to avoid them and rather define the existing email addresses. Even if it seems to be more work.

7 Comments

Small mistakes in your text

Hello,


It seems that you let some old-version terms in your text like this john@foo.org instead of john@example.org :

If later you find that this mapping is not doing what you intended then the "postmap -q" command can be used to ask Postfix what the right-hand side value for a given left-side value would be. Say that you are interested in the mailbox_path for the email_address "john@foo.org":

postmap -q john@example.org mysql:virtual_mailbox_maps.cf

Great guide.   you made

Great guide.

 

you made things clearer than they are.

 

you have a little typo here, right after this example.

Example 2: forward all email for john@example.org to jeff@example.com but also receive a copy

 

john@foo.org

postmap: fatal: bad string length 0 < 1: virtual_mailbox_maps.cf

Very Cool tutorial great :-)

i get an error when i do that

postmap -q john@mydomain.de mysql:virtual_mailbox_maps.cf

postmap: fatal: bad string length 0 < 1: virtual_mailbox_maps.cf_dbname =

First i think the problem is the database. my DB_User ist mailsystem and i can connect with mysql -u mailsystem -p

mysql>

my main.cf is this here

virtual_mailbox_maps = mysql:virtual_mailbox_maps.cf

# Information on how to connect to your MySQL server
user = mailsystem
password = mypassword
hosts = localhost

# The database name on the MySQL server
dbname = mailsystem

# The SQL query string
query = SELECT mailbox_path FROM virtual_users WHERE email_address='%s'

mydestination?

Hi,

Great stuff here.

Followed your tutorial to the point, and most things returned what it should (I do get a db_name error in the end, and this is just backtracking to debug. Therefore this question.)

But I'm a little confused about the mydestination part. Is it something I shoud put somewhere, like create a file or ... ? I read http://www.postfix.org/VIRTUAL_README.html, but left me just as baffled.

Anything important I should be aware of?

Mixed up domain names?

Hi Christoph,

I have not yet installed postfix nor ever tried to setup a mail server. Instead I started looking for information about the various parts I will need. And postfix seems to be a good idea and starting point.
So forgive me if I'm totally wrong. But for me it seems you have re-worked this part of the tutorial several times without clean up the domain names.
In detail I mean:

1) "You have two domains: foo.org and bar.org. So first you will have to tell Postfix about these domains. This is done by setting virtual_mailbox_domains = example.org example.com"
Why are the virtual domains not setup with foo.org and bar.org?
2) And in all following lines for paragraph about Virtual domains you mention the "example.org" and "example.com" domain instead of foo.org and bar.org.
3) "@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@baz.org."
I think it has to be "joe@bar.com" which means "r" instead of "z" and "com" instead of "org".

Hope I did not made a complete idiot of myself with this post.

Greets Joe