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, '&').replace(/</g, '<')}${_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
- Read the data.error string — it typically names the account/folder and IMAP reason
- Re-validate the email account credentials in settings (re-auth the account)
- Confirm the account_id query parameter still matches a configured account
- 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
- Surface data.error strings verbatim — they name the account/IMAP cause
- Keep account credentials fresh; hook 401/error flows to a re-auth prompt
- Use the cached_only probe (as done here) so cached views survive upstream failures
- Distinguish auth errors from provider outages in the rendered hint
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.