Mail aliases

What are mail aliases?

Email aliases provide a method by which mail can be received at one address but then delivered to another. This mechanism can be used to provide either the traditional alias mappings of multiple addresses to a single mailbox or to redirect mail from one account to another. These concepts are best illustrated with two simple examples both of which will assume that you provide mail services to a fictional company on the example.com domain.

In our first example we have a user, who we shall call Alice, who has the account alice@example.com and a mail box configured to receive this mail accordingly. Rather unfairly we would like to assign all email sent to the support@example.com address to Alice. This is an example of the traditional use of aliases to redirect mail from a virtual-address to a concrete mailbox. We can use the implementation detailed in this example to provide these services and more as we shall see.

In the second example we have two users, Alice and Bob. Not surprisingly given that Alice has been responsible for the entire technical support load of our fictional company she had decided that she needs a holiday and management have decided that Bob would be an ideal candidate to receive all email sent to Alice's account, including the support emails! This is an example of the use of aliases to provide mail redirection from one physical account to another and can also be provided using the example detailed in this section.

Additional database configuration

Before we can start to create aliases to perform any of the functions we have detailed in our examples we first need to create some more infrastructure which will be required to enable the required functionality. Firstly we need to create a new database table which will used to specify aliases on a domain. Such a table can be created using the SQL below.

postgres=# CREATE TABLE aliases ( 
postgres(#   id          SERIAL       PRIMARY KEY, 
postgres(#   domain_id   INTEGER      NOT NULL, 
postgres(#   address     VARCHAR(128) NOT NULL, 
postgres(#   destination TEXT         NOT NULL, 
postgres(#   active      BOOLEAN      NOT NULL     DEFAULT TRUE, 
postgres(#   CONSTRAINT domain_exists 
postgres(#     FOREIGN KEY(domain_id) REFERENCES domains(id) 
postgres(#   ON DELETE CASCADE 
postgres(# ); 
NOTICE:  CREATE TABLE will create implicit sequence "aliases_id_seq" for serial column "aliases.id" 
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "aliases_pkey" for table "aliases" 
CREATE TABLE 

As we don't want to allow the possibility of duplicate email addresses being entered into the aliases table, but we only store the address portion of the email address in our table in expanded form, the easiest way to achieve this end is to create a unique index on the domain_id and the address fields together as shown below. This also has the useful side effect of speeding up lookups when more than one record could fulfil the address part of the query.

postgres=# CREATE UNIQUE INDEX alias_unique ON aliases(domain_id, address); 
CREATE INDEX 

Also, as this table may be very heavily used, and in most cases the mail server will need to query the table for records which have a field containing a particular value, we should create some additional indexes to further speed up this process by allowing the database engine to locate the rows we need without ever performing a table scan. This can be accomplished using the following commands.

postgres=# CREATE INDEX alias_domain_index ON aliases(domain_id); 
CREATE INDEX 
postgres=# CREATE INDEX alias_address ON aliases(address); 
CREATE INDEX 

We shall also create a view which we shall use to join the aliases and domains table as well as ensure that only the active alias entries are ever used by the Postfix server without having to remember to include those query elements in our mail server configuration files. A suitable view can be created using the SQL shown below.

postgres=# CREATE VIEW active_alias_maps AS  
postgres-#   SELECT a.id, a.address, d.name AS domain, a.destination   
postgres-#     FROM domains d, aliases a   
postgres-#       WHERE a.domain_id = d.id AND   
postgres-#             d.active = TRUE    AND   
postgres-#             a.active = TRUE;   
CREATE VIEW 

With the required database infrastructure in place we can assign suitable permissions on these objects so that the mail administrator has the usual full control while the mail server is only allowed to read from the newly created view. The SQL commands shown below can be used to perform this task.

postgres=# GRANT ALL ON aliases, aliases_id_seq, active_alias_maps TO mail_admin; 
GRANT 
postgres=# GRANT SELECT ON active_alias_maps TO mail_server; 
GRANT 

Additional Postfix configuration

The configuration of the Postfix database access component follows the usual procedure. We create a new file to hold the configuration for this function specifying the database, user credentials and query to issue to the database server. Unfortunately, as you can see from our example query below, we are unable to use the %u escape sequence as the Postfix daemon would terminate queries without an address portion, which would cause catch-all aliases to fail. As a result we have to use the split_part function provided by the PostgreSQL database server to split the query string on either side of the @ symbol. This is considerably more efficient than using a virtual-field in the database view as all queries against virtual fields result in a table scan.

/etc/postfix/pgsql/pgsql-virtual-aliasmaps.cf
hosts    = database
dbname = mail
user = mail_server
password = mail_server_password

query = SELECT destination FROM active_alias_maps WHERE address = split_part('%s', '@', 1) AND domain = split_part('%s', '@', 2);

Now that we have the database access component configured to map our aliases using the database view we created in the previous section all that remains is to configure the Postfix server to provide alias functionality. This can be accomplished by adding the single line shown below to our existing configuration file.

/etc/postfix/main.cf
virtual_alias_maps = pgsql:/etc/postfix/pgsql/pgsql-virtual-aliasmaps.cf

So that our configuration changes are used immediately we should also instruct the Postfix daemon to reload the configuration files, as shown below.

lisa /etc/init.d/postfix reload

Creating our example aliases

With the configuration complete we can finally move on to creating our example aliases and testing our setup to ensure that everything works as expected. This section assumes that you already have suitable test accounts created for both of our example users with the names Alice and Bob respectively, if not then the SQL provided below will create two such accounts associated with the primary domain. The password assigned to these accounts will be password as before.

postgres=# INSERT INTO mailboxes(domain_id, username, password) VALUES(1, 'alice', '$1$/g$hfp5Tly0QAzcqhfMUuRwS.'); 
INSERT 0 1 
postgres=# INSERT INTO mailboxes(domain_id, username, password) VALUES(1, 'bob', '$1$/g$hfp5Tly0QAzcqhfMUuRwS.'); 
INSERT 0 1 

In the first of our example scenarios we wanted to redirect all mail sent to the support@example.com address to the account we have created for Alice, which can already be reached at alice@example.com as she has an account in the mailboxes table. We can achieve this end by inserting a single line of data into the aliases table as shown below. As you can see the entry is extremely simple consisting of the numeric code assigned to our domain, the address on that domain at which email is to be received and the address to which that mail should be delivered. In the example below the mail is delivered within the example.com domain however this does not have to be the case and external domains are perfectly valid targets for aliases.

postgres=# INSERT INTO aliases(domain_id, address, destination) VALUES(1, 'support', 'alice@example.com'); 
INSERT 0 1 

In the second of our example scenarios we wanted to redirect all mail sent to the existing mailbox used by Alice, including the support emails, to the mailbox we created for use by Bob, which can be reached at bob@example.com as expected. This can also be achieved by inserting a single line of data into the aliases table as shown below. As you can see the entry also extremely simple and uses exactly the same fields ad in the above example only in this case redirecting mail from the alice account to the bob@example.com address. This is possible because the aliases table will be consulted on each delivery attempt, not just the first delivery attempt for a given message.

postgres=# INSERT INTO aliases(domain_id, address, destination) VALUES(1, 'alice', 'bob@example.com'); 
INSERT 0 1 

When we introduced the concept of aliases we said that they could be used to achieve much more than that required by our two examples. We have already discovered that any valid address can be used as the destination for an alias so that mail can be forwarded from one domain to another. Another commonly used feature of the aliases system is to assign a single account as the recipient for all unassigned addresses on a domain. In the example below we create a "catch-all" alias which will send any mail received to the support@example.com address.

postgres=# INSERT INTO aliases(domain_id, address, destination) VALUES(1, '', 'support@example.com'); 
INSERT 0 1 
Information:
It should be noted that in the absence of any of our other aliases the final alias would not have redirected mail destined for an existing account to the support address. Any existing aliases or mailboxes are used as the destination before the "catch-all" alias is consulted.