← Back home

2026.08 / recipient rejection 046

Docker Mailserver “User Unknown in Virtual Mailbox Table”? Fix It

A sender receives a bounce containing 550 5.1.1 Recipient address rejected: User unknown in virtual mailbox table. The domain reaches your Docker Mailserver host, Postfix answers, but it refuses the recipient during the SMTP conversation. That wording is useful: it points to recipient recognition, not a generic outage.

Do not disable recipient checks or turn the domain into a catch-all just to make the error disappear. Postfix rejects unknown recipients at SMTP time so it does not accept mail that cannot be delivered. The safe repair is to prove which address was rejected, determine whether it should be a mailbox or alias, update the configured Docker Mailserver account source, and test the active lookup.

Keep real addresses, server addresses, complete logs, account files, password hashes, and private configuration out of tickets and public posts. The examples below use [email protected].

What the error proves—and what it does not

EvidenceMeaning
550 5.1.1 during RCPT TOThe receiving SMTP server rejected that envelope recipient before accepting the message.
virtual mailbox tablePostfix classified the domain as a virtual mailbox domain and did not find a valid recipient in its effective maps.
local recipient tableA different address class is involved; inspect mydestination and the mail hostname/domain relationship.
A later non-delivery report after acceptanceThis is not the same failure; follow the queue ID and final delivery error.
One spelling works and another failsThe path is healthy; compare the exact mailbox or alias definition.

The official Postfix address-class documentation says valid recipients for virtual mailbox domains are listed through virtual_mailbox_maps; invalid recipients are rejected with this exact response. That rejection does not, by itself, prove which Docker Mailserver source is stale or missing.

1. Capture the exact rejected envelope recipient

Read the sender’s complete SMTP response or a narrow private server-log window. Do not rely on the visible To: header: mailing software, aliases, forwarding, and blind-copy delivery can make the SMTP envelope recipient different.

docker compose logs --since 10m mailserver

Use your actual Compose service name. Find the single rejection and record privately:

Common causes at this point are mundane: a typo, an old address retained by a contact form, a removed mailbox, or an alias that was planned but never provisioned.

2. Prove public mail routes to this server

Confirm the recipient domain’s MX records and resolve each target:

dig +short MX example.com

dig +short A mail.example.com
dig +short AAAA mail.example.com

A rejection in your current Docker Mailserver logs already proves that one attempt reached this instance. DNS checks still matter when some senders succeed and others hit a retired host, a stale IPv6 route, or a secondary MX with different account data. Mail records should normally remain DNS-only when using Cloudflare; Cloudflare’s ordinary orange-cloud web proxy is not an SMTP proxy.

3. Decide whether the address should be an account or an alias

Docker Mailserver’s account-management overview distinguishes the two:

If a person needs a separate inbox and IMAP login, create an account. If [email protected] should arrive in [email protected], create an alias. An alias does not become a separate IMAP user. This distinction also prevents unnecessary duplicate mailboxes.

4. List provisioned accounts and aliases through the supported CLI

For Docker Mailserver’s default file provisioner, use the setup commands rather than opening files that contain account hashes:

docker compose exec mailserver setup email list

docker compose exec mailserver setup alias list

Check the exact full address, including its domain. If you use LDAP, the container environment, or another documented provisioner, query that active source instead. Adding an entry to the file provisioner cannot repair an LDAP lookup.

Also check the alias target. An alias that points to a misspelled, removed, or unintended target is not a valid end-to-end repair merely because its left-hand address appears in a list.

5. Add the smallest correct recipient definition

To add a real mailbox with the file provisioner, use an interactive prompt so the password is not placed in shell history:

docker compose exec mailserver \
  setup email add [email protected]

To add an alias that delivers to an existing mailbox:

docker compose exec mailserver \
  setup alias add [email protected] [email protected]

Use the syntax documented for your pinned Docker Mailserver version. Do not paste passwords into command arguments, publish the generated account file, or create a domain-wide catch-all as a shortcut. Catch-alls attract typo mail and spam, hide address mistakes, and make recipient ownership harder to audit.

6. Query Postfix’s effective maps instead of guessing

First inspect the active parameters:

docker compose exec mailserver \
  postconf virtual_mailbox_domains virtual_mailbox_maps virtual_alias_maps mydestination

Keep the output private because it can reveal hosted domains and internal lookup paths. Then use the lookup type and map shown by postconf to query the exact address. A typical map query has this shape:

docker compose exec mailserver \
  postmap -q [email protected] <map-from-postconf>

docker compose exec mailserver \
  postmap -q [email protected] <alias-map-from-postconf>

Do not copy a map path from an old forum answer. Docker Mailserver versions and provisioners can expose different lookup types. The effective postconf result on the running container is the evidence that matters.

7. Check persistence before restarting anything

Confirm the configuration volume is the intended persistent host path or named volume and that the change was made through the provisioner your deployment actually uses:

docker compose config

docker compose ps

Review the rendered Compose output locally and redact it before sharing; it may contain domains, paths, and environment details. A recipient added only inside an ephemeral container or to the wrong mounted directory can vanish on recreation. Conversely, blindly restarting may hide which supported setup operation should have updated the running maps.

If your version documents a reload or restart after an account-source change, use that narrow action and immediately re-run the exact map query. Avoid editing generated Postfix files in the container: the next startup can overwrite the change.

8. Handle “local recipient table” as a different branch

If the response says User unknown in local recipient table, inspect the mail hostname and mydestination. Docker Mailserver’s FAQ specifically warns against listing the same domain in both mydestination and virtual_mailbox_domains.

A frequent mistake is using the bare mailbox domain as the server hostname, then unintentionally making Postfix classify addresses under the local domain rules. Keep a stable host identity such as mail.example.com, while example.com remains a hosted mailbox domain. Make persistent changes through Compose environment or documented override files, then compare effective postconf output.

9. Test SMTP recipient acceptance without sending a message

After the map recognizes the recipient, test from a permitted external network over port 25. You can stop after RCPT TO and QUIT; do not send message content:

openssl s_client -quiet -starttls smtp \
  -connect mail.example.com:25 \
  -servername mail.example.com

EHLO test.example.net
MAIL FROM:<[email protected]>
RCPT TO:<[email protected]>
QUIT

Use domains and addresses you control. A 250-class recipient response proves Postfix now accepts that envelope address; it does not prove final mailbox delivery. If your provider blocks outbound port 25 from the test network, use a controlled message from an external mailbox and correlate it with the server log.

10. Prove final delivery and persistence

  1. Send one harmless message from an external account to the repaired address.
  2. Match the SMTP event and queue ID in a short private log window.
  3. Confirm the message reaches the intended mailbox, not merely that Postfix accepted it.
  4. If it is an alias, confirm it reaches the exact intended target.
  5. Restart or recreate only as your normal deployment procedure requires.
  6. Repeat the account/alias listing and Postfix map query after recreation.
  7. Send one final controlled message and confirm there is no fresh 550 5.1.1.

This catches two false fixes: an ephemeral account that disappears after deployment, and an alias accepted by SMTP but routed to the wrong destination.

Do not “fix” it by weakening recipient validation

Postfix documentation explains why rejecting unknown recipients during SMTP is desirable: the sender receives an immediate, accurate response, while your server avoids accepting undeliverable mail and generating backscatter later. Emptying recipient maps, accepting every local part, or broadly trusting networks changes the security model instead of repairing the address.

If you genuinely need many role addresses, define explicit aliases and keep their targets reviewed. If you need a temporary migration address, give it an owner and removal date. Recipient validity should be intentional and testable.

Compact diagnosis order

  1. Capture the exact envelope recipient and full rejection class.
  2. Confirm MX, A, and AAAA route senders to the intended host.
  3. Decide whether the address should be a mailbox account or an alias.
  4. List it through the active Docker Mailserver provisioner.
  5. Add only the missing account or explicit alias through supported tooling.
  6. Inspect effective Postfix domain, mailbox, and alias maps.
  7. Resolve any virtual-versus-local address-class conflict.
  8. Test RCPT TO, then prove final mailbox delivery.
  9. Recheck map recognition and delivery after normal recreation.

Authoritative references: Docker Mailserver’s account-management overview, file provisioner documentation, and FAQ; plus Postfix’s official address classes and unknown local recipient documentation. Match commands to the versions and provisioner you actually run.