Outlook: History screen shows 'Email unavailable' — getMessagesBatch fires unthrottled parallel Graph calls, gets ErrorAccessDenied
Description
The AI Assistant > History screen shows "Email unavailable" for most entries on an Outlook-connected account. Actions applied by rules (labels etc.) are correct — only the original-email fetch for display is failing.
Root cause (traced via docker logs + source inspection)
GET /api/messages/batch (called by the History screen) resolves to getMessagesBatch() in the Outlook provider, which does no real batching or concurrency limiting:
ids.map(id => this.getMessage(id))wrapped in Promise.all. In one History page load this fires 47 fully-parallel individual GET /me/messages/{id} requests against Microsoft Graph.
Graph responds to the burst with the same error on every id in the batch — including a message received 10 minutes prior:
"error": "Access is denied. Check credentials and try again., The store ID provided isn't an ID of an item.", "code": "ErrorAccessDenied"This looks like Microsoft Graph's per-mailbox concurrent-request throttling being hit, and the retry wrapper classifies ErrorAccessDenied as non-retryable, so it gives up immediately and the UI shows "Email unavailable" for the whole batch.
Confirmed NOT caused by:
- Token/permission issue — the account's OAuth token is valid and non-expired; delta sync and rule execution are running fine concurrently.
- Env var / config drift — no relevant setting exists to control batch concurrency.
Secondary contributing factor: the server-side EmailMessage content cache table appears to have stopped being written to (no new rows in ~a month), likely superseded by the newer client-side cache work (#3210, #3388). With no server cache to serve from, History now falls back to live-fetching far more messages per page load than before, making the throttling much more likely to trigger.
Expected behavior
History should reliably display original email content for recently-processed messages on an Outlook account.
Suggested fix
- Use Microsoft Graph's real
$batchendpoint (POST /$batch) forgetMessagesBatch()instead of N parallel single-item GETs, or - Add a concurrency limit (e.g. p-limit) around the
Promise.all, and/or - Treat
ErrorAccessDeniedfrom Graph as retryable-with-backoff rather than immediately failing, and - Investigate whether the server-side
EmailMessagecache write path is still expected to run.
Environment
- Self-hosted, image
elie222/inbox-zero:1ba3c68 - Provider: Microsoft Outlook / Graph API
Source: elie222/inbox-zero