odysseus-dev/odysseus · error · Error

data.error

Error message

data.error

What it means

Thrown after GET /api/email/list when the endpoint answers 200 (or any parseable JSON) with an error field — the server reports a mailbox-level failure such as IMAP connect failure or missing account config inside the body. The catch renders 'Failed to load: <message>' into the inbox list.

Source

Thrown at static/js/emailInbox.js:424

      _total = data.total || 0;
      if (_listSpinner) { _listSpinner.destroy(); _listSpinner = null; }
      _renderList();
      const unreadCount = _emails.filter(e => !e.is_read).length;
      const dot = document.getElementById('email-unread-dot');
      if (dot) dot.style.display = unreadCount > 0 ? '' : 'none';
    };
    if (!append && !_senderFilter) {
      try {
        const cachedRes = await fetch(`${API_BASE}/api/email/list?folder=${encodeURIComponent(_currentFolder)}&limit=50&offset=${_offset}&cached_only=1${_acct()}`);
        const cachedData = await cachedRes.json();
        if (!cachedData.error && (cachedData.emails || []).length) {
          applyListData(cachedData);
        }
      } catch (_) {}
    }
    const res = await fetch(`${API_BASE}/api/email/list?folder=${encodeURIComponent(_currentFolder)}&limit=50&offset=${_offset}${fromQS}${_acct()}`);
    const data = await res.json();
    if (data.error) throw new Error(data.error);
    applyListData(data);
  } catch (e) {
    console.error('Failed to load emails:', e);
    if (_listSpinner) { _listSpinner.destroy(); _listSpinner = null; }
    if (!append && list) {
      const msg = e && e.message ? `Failed to load: ${e.message}` : 'Failed to load';
      list.innerHTML = `<div class="email-loading">${msg.replace(/&/g, '&amp;').replace(/</g, '&lt;')}${_emailSetupHint()}</div>`;
    }
  } finally {
    _loading = false;
  }
}

async function loadFolders() {
  try {
    const accountQS = _acct().replace(/^&/, '');
    const res = await fetch(`${API_BASE}/api/email/folders${accountQS ? `?${accountQS}` : ''}`);
    const data = await res.json();

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Read the data.error string — it typically names the account/folder and IMAP reason
  2. Re-validate the email account credentials in settings (re-auth the account)
  3. Confirm the account_id query parameter still matches a configured account
  4. Retry after the provider outage/timeout clears; use Refresh rather than reopening to force the cache-busted call
Defensive patterns

Strategy: try-catch

Type guard

function isEmailListOk(d) { return d != null && typeof d === 'object' && !d.error && Array.isArray(d.emails); }

Try / catch

try { const data = await res.json(); if (data.error) throw new Error(data.error); applyListData(data); } catch (e) { list.innerHTML = `<div class="email-loading">Failed to load: ${esc(e.message)}</div>` + setupHint(e); }

Prevention

When it happens

Trigger: Opening a folder whose IMAP connection fails (bad password, expired OAuth token, provider outage), requesting an account_id that is not set up, or folder name the server cannot select — all returned as {error: ...} rather than a non-2xx status. Note the earlier cached_only probe is intentionally swallowed, so only the authoritative call surfaces errors.

Common situations: Gmail app-password revoked; OAuth refresh token expired; account removed from settings while the inbox tab stayed open; server-side IMAP timeout during first sync of a huge folder.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/47f4e04e4edd9ce1. Report an issue: GitHub.