odysseus-dev/odysseus · error · ValueError
No SMTP-capable email account configured
Error message
No SMTP-capable email account configured
What it means
ValueError raised at the end of _resolve_send_config: no explicit account was given, the default account is not SMTP-ready, and the fallback scan over all enabled accounts owned by the caller (including legacy ownerless rows matching their mailbox) found none that passes _smtp_ready. The intermediate per-account errors are only logged at DEBUG.
Source
Thrown at routes/email_routes.py:1323
try:
from core.database import SessionLocal as _SL, EmailAccount as _EA
from sqlalchemy import and_, or_
db = _SL()
try:
q = db.query(_EA).filter(_EA.enabled == True) # noqa: E712
if owner:
unowned = or_(_EA.owner == None, _EA.owner == "") # noqa: E711
same_mailbox = or_(_EA.imap_user == owner, _EA.from_address == owner)
q = q.filter(or_(_EA.owner == owner, and_(unowned, same_mailbox)))
for row in q.order_by(_EA.is_default.desc(), _EA.created_at.asc()).all():
trial = _get_email_config(account_id=row.id, owner=owner)
if _smtp_ready(trial):
return trial
finally:
db.close()
except Exception as e:
logger.debug(f"SMTP-capable account fallback failed: {e}")
raise ValueError("No SMTP-capable email account configured")
def _store_email_flag(conn, uid: str, flag: str, add: bool = True) -> bool:
# imaplib's plain store() takes a message SEQUENCE NUMBER, not a UID, so the
# old `else` fallback flagged whichever message happened to occupy sequence
# position == the UID value. When the UID isn't present, fail safe (callers
# surface "Email not found") rather than touch an unrelated message.
if not _uid_exists(conn, uid):
return False
op = "+FLAGS" if add else "-FLAGS"
status, _ = conn.uid("STORE", _uid_bytes(uid), op, flag)
return status == "OK"
def _move_email_message(conn, uid: str, dest: str, role: str = "") -> bool:
dest = _resolve_mail_folder(conn, dest, role or _folder_role_from_name(dest))
# copy()/store() are SEQUENCE-NUMBER commands; using them with a UID (the old
# `else` branch) copied + \Deleted-flagged the wrong message and thenView on GitHub (pinned to f9235ebbf1)
Solutions
- Add and enable at least one account with SMTP host + credentials (or Google OAuth with send scope).
- Check server DEBUG logs for 'SMTP-capable account fallback failed' to see why each candidate was rejected.
- In the UI, block compose/send until _resolve_send_config could succeed (at least one enabled SMTP account).
Example fix
# before
await send_email(to=..., subject=..., body=...) # no accounts -> ValueError
# after
if not any(a.enabled and a.smtp_host for a in accounts):
raise UserVisibleError('Configure an outgoing email account first')
await send_email(to=..., subject=..., body=...) Defensive patterns
Strategy: validation
Validate before calling
const capable = (await api.get('/api/emails/accounts')).filter(a => a.enabled && a.smtp_host);
if (capable.length === 0) { promptAccountSetup(); return; } Type guard
function hasSmtpAccount(accts: Array<{enabled: boolean; smtp_host?: string | null}>): boolean {
return accts.some(a => a.enabled && a.smtp_host);
} Try / catch
try { await sendEmail(...); } catch (e) { if (/No SMTP-capable email account/.test(e.message)) { promptAccountSetup(); return; } throw e; } Prevention
- Block compose/send in the UI until at least one enabled SMTP account exists.
- Enable DEBUG logs to see why each fallback candidate was rejected.
- After auto-discover, verify smtp_host was populated, not just imap_host.
When it happens
Trigger: Calling send endpoints on a fresh install with zero accounts; all accounts receive-only; every SMTP-capable account disabled (enabled=False); Google accounts whose token refresh fails during the trial loop.
Common situations: First-run send attempt before any account setup; account created via auto-discover that populated IMAP but left SMTP blank; all accounts toggled off but UI still allows compose.
Related errors
- Email account {cfg.get('account_name') or account} has no SM
- No SMTP-capable email account configured
- Email account {cfg.get('account_name') or account or 'defaul
- Email account {cfg.get('account_name') or account_id} has no
- Unsubscribe failed
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/0ab676daa4a5ce02.
Report an issue: GitHub.