Setting Up Postfix with Virtual Mailboxes

August 16, 2026

Postfix is the MTA (Mail Transfer Agent) that handles sending and receiving email. It accepts incoming mail on port 25, delivers it to local mailboxes, and relays outgoing mail from authenticated users on port 587. This post covers installing Postfix on Gentoo with virtual mailbox domains backed by MySQL, TLS encryption, and SASL authentication through Dovecot.

Prerequisites

Before starting:

Setting USE Flags

Postfix needs MySQL support for virtual mailbox lookups and SASL support for authenticated submission:

cat > /etc/portage/package.use/postfix << 'EOF'
mail-mta/postfix mysql sasl ssl
EOF

Installing Postfix

emerge -av mail-mta/postfix

If another MTA (like ssmtp or sendmail) is installed, Portage will block. Remove it first:

emerge --deselect mail-mta/ssmtp
emerge -C mail-mta/ssmtp

Creating the Virtual Mail User

Virtual mailboxes are stored on disk under a single system user rather than individual Unix accounts. Create the vmail user and group:

groupadd -g 5000 vmail
useradd -g vmail -u 5000 -d /var/vmail -s /sbin/nologin -m vmail

Create the mail storage directory:

mkdir -p /var/vmail
chown vmail:vmail /var/vmail
chmod 770 /var/vmail

All virtual mailboxes will be stored under /var/vmail/domain/user/ in Maildir format.

Creating the Mail Database

Create the database and user that Postfix will query for virtual domains, mailboxes, and aliases:

mysql << 'EOF'
CREATE DATABASE IF NOT EXISTS mailserver CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS 'mailuser'@'localhost' IDENTIFIED BY 'mail-db-password';
GRANT SELECT ON mailserver.* TO 'mailuser'@'localhost';
FLUSH PRIVILEGES;
EOF

Postfix only needs SELECT access — it reads domain and mailbox information but never writes to the database. PostfixAdmin handles the write operations.

Configuring main.cf

The main Postfix configuration file is /etc/postfix/main.cf. This is the core of the mail server.

# Identity
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain
mydestination = localhost

# Network
inet_interfaces = all
inet_protocols = ipv4

# TLS (incoming connections)
smtpd_tls_cert_file = /etc/lego/example.com/certificates/mail.example.com.crt
smtpd_tls_key_file = /etc/lego/example.com/certificates/mail.example.com.key
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

# TLS (outgoing connections)
smtp_tls_security_level = may
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

# SASL Authentication (via Dovecot)
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes

# Restrictions
smtpd_recipient_restrictions =
    permit_mynetworks,
    permit_sasl_authenticated,
    reject_unauth_destination

smtpd_relay_restrictions =
    permit_mynetworks,
    permit_sasl_authenticated,
    reject_unauth_destination

# Virtual mailbox configuration
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailboxes.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-aliases.cf
virtual_mailbox_base = /var/vmail
virtual_minimum_uid = 5000
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000

# Delivery via Dovecot LMTP
virtual_transport = lmtp:unix:private/dovecot-lmtp

# Limits
message_size_limit = 52428800
mailbox_size_limit = 0

# Misc
smtpd_banner = $myhostname ESMTP
biff = no
append_dot_mydomain = no

Key Decisions

mydestination = localhost — Postfix doesn't deliver to local Unix accounts. All mail goes through the virtual mailbox system.

virtual_transport = lmtp:unix:private/dovecot-lmtp — Postfix hands incoming mail to Dovecot via LMTP (Local Mail Transfer Protocol). Dovecot handles final delivery, Sieve filtering, and quota enforcement.

smtpd_tls_auth_only = yes — SASL credentials are only accepted over TLS-encrypted connections. This prevents passwords from being sent in cleartext.

smtpd_tls_security_level = may — TLS is offered but not required for incoming mail on port 25. Requiring TLS on port 25 would reject mail from servers that don't support it. Port 587 (submission) requires TLS separately.

message_size_limit = 52428800 — 50MB maximum message size. Adjust based on your needs.

MySQL Lookup Files

Create the files Postfix uses to query the mail database.

/etc/postfix/mysql-virtual-domains.cf

user = mailuser
password = mail-db-password
hosts = 127.0.0.1
dbname = mailserver
query = SELECT domain FROM domain WHERE domain='%s' AND active = '1'

/etc/postfix/mysql-virtual-mailboxes.cf

user = mailuser
password = mail-db-password
hosts = 127.0.0.1
dbname = mailserver
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'

/etc/postfix/mysql-virtual-aliases.cf

user = mailuser
password = mail-db-password
hosts = 127.0.0.1
dbname = mailserver
query = SELECT goto FROM alias WHERE address='%s' AND active = '1'

Set restrictive permissions — these files contain database credentials:

chmod 640 /etc/postfix/mysql-virtual-*.cf
chown root:postfix /etc/postfix/mysql-virtual-*.cf

Configuring master.cf

Edit /etc/postfix/master.cf to enable the submission port (587) with mandatory TLS and authentication:

# Standard SMTP (port 25)
smtp      inet  n       -       n       -       -       smtpd

# Submission (port 587) - authenticated users only
submission inet n       -       n       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_tls_auth_only=yes
  -o smtpd_reject_unlisted_recipient=no
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

The submission port requires TLS (smtpd_tls_security_level=encrypt) and only accepts authenticated users. This is the port mail clients use for sending.

Starting and Enabling Postfix

rc-update add postfix default
rc-service postfix start

Alias Domains

If you host mail for a secondary domain that should mirror the primary domain's mailboxes (e.g., mail sent to user@example.org delivers to the same mailbox as user@example.com), configure it as an alias domain in PostfixAdmin. Postfix handles this through its virtual alias maps — the MySQL query resolves the alias to the primary address, and delivery proceeds normally.

For domain-level aliases, add an entry in the alias_domain table mapping example.org to example.com. Postfix needs an additional lookup file for this.

/etc/postfix/mysql-virtual-alias-domains.cf

user = mailuser
password = mail-db-password
hosts = 127.0.0.1
dbname = mailserver
query = SELECT alias_domain FROM alias_domain WHERE alias_domain='%s' AND active = '1'

Add to main.cf:

virtual_alias_domains = mysql:/etc/postfix/mysql-virtual-alias-domains.cf

Set permissions:

chmod 640 /etc/postfix/mysql-virtual-alias-domains.cf
chown root:postfix /etc/postfix/mysql-virtual-alias-domains.cf

Postfix Logging

Postfix logs all mail transactions to the system mail log. On Gentoo, this is /var/log/mail.log or /var/log/messages depending on your syslog configuration.

Monitor deliveries and errors in real time:

tail -f /var/log/mail.log

Common log entries to understand:

  • status=sent — message delivered successfully
  • status=deferred — temporary failure, Postfix will retry
  • status=bounced — permanent failure, sender gets a bounce message
  • NOQUEUE: reject — message rejected before queuing (policy restriction)

Verifying the Installation

Check Postfix Is Listening

ss -lnt sport = :25
ss -lnt sport = :587

Both should show Postfix listening.

Test SMTP Connection

telnet localhost 25

You should see the Postfix banner:

220 mail.example.com ESMTP

Type EHLO test and verify TLS is offered:

250-STARTTLS

Type QUIT to exit.

Test TLS on Submission Port

openssl s_client -connect localhost:587 -starttls smtp

This should show the TLS handshake and certificate details.

Verify MySQL Lookups

Test that Postfix can query the database (after PostfixAdmin creates the tables):

postmap -q example.com mysql:/etc/postfix/mysql-virtual-domains.cf

This returns the domain name if it exists in the database.

Check the Mail Log

tail -f /var/log/mail.log

Send a test email and watch for delivery or error messages.

Summary

After completing these steps:

  • Postfix accepts incoming mail on port 25 with opportunistic TLS
  • Authenticated users send mail via port 587 with mandatory TLS
  • Virtual domains and mailboxes are stored in MySQL
  • Mail is delivered to Dovecot via LMTP for final mailbox storage
  • The vmail user owns all mailbox files with Maildir format
  • MySQL credentials are protected with restrictive file permissions

Postfix is now ready to receive mail, but delivery requires Dovecot for LMTP delivery and IMAP access.