Linux

Postfix – maildrop Full, mailq Empty: unsupported dictionary type: mysql

By September 11, 2026No Comments

Postfix was writing 140 MB of /var/log/maillog per day on a server of mine, refusing every message with unsupported dictionary type: mysql, and I had received exactly zero of the alert emails it was supposed to be sending. mailq was empty; maildrop was not. An earlier check of mine had concluded, from that empty queue, that mail was fine.

It was not fine. There were 9,103 messages sitting in /var/spool/postfix/maildrop, the oldest one 103 days old.

Why it happens: unsupported dictionary type: mysql

Control panels — CWP in my case, and the same applies to ISPConfig, iRedMail, CyberPanel, Plesk, mailcow or any hand-built setup that stores mail users in MySQL — write a main.cf where every lookup goes through a MySQL map:

virtual_alias_maps    = proxy:mysql:/etc/postfix/mysql-virtual_alias_maps.cf
virtual_mailbox_maps  = proxy:mysql:/etc/postfix/mysql-virtual_mailbox_maps.cf
virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql-virtual_domains.cf
virtual_uid_maps      = proxy:mysql:/etc/postfix/mysql-virtual_uid.cf
virtual_gid_maps      = proxy:mysql:/etc/postfix/mysql-virtual_gid.cf
relay_domains         = proxy:mysql:/etc/postfix/mysql-relay_domains.cf
recipient_bcc_maps    = proxy:mysql:/etc/postfix/mysql-recipient_bcc.cf

MySQL map support is not in the postfix package — it is in the postfix-mysql subpackage (apt install postfix-mysql on Debian and Ubuntu). On this box only postfix was installed. dovecot-mysql was there, which is why mail looked half-configured rather than obviously broken: IMAP worked.

Every lookup then fails in proxymap:

postfix/proxymap[1234]: warning: mysql:/etc/postfix/mysql-virtual_alias_maps.cf is unavailable. unsupported dictionary type: mysql

cleanup refuses the message with map lookup problem -- message not accepted, try again later:

postfix/cleanup[1235]: warning: ...: map lookup problem -- message not accepted, try again later

The message stays in /var/spool/postfix/maildrop. pickup re-reads maildrop, hands it to cleanup, which fails again — forever. Each loop writes more log. Nothing ever expires, because the message was never accepted into the real queue in the first place.

Mail arriving over SMTP fails differently, with a 451 4.3.0 <...>: Temporary lookup failure to the sending server — same cause, different symptom.

The same error appears as unsupported dictionary type: pcre, pgsql or ldap, and the fix is the matching subpackage: postfix-pcre, postfix-pgsql, postfix-ldap. There is also a second root cause worth knowing: an entry in dynamicmaps.cf without the full path to the shared object, which typically shows up after a distribution upgrade. The line should read mysql /usr/lib/postfix/postfix-mysql.so dict_mysql_open.

mailq is empty but /var/spool/postfix/maildrop is full

mailq is not a reliable answer here. It reads the queue through showq, which does scan maildrop but skips any file it cannot parse or whose permissions do not mark it as complete — so you can get Mail queue is empty while the directory holds thousands of files.

Count the directory. That is the answer that cannot be wrong:

ls -1 /var/spool/postfix/maildrop | wc -l

And confirm the cause:

postconf -m | grep mysql        # no output = no MySQL map support
grep 'unsupported dictionary type' /var/log/maillog | tail -1

The fix, in this order

The order matters. Install the package first and Postfix will happily deliver the entire backlog — in my case several thousand alert emails from the last three months, to one mailbox, at once.

systemctl stop postfix

# park the backlog instead of deleting it
mv /var/spool/postfix/maildrop /var/spool/postfix/maildrop.stuck-$(date +%Y%m%d)
mkdir /var/spool/postfix/maildrop
chown postfix:postdrop /var/spool/postfix/maildrop
chmod 730 /var/spool/postfix/maildrop
restorecon -Rv /var/spool/postfix/maildrop    # the new dir inherits the wrong SELinux type
postfix set-permissions                       # or at least: postfix check

dnf -y install postfix-mysql

systemctl start postfix

postfix:postdrop and mode 730 are stock — they are what conf/postfix-files specifies — but they are only correct if mail_owner and setgid_group are at their defaults. The restorecon matters on any enforcing RHEL-family box: the recreated directory inherits its parent's SELinux type instead of postfix_spool_maildrop_t, and postdrop is then denied, which leaves local mail submission broken after you followed the fix.

If dnf answers "nothing provides postfix = …", your Postfix came from a control panel's own repository rather than the distribution's, and you need the matching package from that repo — installing the distribution's postfix-mysql against a panel-built postfix will not resolve.

Verify

postconf -m | grep mysql          # must now print: mysql
postfix check
grep 'unsupported dictionary type' /var/log/maillog | tail -1
tail -1 /var/log/maillog

The last error must predate the restart — which assumes the log timestamps are trustworthy, and they are not on a box where rsyslog cached the timezone.

Then send yourself a test message and watch it leave:

echo test | mail -s "postfix test" [email protected]
tail -f /var/log/maillog          # look for status=sent, dsn=2.0.0

The parked backlog

Look inside before you decide:

postcat /var/spool/postfix/maildrop.stuck-*/<file>

To re-inject anything worth keeping, move the files back in small batches and reload — use mv, not cp: a copy resets owner and mode, and pickup will then ignore the file.

mv /var/spool/postfix/maildrop.stuck-*/ABC123 /var/spool/postfix/maildrop/
systemctl reload postfix

Once you are sure nothing there is worth recovering, drop the parked directory. If you ever need to empty the live maildrop instead, the supported way is postsuper -d ALL maildrop, not a bare rm -rf.

If the retry loop has already filled your disk, the other half of the problem is the rotation: on RHEL-family 9, logrotate keeps four uncompressed copies by default.

The lesson that was worth more than the fix

The messages piling up were my own firewall and intrusion-detection alerts. For 103 days the alerting was completely dead while every dashboard said it was configured and I had no reason to doubt it.

A notification channel that has never been tested is not a notification channel. Send yourself a deliberate test alert when you build the box, and again on a schedule — a monthly cron that triggers one real alert costs nothing and is the only thing that distinguishes "no problems" from "no delivery".

Leave a Reply