Imap/submission via app password leads to auth.failed after random time until cache invalidation

Issue Description

Despite a successful authentication via app password on MacOS mail after some timeout (?) in the range of several days – sometimes hours – the app password is suddenly no longer valid.
A stalwart-cli create action/InvalidateCaches is required to make the same app password work again for the next few days.

Expected Behavior

The app password should only expire after the time, that is set as expiry date on the app password.

Reproduction Steps

  1. Log on with app password with an imap client.
  2. Wait several days. Password is suddenly no longer valid.
  3. Create a stalwart-cli create action/InvalidateCaches action
  4. App password is valid again

Relevant Log Output

2026-10-08T16:09:25Z INFO SMTP EHLO command (smtp.ehlo) listenerId = "submissions", localPort = 465, remoteIp = 91.xx.yy.zz, remotePort = 51901, domain = "smtpclient.apple"
2026-10-08T16:09:25Z DEBUG IMAP ID command (imap.id) listenerId = "imaps", localPort = 993, remoteIp = 91.xx.yy.zz, remotePort = 51908, elapsed = 0ms
2026-10-08T16:09:25Z DEBUG Cache hit (store.cache-hit) key = "my-domain-redacted.priv", collection = "domainName"
2026-10-08T16:09:25Z DEBUG Cache hit (store.cache-hit) key = 2, collection = "domainId"
2026-10-08T16:09:25Z DEBUG Cache miss (store.cache-miss) key = "post", domain = 2, collection = "email"
2026-10-08T16:09:25Z DEBUG Cache hit (store.cache-hit) key = "my-domain-redacted.priv", collection = "domainName"
2026-10-08T16:09:25Z DEBUG Cache hit (store.cache-hit) key = 2, collection = "domainId"
2026-10-08T16:09:25Z DEBUG Cache hit (store.cache-hit) key = "post", domain = 2, collection = "emailNegative"
2026-10-08T16:09:25Z DEBUG Authentication failed (auth.failed) listenerId = "submissions", localPort = 465, remoteIp = 91.xx.yy.zz, remotePort = 51901, accountName = "[email protected]", reason = "App password authentication failed: account not found", remoteIp = 91.xx.yy.zz
2026-10-08T16:09:26Z DEBUG Authentication failed (auth.failed) listenerId = "imaps", localPort = 993, remoteIp = 91.xx.yy.zz, remotePort = 51908, accountName = "[email protected]", reason = "App password authentication failed: account not found", remoteIp = 91.xx.yy.zz, id = "2.1894"
2026-10-08T16:09:26Z DEBUG Cache hit (store.cache-hit) key = "my-domain-redacted.priv", collection = "domainName"
2026-10-08T16:09:26Z DEBUG Cache hit (store.cache-hit) key = 2, collection = "domainId"
2026-10-08T16:09:26Z DEBUG Cache hit (store.cache-hit) key = "post", domain = 2, collection = "emailNegative"
2026-10-08T16:09:26Z DEBUG Authentication failed (auth.failed) listenerId = "imaps", localPort = 993, remoteIp = 91.xx.yy.zz, remotePort = 51908, accountName = "[email protected]", reason = "App password authentication failed: account not found", remoteIp = 91.xx.yy.zz, id = "3.1894"
2026-10-08T16:09:26Z DEBUG IMAP LOGOUT command (imap.logout) listenerId = "imaps", localPort = 993, remoteIp = 91.xx.yy.zz, remotePort = 51908, elapsed = 0ms
2026-10-08T16:09:26Z DEBUG IMAP connection ended (imap.connection-end) listenerId = "imaps", localPort = 993, remoteIp = 91.xx.yy.zz, remotePort = 51908, elapsed = 1040ms
2026-10-08T16:09:27Z DEBUG HTTP connection started (http.connection-start) listenerId = "https", localPort = 443, remoteIp = 77.90.2.164, remotePort = 50244
2026-10-08T16:09:27Z DEBUG HTTP request URL (http.request-url) listenerId = "https", localPort = 443, remoteIp = 77.90.2.164, remotePort = 50244, url = "/account/"
2026-10-08T16:09:27Z DEBUG HTTP connection ended (http.connection-end) listenerId = "https", localPort = 443, remoteIp = 77.90.2.164, remotePort = 50244, elapsed = 13ms
2026-10-08T16:09:30Z DEBUG Cache hit (store.cache-hit) key = "my-domain-redacted.priv", collection = "domainName"
2026-10-08T16:09:30Z DEBUG Cache hit (store.cache-hit) key = 2, collection = "domainId"
2026-10-08T16:09:30Z DEBUG Cache hit (store.cache-hit) key = "post", domain = 2, collection = "emailNegative"
2026-10-08T16:09:31Z DEBUG Authentication failed (auth.failed) listenerId = "submissions", localPort = 465, remoteIp = 91.xx.yy.zz, remotePort = 51901, accountName = "[email protected]", reason = "App password authentication failed: account not found", remoteIp = 91.xx.yy.zz
2026-10-08T16:09:36Z DEBUG SMTP QUIT command (smtp.quit) listenerId = "submissions", localPort = 465, remoteIp = 91.xx.yy.zz, remotePort = 51901
2026-10-08T16:09:36Z DEBUG SMTP connection ended (smtp.connection-end) listenerId = "submissions", localPort = 465, remoteIp = 91.xx.yy.zz, remotePort = 51901, elapsed = 10761ms

Account is no longer found. After invalidating the caches, everything is fine again:

2026-10-08T16:09:48Z DEBUG SMTP connection started (smtp.connection-start) listenerId = "submissions", localPort = 465, remoteIp = 91.xx.yy.zz, remotePort = 51918
2026-10-08T16:09:48Z DEBUG IMAP connection started (imap.connection-start) listenerId = "imaps", localPort = 993, remoteIp = 91.xx.yy.zz, remotePort = 51922
2026-10-08T16:09:48Z INFO SMTP EHLO command (smtp.ehlo) listenerId = "submissions", localPort = 465, remoteIp = 91.xx.yy.zz, remotePort = 51918, domain = "smtpclient.apple"
2026-10-08T16:09:48Z DEBUG IMAP ID command (imap.id) listenerId = "imaps", localPort = 993, remoteIp = 91.xx.yy.zz, remotePort = 51922, elapsed = 0ms
2026-10-08T16:09:48Z DEBUG Cache miss (store.cache-miss) key = 1, collection = "domainId"
2026-10-08T16:09:48Z DEBUG Cache miss (store.cache-miss) key = "post", domain = 1, collection = "email"
2026-10-08T16:09:48Z DEBUG Cache miss (store.cache-miss) key = 2, collection = "account"
2026-10-08T16:09:48Z DEBUG Cache hit (store.cache-hit) key = 1, collection = "domainId"
2026-10-08T16:09:48Z DEBUG Cache hit (store.cache-hit) key = "my-domain-redacted.priv", collection = "domainName"
2026-10-08T16:09:48Z DEBUG Cache hit (store.cache-hit) key = 1, collection = "domainId"
2026-10-08T16:09:48Z DEBUG Cache hit (store.cache-hit) key = "post", domain = 1, collection = "email"
2026-10-08T16:09:48Z INFO Authentication successful (auth.success) listenerId = "submissions", localPort = 465, remoteIp = 91.xx.yy.zz, remotePort = 51918, accountName = "post", accountId = 2, id = 5, details = "Authenticated with app password"
2026-10-08T16:09:48Z DEBUG Cache miss (store.cache-miss) key = 2, collection = "accessToken"
2026-10-08T16:09:48Z DEBUG Cache miss (store.cache-miss) key = 1, collection = "role"
2026-10-08T16:09:48Z DEBUG Cache hit (store.cache-hit) key = 2, collection = "account"
2026-10-08T16:09:48Z DEBUG Cache hit (store.cache-hit) key = 1, collection = "domainId"
2026-10-08T16:09:48Z DEBUG Cache miss (store.cache-miss) key = 2, collection = "domainId"
2026-10-08T16:09:48Z INFO Authentication successful (auth.success) listenerId = "imaps", localPort = 993, remoteIp = 91.xx.yy.zz, remotePort = 51922, accountName = "post", accountId = 2, id = 3, details = "Authenticated with app password"
2026-10-08T16:09:48Z DEBUG Cache hit (store.cache-hit) key = 2, collection = "accessToken"
2026-10-08T16:09:48Z DEBUG Cache update (store.cache-update) accountId = 2, collection = "email", changeId = 7982, details = [1, 0], total = [114, 6], elapsed = 0ms
2026-10-08T16:09:48Z DEBUG SMTP QUIT command (smtp.quit) listenerId = "submissions", localPort = 465, remoteIp = 91.xx.yy.zz, remotePort = 51918
2026-10-08T16:09:48Z DEBUG SMTP connection ended (smtp.connection-end) listenerId = "submissions", localPort = 465, remoteIp = 91.xx.yy.zz, remotePort = 51918, elapsed = 447ms
2026-10-08T16:09:48Z DEBUG IMAP ENABLE command (imap.enable) listenerId = "imaps", localPort = 993, remoteIp = 91.xx.yy.zz, remotePort = 51922, details = ["QResync"], elapsed = 0ms
2026-10-08T16:09:48Z DEBUG IMAP LOGOUT command (imap.logout) listenerId = "imaps", localPort = 993, remoteIp = 91.xx.yy.zz, remotePort = 51922, elapsed = 0ms
2026-10-08T16:09:48Z DEBUG IMAP connection ended (imap.connection-end) listenerId = "imaps", localPort = 993, remoteIp = 91.xx.yy.zz, remotePort = 51922, elapsed = 526ms

Stalwart Version

v0.16.x

Installation Method

Docker

Database Backend

RocksDB

Blob Storage

Filesystem

Search Engine

Internal

Directory Backend

Internal

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

Based on the logs and the cache code, this isn’t a password-expiry issue: it’s a stale domain-ID cache entry. Note that before invalidating, the domainName -> domainId cache mapping resolved your domain to domainId = 2, and under that (wrong) domain ID the post mailbox doesn’t exist, so lookups fall into the emailNegative cache and every login fails with “account not found.” After running InvalidateCaches, the mapping is rebuilt and resolves to domainId = 1, which is where your account actually lives, so logins succeed again.

The key detail is that Stalwart’s in-memory caches split into two kinds:

  • Negative caches (domainNamesNegative, emailAddressesNegative) expire automatically after negativeTtl (default 1 hour), configured on the Cache object.
  • Positive caches (domains, domainNames, emailAddresses, accounts, etc.) have no TTL at all. They’re only refreshed when the server sends an explicit cache-invalidation event (e.g. after a domain/account is modified). If, for any reason, the domain record’s underlying ID changes without that invalidation reaching all caches, the stale domainName -> domainId mapping just sits there indefinitely, since nothing ever expires it, which matches exactly what you’re seeing (“random” multi-day persistence until a manual full flush).

So the app password itself isn’t expiring; the server is resolving your domain to the wrong (stale) internal ID. That points to something regenerating/recreating the domain object’s ID (directory sync, restore, re-import, etc.) without the corresponding cache-invalidation broadcast firing for all affected cache entries.

Given the specifics involved (why the domain ID is changing and why its invalidation isn’t reaching the domainNames positive cache), this needs a closer look at your setup/logs by the team.

This isn’t covered in the current docs. A community member or staff member will follow up.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

For future guests: This error might occur, if you have two domains A and B, where domain B is a alias of domain A – but the user in domain A behind the token has also an alias to domain B