Madmail stores accounts, settings, quotas, tokens, and federation state in one application SQL database: SQLite (default) or PostgreSQL. Mail bodies stay on disk as Maildir. Contact-sharing pages use a separate SQLite file (sharing.db).
MySQL is not supported. Changing driver does not copy an existing SQLite file by itself. After you edit driver / dsn, restart the process — madmail reload does not re-open the database.
madmail install always writes driver sqlite3. The file path follows your state_dir.
auth.pass_table local_authdb {
table sql_table {
driver sqlite3
dsn /var/lib/madmail/credentials.db
table_name passwords
}
}
Create an empty database and a role that can create tables. Set both auth.pass_table and storage.imapsql to the same Postgres driver and DSN (v2 opens the auth DSN; keeping both in sync avoids a misleading config).
CREATE USER madmail WITH PASSWORD 'choose-a-secret';
CREATE DATABASE madmail OWNER madmail;
Example in madmail.conf:
auth.pass_table local_authdb {
auto_create yes
jit_domain $(primary_domain)
table sql_table {
driver postgres
dsn host=127.0.0.1 port=5432 user=madmail password=choose-a-secret dbname=madmail sslmode=disable
table_name passwords
}
}
storage.imapsql local_mailboxes {
auto_create yes
driver postgres
dsn host=127.0.0.1 port=5432 user=madmail password=choose-a-secret dbname=madmail sslmode=disable
retention 24h
default_quota 1G
appendlimit 100M
}
URL form is also accepted: postgres://madmail:choose-a-secret@127.0.0.1:5432/madmail?sslmode=disable.
Then restart Madmail. First start against empty Postgres applies sqlx migrations. You do not run GORM or create quotas / contacts by hand.
Stop the server so SQLite is idle, then:
madmail db sqlite-to-postgres --dsn 'host=127.0.0.1 user=madmail dbname=madmail sslmode=disable' --dry-run
madmail db sqlite-to-postgres --dsn 'host=127.0.0.1 user=madmail dbname=madmail sslmode=disable' -y
After the copy, set driver postgres as above and start the server. This copies application tables only. It does not move Maildir, the retry queue, admin_token, or sharing.db, and it does not rewrite madmail.conf. If Postgres already has password rows, pass --force.
For performance, the running server may keep a copy of credential rows (auth.pass_table) and per-user quota totals (storage.imapsql) in RAM. The authoritative data remains in the database.
If you change those databases with the CLI (for example madmail creds, madmail accounts, or madmail imap-acct) while the daemon stays up, call the Admin API resource POST /admin/cache/reload once so the process re-reads the tables. Alternatively, restart the service.