Troubleshooting
The failures that have actually happened, and the smallest thing that fixes each one.
Mail isn't arriving
- Is port 25 reachable? From outside your network:
telnet mail.yourdomain.com 25should show the Postfix banner. If it hangs, your ISP or host is blocking inbound port 25 — you can't receive directly. - Is the MX right?
dig MX yourdomain.comshould point at yourmailhostname, and that A record must be DNS-only (grey cloud), not proxied. - Watch the conversation:
docker compose logs -f mtashows every SMTP session live.
Log lines worth knowing:
NOQUEUE: reject: RCPT ... Relay access denied— the recipient isn't inMAIL_ACCOUNTS.status=bouncedwith a DNS error — the container can't resolve; check the host's DNS.
Mail goes to spam / gets rejected
- Check the triad: SPF, DKIM and DMARC all published and valid? Use the admin DNS checker or an external validator (e.g. mail-tester.com).
- PTR record: no reverse DNS on your IP is the most common reason Gmail distrusts a new server.
- Home IP? Stop delivering directly and relay through Mailjet — Sending: direct or relayed.
- New domain? Fresh domains have no reputation. Warm up with real correspondence before any volume.
A container is restart-looping
The app containers run database migrations on every boot. A failing migration restart-loops the container:
docker compose logs php | tail -40 # or worker / scheduler / imap-supervisor
Look for the migration error at the top of the loop. Common cause: a non-idempotent migration replayed against a partially-migrated database. Fix the migration to be idempotent (CREATE TABLE IF NOT EXISTS, guarded ALTERs), then rebuild all four app services — they share one image:
docker compose --profile mail up -d --build
All app services share the plmail-fork:latest image. Rebuilding only php leaves the others on the old image with the old bug.
Relay stopped working
- Check Admin → Mail server: is the relay still configured, or did someone clear the host field (which reverts to direct delivery)?
- Remember:
MAIL_RELAY*env vars override the UI. If you set them in the shell and forgot, the UI can't change them. - Mailjet secret rotated? Update it in the same place you set it.
Android app won't pair / sync
- Auth proxy in front? The app needs a direct path to the server — bypass forward-auth (Authentik etc.) for the mail hostname.
- "Scan the code" closes the app or says the camera could not be started: update to 0.0.33 or later (the scanner uses ZXing instead of ML Kit). Meanwhile use Enter the address instead. Check that Android's Camera access quick-settings toggle is on.
- No update offered: the app checks at most once an hour. Use Drawer → Check for updates, or fully close and reopen the app.
- Signed out after reinstalling: expected. Updates keep the login; uninstalling or Clear storage removes it. Pair again.
- App closed unexpectedly: reopen it, tap Copy on the crash report and include it in a GitHub issue.
- Release workflow fails with
No key with alias … found in keystore: theKEY_ALIASsecret must contain only the alias word (check withkeytool -list), then re-run the Release workflow. - Updates failing to install: every APK must be signed with the same key. If you installed a debug build or a differently-signed APK, uninstall first — Android won't let a mismatched signature update in place.
Share links don't work
- Check the link hasn't expired — expiries are enforced server-side.
- If the share page loads but download fails, check the php container's logs and that
/app/var/uploads/driveis writable and has disk space.
Still stuck
Collect docker compose logs --tail=100 <service> for the failing service and open an issue on the relevant repo — server or android. Say what you expected, what happened, and what the logs show.