odysseus-dev/odysseus · error · ValueError

Email account {cfg.get('account_name') or account} has no SM

Error message

Email account {cfg.get('account_name') or account} has no SMTP configured

What it means

Raised by _resolve_send_config when the caller explicitly names an account but _smtp_ready(cfg) is false — the loaded config is missing smtp_host, smtp_user, or smtp_password. Sending requires SMTP credentials even though IMAP reading may already work.

Source

Thrown at mcp_servers/email_server.py:1321

        return {
            "error": (
                f"UID {uid or message_id} exists in multiple accounts: {accounts}. "
                "Call read_email again with the account name/email."
            )
        }
    return {"error": f"Email not found in any configured account. Checked: {'; '.join(errors)}"}


def _smtp_ready(cfg: dict) -> bool:
    return bool(cfg.get("smtp_host") and cfg.get("smtp_user") and cfg.get("smtp_password"))


def _resolve_send_config(account=None):
    cfg = _load_config(account)
    if _smtp_ready(cfg):
        return account, cfg
    if account:
        raise ValueError(f"Email account {cfg.get('account_name') or account} has no SMTP configured")
    for row in _list_accounts_raw():
        selector = row.get("id") or row.get("name") or row.get("imap_user")
        trial = _load_config(selector)
        if _smtp_ready(trial):
            return selector, trial
    raise ValueError("No SMTP-capable email account configured")


def _smtp_connect(account=None, cfg=None):
    """Connect to SMTP server, returns logged-in connection."""
    cfg = cfg or _load_config(account)
    if not _smtp_ready(cfg):
        raise ValueError(f"Email account {cfg.get('account_name') or account or 'default'} has no SMTP configured")
    port = int(cfg.get("smtp_port") or 465)
    security = str(cfg.get("smtp_security") or "").strip().lower()
    if security not in {"ssl", "starttls", "none"}:
        security = "starttls" if port == 587 else "ssl"
    if security == "starttls":

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Set smtp_host, smtp_port, smtp_user, smtp_password (and smtp_security) on that account's row/config
  2. Verify the SMTP password is stored encrypted via src.secret_storage and decrypts correctly
  3. If you meant to send from any account, omit the account argument so the fallback scan finds an SMTP-capable one

Example fix

# before
send_email(account='imap-only-acct', ...)
# after — configure SMTP on the account, or drop the explicit selector
send_email(..., account='main-account-with-smtp')
Defensive patterns

Strategy: type-guard

Validate before calling

if not _smtp_ready(_load_config(account)):
    raise ValueError(f'{account} cannot send; configure SMTP or drop the selector')

Type guard

def can_send(account: str | None) -> bool:
    return _smtp_ready(_load_config(account))

Try / catch

try:
    acct, cfg = _resolve_send_config(account)
except ValueError as e:
    if 'no SMTP configured' in str(e):
        notify_admin_to_configure_smtp(e)
    raise

Prevention

When it happens

Trigger: Calling a send/draft tool with account=<selector> where that account's row (and env/config fallbacks) lacks any SMTP field — e.g. an account configured for IMAP-only reading.

Common situations: Read-only mail accounts (import/scan setups) reused for sending; SMTP password stored encrypted but the smtp_host column empty; provider requires app-specific SMTP password that was never set.

Related errors


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