odysseus-dev/odysseus · error · ValueError
Email account {cfg.get('account_name') or account_id} has no
Error message
Email account {cfg.get('account_name') or account_id} has no SMTP configured What it means
ValueError from _resolve_send_config when the caller explicitly passed account_id but that account's config fails _smtp_ready (no usable smtp_host/credentials). Explicit account choice is honored strictly — no fallback — so a receive-only account selected for sending fails immediately with the account's name in the message.
Source
Thrown at routes/email_routes.py:1304
def _smtp_ready(cfg: dict) -> bool:
if not cfg.get("smtp_host") or not cfg.get("smtp_user"):
return False
return bool(cfg.get("smtp_password") or cfg.get("oauth_provider"))
def _resolve_send_config(account_id: str | None = None, owner: str = "") -> dict:
"""Resolve an account for outbound SMTP.
If the caller explicitly picked an account, use only that account and
return a clear error when it cannot send. If no account was picked and
the default is receive-only, fall back to the first SMTP-capable account
owned by the same user.
"""
cfg = _get_email_config(account_id, owner=owner)
if _smtp_ready(cfg):
return cfg
if account_id:
raise ValueError(f"Email account {cfg.get('account_name') or account_id} has no SMTP configured")
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}")View on GitHub (pinned to f9235ebbf1)
Solutions
- Pick an account that has SMTP configured (smtp_host set, credentials or valid OAuth token).
- Complete SMTP settings on the chosen account, or connect it via Google OAuth with gmail.send.
- Alternatively omit account_id so the fallback can select the first SMTP-capable account owned by the caller.
Example fix
# before send_email(to, subject, body, account_id='inbox-only-acct') # ValueError # after send_email(to, subject, body) # or account_id of an SMTP-capable account
Defensive patterns
Strategy: validation
Validate before calling
const sendable = (await api.get('/api/emails/accounts')).filter(a => a.enabled && a.smtp_host);
if (account_id && !sendable.some(a => a.id === account_id)) throw new Error('chosen account cannot send'); Type guard
function canSend(a: { enabled: boolean; smtp_host?: string | null }): boolean {
return Boolean(a.enabled && a.smtp_host);
} Try / catch
try { await sendWith(account_id); } catch (e) { if (/has no SMTP configured/.test(e.message)) { pickSmtpAccount(); } else throw e; } Prevention
- Restrict the send-account picker to SMTP-capable accounts.
- Omit account_id to let the server fall back to the first capable account.
- Catch ValueError and surface the account name in the UI message.
When it happens
Trigger: POST a send/schedule/test-email endpoint with account_id of an IMAP-only (receive-only) account, or a Google OAuth account whose SMTP side is not ready (see 465-style token failure during readiness check).
Common situations: UI defaulting to the first account which is the receive-only inbox; OAuth account whose token refresh fails at _smtp_ready time; account saved with smtp_host empty.
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
- IMAP is not configured for account {cfg.get('account_name')
- No SMTP-capable email account configured
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/c23af288557f525a.
Report an issue: GitHub.