odysseus-dev/odysseus · warning · EmailNotConfiguredError
IMAP is not configured for account {cfg.get('account_name')
Error message
IMAP is not configured for account {cfg.get('account_name') or 'default'!r} What it means
EmailNotConfiguredError raised by the IMAP connection helper when the resolved account config has no imap_host. This is the typed guard for SMTP-only (send-only) accounts: without it imaplib would dial localhost:993 and fail with a confusing '[Errno 111] Connection refused'.
Source
Thrown at routes/email_helpers.py:1214
# "got more than 1000000 bytes" on UID SEARCH ALL. (#2883)
imaplib._MAXLINE = 50_000_000
return conn
def _imap_connect(account_id: str | None = None, owner: str = "",
timeout: int = _IMAP_TIMEOUT_SECONDS):
# SECURITY: passing `owner` scopes the fallback config lookup so a brand
# new user doesn't get connected against another user's default mailbox
# when they have no account configured.
#
# `timeout` is overridable so short-lived callers (e.g. the service-health
# probe) can impose a tighter budget than the default IMAP timeout.
cfg = _get_email_config(account_id, owner=owner)
# Send-only (SMTP-only) account: no IMAP host means there is no inbox to
# read. Bail out with a clear, typed error instead of handing an empty
# host to imaplib — IMAP4("", 993) silently dials localhost:993 and fails
# with a confusing "[Errno 111] Connection refused" on every inbox poll.
if not cfg.get("imap_host"):
raise EmailNotConfiguredError(
f"IMAP is not configured for account {cfg.get('account_name') or 'default'!r}"
)
# Connection mode:
# STARTTLS on → plain + upgrade
# STARTTLS off + port 993 → implicit SSL (IMAPS)
# STARTTLS off + any other port → plain (local Dovecot, custom ports)
# The last branch is critical: previously this fell into IMAP4_SSL
# for any non-STARTTLS port, which would fail the TLS handshake on
# plain local servers (Dovecot on 31143, etc.).
conn = _open_imap_connection(
cfg["imap_host"],
cfg["imap_port"],
starttls=bool(cfg.get("imap_starttls")),
timeout=timeout,
)
try:
if cfg.get("oauth_provider") == "google":
token = _get_valid_google_token(cfg.get("account_id"), cfg)View on GitHub (pinned to f9235ebbf1)
Solutions
- Fill in IMAP host/port for the account (e.g. imap.gmail.com:993) if it should receive mail.
- If send-only is intended, stop calling inbox endpoints with that account_id and filter send-only accounts out of poll loops.
- Catch EmailNotConfiguredError client-side and show a 'receive not configured' state instead of retrying.
Example fix
# before
for acct in all_enabled_accounts:
with _imap(acct.id) as c: ... # raises for send-only
# after
for acct in all_enabled_accounts:
if not account_cfg(acct.id).get('imap_host'):
continue
with _imap(acct.id) as c: ... Defensive patterns
Strategy: type-guard
Validate before calling
const cfg = await api.getAccount(account_id);
if (!cfg.imap_host) { markSendOnly(account_id); skipInboxPoll(account_id); } Type guard
function canReceive(cfg: { imap_host?: string | null }): boolean { return Boolean(cfg.imap_host); } Try / catch
try { with _imap(account_id, owner=owner) as c: ... }
except EmailNotConfiguredError as e:
log.info(f'skip inbox for send-only account: {e}') Prevention
- Filter send-only accounts out of pollers and inbox UI.
- Catch EmailNotConfiguredError distinctly from connection errors — it is config, not network.
- Complete IMAP fields before advertising an inbox for the account.
When it happens
Trigger: Opening an inbox/folder/poll on an account configured with only smtp_host (e.g. a transactional send account), passing account_id of such an account to any read endpoint, or a default account that was never given IMAP settings.
Common situations: User sets up Gmail SMTP-only for sending then clicks Inbox; scheduled poller iterating all enabled accounts including send-only ones; default account row with empty IMAP fields.
Related errors
- Email account {cfg.get('account_name') or account_id} has no
- HTTP ${res.status}
- data.error || 'failed'
- data.error
- Cleanup failed
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/47cbbf24bb0a6d62.
Report an issue: GitHub.