Incoming mail is not arriving
When to use: senders report bounces or long delays to addresses on this server, a test message from an outside account never reaches the inbox, or the Delivery log shows no inbound traffic when there should be some.
Preconditions: shell access to the host; an outside machine (not this server) that can make connections to port 25; admin access to the console.
Work from the outside in and stop at the first step that fails: each one is only meaningful once the one before it passes. Commands use the container names Compose gives the stack (mailserver-postfix-1, mailserver-dovecot-1, …). Start with mailctl doctor (Doctor): it answers steps 3 and 4 in one pass.
It is not the licence. No licence stage ever stops incoming mail: SMTP, LMTP and IMAP are never checked against it, and the daemons do not read it. A lapsed licence refuses growth from 30 days, makes the admin panel read-only from 60 and, on a billing lapse, refuses outgoing mail from 90 days — never receiving or reading (Licensing). Do not spend time on Licence while inbound mail is down.
-
DNS: does the world send to this server? From any machine:
Terminal window dig +short MX example.comdig +short A mail.example.comThe MX must name the mail hostname, and that hostname’s A record must be this server’s public IP. Domains → the domain → DNS & routing → Verify now checks the same from the server’s side and shows what is actually published next to what should be. A domain whose routing is set to Another server is deliberately left out of what Postfix accepts; its mail goes to its own MX, not here.
-
Is port 25 reachable from outside? From a machine that is not this server (many home and office connections block outbound 25, so use another server):
Terminal window nc -v mail.example.com 25A working server answers
220 <mail hostname> ESMTP. A timeout means something between the internet and Postfix drops the connection: the provider’s or host’s firewall, or a cloud security group. Check that Docker publishes the port on the host:Terminal window docker port mailserver-postfix-1 25On a default install it prints
0.0.0.0:25; the address and port come fromPUBLISH_BINDandSMTP_PORTin.env.PUBLISH_BIND=127.0.0.1publishes on loopback only, which is for a development stack and receives nothing from outside. -
Is Postfix running?
Terminal window docker ps --filter name=mailserver-postfix-1docker logs --tail 100 mailserver-postfix-1The status must be
Up … (healthy). A container that restarts in a loop usually means a configuration it refused:GET /api/v1/server/config/historyshows the validator output, andPOST /api/v1/server/config/rollbackreturns to the previous version (Doctor → Common findings). The container healthcheck ispostfix status, which passes with a dead database, so healthy does not clear step 4. -
Can Postfix read the database? Postfix and Dovecot read the domains, mailboxes and aliases straight from Postgres views, with their own credentials (
DB_DAEMON_USER, defaultmail_daemon). When that path breaks, all mail stops while every container still reports healthy.mailctl doctorreports it aspostfix → postgresanddovecot → postgres; to ask by hand, query the maps Postfix uses:Terminal window docker exec mailserver-postfix-1 postmap -q example.com pgsql:/etc/postfix/sql/domains.cfdocker exec mailserver-postfix-1 postmap -q user@example.com pgsql:/etc/postfix/sql/mailboxes.cfdocker exec mailserver-postfix-1 postmap -q user@example.com pgsql:/etc/postfix/sql/aliases.cfThe domain map prints the domain and the mailbox map prints the Maildir path (
example.com/user/). Awarning: connect to pgsql serveron stderr is the broken path: checkpostgresis up, then restartapi(or, if you changedDB_DAEMON_PASSWORDin.env, recreateapi postfix dovecot), which sets themail_daemonpassword fromDB_DAEMON_PASSWORDon every boot (Doctor). No output and no warning means the database answered and the row is not there. The views leave out:- a domain that is inactive, or whose tenant is inactive, or that is routed to Another server;
- a mailbox that is inactive or suspended (Suspended (login and delivery refused; mail is kept) on the mailbox’s page) — mail to it is refused as
User unknown in virtual mailbox table; - an alias or forwarder that is inactive.
-
What is Postfix saying to the senders? The Postfix log is in the shared
maillogvolume and covers the current day; earlier days are rotated beside it, gzipped, and kept for seven days.Terminal window docker exec mailserver-postfix-1 grep 'NOQUEUE' /var/log/mail/postfix.log | tail -20The same rejections are in the Delivery log with Outcome set to rejected at the door and direction
inbound(Delivery log & bounce classification). What the common lines mean:Relay access denied— Postfix does not consider the recipient’s domain one of its own: back to step 4 for the domain map.User unknown in virtual mailbox table— the domain is ours, the address is not a mailbox, alias or catch-all here (or the mailbox is suspended).milter-reject— Rspamd refused the message as spam (score at or above the reject score, default 15, under Settings → Spam thresholds, and per domain or per mailbox under Spam on their pages, Per-domain and per-mailbox spam settings). Messages scoring at or above the greylist score (default 4) are deferred withTry again laterand accepted when the sender retries after Rspamd’s five-minute greylist timeout, so a delay of a few minutes on a first message is greylisting, not an outage.Connection rate limit exceeded— one sending IP opened more connections thansmtpdClientConnectionRateLimitallows (default 60 per hour, Settings → Sending limits; see Outbound rate limits). A large sender delivering from one address can hit it.
If Rspamd is down, Postfix accepts mail without scanning it (
milter_default_action = accept) rather than refusing it, so a stoppedrspamdmakes mail less filtered, not undelivered. -
Is Postfix handing the mail to Dovecot? Accepted mail goes to Dovecot over LMTP (
dovecot:24):Terminal window docker exec mailserver-postfix-1 grep 'relay=dovecot' /var/log/mail/postfix.log | tail -5docker exec mailserver-postfix-1 postqueue -p | tail -5status=sent (250 2.0.0 … Saved)is a delivered message.status=deferredwithrelay=dovecotmeans Postfix accepted the mail and Dovecot did not take it — the message waits in the queue (Mail queue in the console, Mail queue (viewer, hold / release / delete)) and is retried, so nothing is lost yet. Check Dovecot next:Terminal window docker ps --filter name=mailserver-dovecot-1docker exec mailserver-dovecot-1 doveadm user user@example.comdocker exec mailserver-dovecot-1 grep 'lmtp' /var/log/mail/dovecot.log | tail -20doveadm userprints the mailbox’shomeandmaillocation;doesn't existis the same missing row as step 4, and a lookup failure is Dovecot’s own database path (dovecot → postgresindoctor). -
Is the mailbox full?
Terminal window docker exec mailserver-dovecot-1 doveadm quota get -u user@example.comA mailbox at its limit defers new mail (
452 4.2.2, Dovecot’squota_full_tempfail = yes): it waits in this server’s queue (Mail queue, orpostqueue -pin thepostfixcontainer), and is bounced only after five days. Raise the quota on the mailbox’s page, or have the owner delete mail; Postfix delivers it at its next retry, within about an hour (its back-off tops out at 4000 s). The mailbox’s page shows its storage use, anddoveadm quota get -Alists every mailbox. -
Is the disk full? A full disk stops Postfix accepting mail and Dovecot storing it.
df -hon the host, then The disk is full.
Verification
Section titled “Verification”From an outside account, send a message to an address on the domain. It appears in the mailbox, and the Delivery log shows it as inbound, class delivered. mailctl doctor reports no ✖. Anything still deferred in the queue drains on Postfix’s next retry; release anything you put on hold.
Rollback
Section titled “Rollback”Diagnosis changes nothing. If you rolled the configuration back in step 3, the version you left is still in GET /api/v1/server/config/history and can be applied again once the reason it was refused is fixed.