#3580·inbox-zero

Outlook: History screen shows 'Email unavailable' — getMessagesBatch fires unthrottled parallel Graph calls, gets ErrorAccessDenied

Author: msoukhomlinovCreated Sep 7, 2026Updated Sep 7, 2026

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:

javascript
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 $batch endpoint (POST /$batch) for getMessagesBatch() instead of N parallel single-item GETs, or
  • Add a concurrency limit (e.g. p-limit) around the Promise.all, and/or
  • Treat ErrorAccessDenied from Graph as retryable-with-backoff rather than immediately failing, and
  • Investigate whether the server-side EmailMessage cache write path is still expected to run.

Environment

  • Self-hosted, image elie222/inbox-zero:1ba3c68
  • Provider: Microsoft Outlook / Graph API