Troubleshooting¶
Three things sit between a reader and their mail, and almost every problem belongs to exactly one of them:
Two checks place most faults straight away:
curl -s http://127.0.0.1:8080/api/health # is ihasmail itself up?
docker logs --tail 50 ihasmail # what did it say when it failed?
If /api/health answers and the browser still cannot sign in, the fault is
between ihasmail and Stalwart, not in the browser.
The server will not start¶
APP_SECRET must be set to a strong random value in production-
Exactly what it says:
NODE_ENV=production— which the Docker image sets — andAPP_SECRETeither unset or stillchange-me. Generate one withopenssl rand -base64 48. [ihasmail] APP_SECRET not set - using an ephemeral secret-
A warning, not an error, and only outside production. Sessions are sealed with a key derived from that secret, so a new one on every restart means everybody is signed out on every restart.
Invalid integer for PORT: …-
A numeric variable —
PORT,SESSION_TTL,UPSTREAM_TIMEOUT,MAX_UPLOAD_BYTES,LOGIN_RATE_LIMIT— was given something that is not a number. Quotes and stray whitespace in a.envare the usual cause.
Nobody can sign in¶
- "Your credentials are fine, but this mail server is older than Stalwart 0.16"
-
ihasmail decides this by looking for Stalwart's
urn:stalwart:jmapcapability, which Stalwart advertises per account — inprimaryAccountsand each account'saccountCapabilities— and never in the session-levelcapabilities. So a genuine 0.16 server is still refused if something between ihasmail and Stalwart rewrites or trims the session response. Fetch<STALWART_URL>/.well-known/jmapas that user and look for the capability in those two places before concluding the server is old.If it really is 0.15, the last release that runs on it is tagged
stalwart-0.15-support. - "This mail server does not accept two-factor codes from webmail"
-
The password and code are almost certainly fine. Stalwart accepts a TOTP code only through an OAuth flow, and offers no password grant, so no client holding a username and password can pass one. Create an app password in Stalwart's own settings and sign in with that; app passwords bypass TOTP.
This is not a regression — the concatenated
password$codeform ihasmail once claimed to send is not a route the server has, and appears never to have been. - "Invalid username or password"
-
An ordinary rejection from Stalwart, with no code in the two-factor field. Worth ruling out an account backed by an external directory (LDAP, SQL, OIDC) whose password lives elsewhere.
- "Too many attempts. Please wait a few minutes and try again."
-
Ten failed sign-ins per fifteen-minute sliding window, counted per IP and per IP + username. It clears itself, and restarting the server clears it immediately — the counters are in memory.
If this fires for everyone at once, the more likely cause is everyone sharing one IP.
- "Could not reach the mail server" / "The mail server did not respond in time"
-
ihasmail could not get to Stalwart. Test from inside the container, since that is what has to reach it:
Check
STALWART_URL(scheme and host, no path), DNS inside the container, and whether a private-network address is reachable from the Docker network. A slow but working server wants a largerUPSTREAM_TIMEOUT.
Signing in works, then does not stick¶
- Back at the sign-in page immediately after signing in
-
The session cookie is being set
Secureand the page is not on HTTPS.SECURE_COOKIESdefaults toauto, which followsX-Forwarded-Proto— so this is normally a proxy that is not forwarding it, or is not trusted. For a deliberately plain-HTTP instance, setSECURE_COOKIES=0. - Everyone signed out after a restart or a redeploy
-
SESSION_FILEunset, or set to a path inside the container that is not on a volume. The image defaults it to/data/sessions.json, whichdocker-compose.ymlputs on the named volume. ChangingAPP_SECRETalso invalidates every session, by design — it is the fastest way to sign the world out on purpose.
Every visitor looks like the same address¶
Rate limiting keys on the client address, so behind a proxy that ihasmail does not trust, every visitor shares one key and ten failures anywhere lock out everyone.
TRUST_PROXY is on by default, but X-Forwarded-* is believed only from a peer
in TRUSTED_PROXIES — unset, that means loopback and the private ranges, which
covers a proxy on the same host or Docker network. A proxy somewhere else has to
be named there.
Not 0.0.0.0/0
That tells ihasmail to believe every client about who it is, which hands the rate limiter to anyone who wants to bypass it.
New mail does not appear until I reload¶
Live updates ride a Server-Sent Events connection, and a reverse proxy that buffers responses holds the events instead of passing them on. This is the single most common proxy mistake in front of ihasmail.
Without proxy_read_timeout the connection is cut after nginx's default 60
seconds; it reconnects, so updates arrive in bursts a minute apart rather than
never — which is the shape of the symptom when only that line is missing.
Notifications¶
- No Web Push option in Settings at all
-
Stalwart is not advertising
urn:ietf:params:jmap:webpush-vapidwith anapplicationServerKey. ihasmail hides the feature rather than offering something that cannot work. See Web Push needs VAPID. - Nothing arrives while ihasmail is closed
-
Expected, if the browser is also closed. Web Push is delivered over a connection the browser holds, so something of it has to be running — with the browser open and every ihasmail tab shut, notifications arrive immediately. With it fully quit and continue running background apps off, they queue until it starts again, and a push message that outlives its TTL is dropped rather than delivered late. Installing ihasmail as a PWA does not change this on a desktop.
- Notifications keep arriving for an account nobody is signed into
-
They should not: the subscription is torn down on sign-out, because it belongs to the account rather than the session. If it survives, the sign-out did not complete — closing the tab is not signing out.
Sending¶
- A scheduled message went out immediately
-
Stalwart's
futureReleaseis not set. It takes theHOLDUNTILparameter, skips the hold and sends at once without an error, while the account capability still advertises thirty days. The capability is no evidence either way; only a submission tells you. See Scheduled send needsfutureRelease. - A message is still in Scheduled after it was sent
-
The folder is ihasmail's own, and nothing in Stalwart moves messages out of it. It reconciles the next time ihasmail is opened — released messages to Sent, cancelled ones back to Drafts. The message itself went out on time regardless.
- No option to send a read receipt
-
Offered only when the sender asked for one, and never for bulk mail, mailing lists or anything marked
Auto-Submitted. If one was already sent, the$mdnsentkeyword stops a second look from offering another. - An attachment is refused
-
Three limits stack, and the smallest wins:
MAX_UPLOAD_BYTES(50 MiB by default), the reverse proxy's body limit —client_max_body_size 60min the example config — and Stalwart's own. A 413 from ihasmail is the first; a 413 with no ihasmail JSON body is usually the proxy.
Mail looks wrong¶
- Images do not load
-
Remote images are blocked until the reader allows that sender. With
IMAGE_PROXY=1(the default) an allowed image is fetched by your server, so it also fails if your server cannot reach the sender's host. - A message ignores the dark theme
-
Deliberate. Messages sit on a light card, untouched as the sender designed them. Appearance › Apply the theme to messages too changes that for plain-text mail and for HTML mail that brings no colours of its own; mail that styles itself is still left alone.
Settings and account¶
- Changing a password is refused
-
Stalwart refuses password changes for accounts backed by an external directory (LDAP, SQL, OIDC). ihasmail shows the server's own message; the password has to change wherever the directory keeps it.
- Settings did not follow me to another device
-
They live in the account's own JMAP Files, so they follow a sign-in. A device that already has ihasmail open keeps what it loaded until it signs in again, and conflicting writes are last-write-wins.
Some settings stay local on purpose and are meant not to follow: pane sizes, density, font size, sidebar state, and the notification toggles, which track a per-device browser permission.
- There is no
ihasmailfolder in Files -
Hidden on purpose, contents and all — it holds the settings file and signature images. Showing the folder is not the alternative to hiding it: the tree attaches a node whose parent is missing to the root, so hiding the folder alone would spill the signature images into the top level.
- Editing one occurrence of a recurring event changes the series
-
Per-occurrence overrides are not supported by the server yet, so colour, category, edits and deletion apply to the whole series. ihasmail does not offer per-occurrence editing rather than pretending to.
Still stuck¶
Worth having to hand before opening an issue:
- the version, from
/api/healthor Settings › About - what Settings › About reports as the detected Stalwart generation
docker logsaround the failure- whether the same thing happens against the mock (
npm run dev:mock), which rules Stalwart in or out
Issues go to the application repository.