Andrew Mercer
on this page

This doc covers the spectrum from lightest to heaviest way of handling "more than one address" on a Postfix server: simple redirects, forwarding an entire extra domain, building a mailing list from aliases, and finally full multi-domain virtual mailboxes backed by a database.

Simple forwarding domain (virtual alias domain)

The lightest pattern: you want an entire additional domain's mail to just forward to addresses on your existing, already-working mail setup — no new mailboxes, no new users, just redirection.

# /etc/postfix/main.cf — add to the bottom
virtual_alias_domains = newdomain1.tld newdomain2.tld
virtual_alias_maps = hash:/etc/postfix/virtual
# /etc/postfix/virtual
postmaster@newdomain1.tld    postmaster
alias@newdomain1.tld         someone@elsewhere.tld
alias2@newdomain1.tld        someoneelse@elsewhereagain.tld

Concretely — if you own example.com and want everything sent to @another-domain.com to land in your example.com inbox:

postmaster@another-domain.com    you@example.com
you@another-domain.com           you@example.com

Apply and test:

postmap /usr/local/etc/postfix/virtual
postfix reload

Send a message to the alias/virtual domain address and confirm it lands where expected. As with every hash:-backed map, remember: edit the source file, then postmap, then reload — see the overview doc.

Always define a postmaster@ alias for any domain you add. RFC 5321 requires every domain accepting mail to have a working postmaster address, and various automated systems (bounce handling, abuse reports) rely on it existing.

Mailing lists via /etc/aliases

For a small, low-maintenance mailing list, /etc/aliases is entirely sufficient — no list-management software needed:

# /etc/aliases
sales:
    john@example.com,
    bob@example.com,
    alice@example.com

Now anything sent to [email protected] fans out to all three addresses. Apply with:

postalias /etc/aliases

(postalias, not postmap — /etc/aliases uses a slightly different compiled format than the hash: maps used elsewhere, though the underlying idea — flat file → compiled database — is identical.)

Common warning: database /etc/aliases.db is older than source file — means you edited /etc/aliases but haven't re-run postalias yet:

postalias /etc/aliases

This alias-fan-out approach is genuinely a mailing list in the "everyone gets a copy" sense, but it has no subscribe/unsubscribe self-service, no digest mode, no per-recipient bounce handling, and no archive — for anything beyond a handful of trusted recipients, dedicated list software (Mailman, etc.) delivered through Postfix is the better tool, with /etc/aliases only pointing at the list software's own inbound address.

Full virtual mailboxes backed by MySQL

The heaviest pattern in this guide: rather than a fixed set of Unix accounts, mailbox existence, ownership, and routing are driven entirely by database lookups — the right approach once you're hosting mail for multiple domains/tenants and don't want to create a system account per mailbox.

# /etc/postfix/main.cf
myhostname = host.domain.tld
mydomain = domain.tld
mydestination = $myhostname, localhost.$mydomain, localhost
mynetworks_style = host
relay_domains = proxy:mysql:/etc/postfix/mysql_relay_domains_maps.cf

# SASL — require authentication for anything not from a trusted network
broken_sasl_auth_clients = yes
smtpd_sender_restrictions = permit_sasl_authenticated, permit_mynetworks
smtpd_recipient_restrictions =
    permit_mynetworks,
    permit_sasl_authenticated,
    reject_non_fqdn_hostname,
    reject_non_fqdn_sender,
    reject_non_fqdn_recipient,
    reject_unauth_destination,
    reject_unauth_pipelining,
    reject_invalid_hostname,
    reject_rbl_client bl.spamcop.net
smtpd_sasl_auth_enable = yes
smtpd_sasl_authenticated_header = yes
smtpd_sasl_local_domain = $myhostname
smtpd_sasl_security_options = noanonymous
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth

# Virtual mailbox / MySQL lookups
virtual_alias_maps = proxy:mysql:/etc/postfix/mysql_virtual_alias_maps.cf
virtual_gid_maps = static:12
virtual_mailbox_base = /etc/virtual_mail
virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql_virtual_domains_maps.cf
virtual_mailbox_limit = 51200000
virtual_mailbox_maps = proxy:mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf
virtual_minimum_uid = 101
virtual_transport = dovecot
virtual_uid_maps = static:101

What each proxy:mysql:/path.cf file contains is a small query template, e.g. (illustrative — not from the original notes, which didn't include the query files themselves):

# /etc/postfix/mysql_virtual_mailbox_maps.cf
user = mail_reader
password = REDACTED
hosts = 127.0.0.1
dbname = mailserver
query = SELECT maildir FROM mailbox WHERE username='%s'

Key settings worth understanding individually: - virtual_transport = dovecot — hands final delivery off to Dovecot's LMTP delivery agent (defined as a service in master.cf) instead of Postfix's own built-in virtual(8) agent, so Dovecot can apply its own delivery logic (Sieve filtering, quota enforcement, etc.). - smtpd_sasl_type = dovecot / smtpd_sasl_path = private/auth — Postfix doesn't implement SASL authentication itself here; it delegates to Dovecot's auth socket, so the same user database serves both "can this person send mail" (SMTP AUTH) and "can this person read mail" (IMAP/POP3 login). - reject_rbl_client bl.spamcop.net — see the email-fundamentals anti-spam doc; add further RBLs as additional lines if desired (e.g. sbl-xbl.spamhaus.org).

A known gotcha: if delivery via Dovecot's LMTP pipe produces warning: pipe flag 'D' requires dovecot_destination_recipient_limit = 1, add:

dovecot_destination_recipient_limit = 1

to main.cf alongside the block above.

Managing virtual domains through a web UI

Hand-editing MySQL tables (or flat files) for every mailbox/domain/alias gets tedious fast, which is why tools like PostfixAdmin exist — a PHP web front-end for managing exactly the domains/mailboxes/aliases that the MySQL-backed configuration above reads from. The historical setup notes for this (FreeBSD ports, PostfixAdmin 2.x, PHP 4/5, an old setup.php-based install flow) are dated enough that they're not reproduced step-by-step here; if you revisit this, treat it as install the current PostfixAdmin release for your current OS from its own documentation, pointed at the same MySQL schema your virtual_*_maps queries expect, rather than following a decade-old install procedure. The core idea carries forward unchanged: PostfixAdmin edits the database rows; Postfix (via the proxy:mysql: maps above) just reads them.

Auto-reply / vacation responder

A small but distinct pattern worth grouping here since it's really "a virtual/alias address that pipes to a script" rather than delivery to a mailbox at all. Three pieces:

# main.cf
transport_maps = hash:/etc/postfix/transport
# /etc/postfix/transport
autoreply.domain.tld    autoreply:
# master.cf — define the pipe service
autoreply  unix  -  n  n  -  -  pipe
  flags= user=nobody argv=/usr/local/bin/autoreply $sender $mailbox
#!/bin/sh
# /usr/local/bin/autoreply
cat /etc/postfix/autoreply.txt | mail -s "Your request has been received" "$1"
postmap /etc/postfix/transport

Any address routed through the autoreply transport gets piped to that script instead of delivered to a mailbox, and the script sends back a canned response. This is a genuinely minimal, low-maintenance way to build a "thanks, we got your message" auto-responder without pulling in a full mail-processing framework — fine for a single address; for anything with state (don't-reply-more-than-once-per-sender-per-day logic, etc.) a purpose-built vacation/autoresponder tool is a better fit than a bare shell script.