Once a message reaches the MTA that's authoritative for its domain, something has to put it where a user (or their IMAP client) can read it. That last step is delivery, and it's conceptually separate from everything SMTP.
Storage formats¶
| Format | Layout | Pros | Cons |
|---|---|---|---|
| mbox | One file per folder; messages concatenated, each starting with a From line |
Simple; one file to copy; the default for /var/mail/<user> |
Needs locking; concurrent writers corrupt it; deleting a message rewrites the file; From lines in bodies get mangled (>From) |
| Maildir | One file per message in tmp/, new/, cur/ per folder |
Lock-free (write to tmp/, atomic rename into new/); robust; easy to back up incrementally; supported by every tool |
Many small files (inode usage); slow ls on huge folders |
| Maildir++ | Maildir with subfolders as dot-prefixed directories (.Sent/, .Lists.Linux/) plus quota files |
The de facto convention for IMAP servers | Same as Maildir |
| sdbox / mdbox (Dovecot) | Dovecot's own formats: one or many messages per file, with indexes | Fastest for very large mailboxes; mdbox saves inodes | Dovecot-only; use doveadm for everything, never touch files directly |
Maildir is the sensible default for anything new. It's what Postfix's virtual(8) writes when the path ends in /, what Dovecot uses unless told otherwise, and what sync tools (mbsync, OfflineIMAP) produce.
Postfix chooses the format by path: a mailbox path ending in / means Maildir, otherwise mbox. (home_mailbox = Maildir/ for system users; virtual_mailbox_maps values like domain.tld/user/ for virtual ones.)
System accounts vs virtual mailboxes¶
Postfix distinguishes two very different delivery models. Mixing them up causes a lot of confusion in older configuration examples.
- System (local) accounts: the recipient is a real Unix user in
/etc/passwd. The domain is listed inmydestination, and mail is delivered bylocal(8)to/var/mail/<user>(mbox) or~/Maildir/(withhome_mailbox = Maildir/).local(8)also processes/etc/aliasesand~/.forward. - Virtual mailboxes: recipients don't need Unix accounts. Domains are listed in
virtual_mailbox_domains, and a lookup table (flat file, SQL, LDAP) maps each address to a storage path. All mail is stored under one service UID (conventionallyvmail). This lets one server host many domains and thousands of users without creating system accounts. It's the model behind PostfixAdmin and every Postfix + Dovecot + SQL setup.
A domain must appear in exactly one address class. Never list the same domain in both mydestination and virtual_mailbox_domains (or virtual_alias_domains). Postfix warns, and delivery becomes unpredictable.
| Postfix address class | Listed in | Delivered by |
|---|---|---|
| Local | mydestination |
local_transport (local:) |
| Virtual alias | virtual_alias_domains |
Rewritten via virtual_alias_maps, then re-routed |
| Virtual mailbox | virtual_mailbox_domains |
virtual_transport (virtual: or lmtp: to Dovecot) |
| Relay | relay_domains |
relay_transport (typically to an internal mail server) |
| Default | Everything else | default_transport (smtp:), the internet |
Aliases vs mailboxes¶
An alias only redirects an address to one or more other addresses. It has no storage of its own. Postfix has two alias mechanisms that look alike but serve different scopes:
/etc/aliases(compiled withpostaliasornewaliases, referenced byalias_maps) applies only to local recipients (mydestinationdomains). Classic uses: sendingrootto a real inbox, a tiny mailing list, or piping an address to a program (list: "|/usr/local/bin/handler").virtual_alias_maps(compiled withpostmap) applies to any domain and is processed first. Use it to forward a whole extra domain elsewhere (a virtual alias domain), or to alias addresses in virtual mailbox domains.
The MDA: where delivery is handed off¶
Postfix can write mailboxes itself (local(8), virtual(8)), but its delivery agents are deliberately simple: no server-side filtering, quotas with limited behaviour, and no index updates. Production setups hand final delivery to Dovecot, which then does double duty:
- As the MDA, it accepts mail from Postfix over LMTP, applies Sieve filters and quotas, writes the mailbox, and updates its indexes so IMAP clients see new mail instantly.
- As the IMAP/POP3 server, it serves the same mailboxes to clients, and provides SASL authentication to Postfix (
smtpd_sasl_type = dovecot) so one user database controls both "can send" and "can read".
# /etc/postfix/main.cf: deliver virtual mailboxes through Dovecot LMTP
virtual_transport = lmtp:unix:private/dovecot-lmtp
# /etc/dovecot/conf.d/10-master.conf
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
mode = 0600
user = postfix
group = postfix
}
}
Older guides use Dovecot's LDA (dovecot-lda) through a pipe(8) service in master.cf instead. That works, but it starts a process per message and needs dovecot_destination_recipient_limit = 1. LMTP is the modern choice.
Historically procmail filled the MDA role for system accounts (mailbox_command = /usr/bin/procmail). It's unmaintained. Use Dovecot + Sieve (with ManageSieve so users can edit rules from their client) instead.
"Postfix + Dovecot + SQL" is such a common combination for this reason: Postfix handles SMTP in and out, and Dovecot handles everything about storing and serving mailboxes.