{"record":{"id":"21c0266acc4d8ff1","repo":"odysseus-dev/odysseus","slug":"email-account-cfg-get-account-name-or-account-21c026","errorCode":null,"errorMessage":"Email account {cfg.get('account_name') or account or 'default'} has no SMTP configured","messagePattern":"Email account (.+?) has no SMTP configured","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"mcp_servers/email_server.py","lineNumber":1334,"sourceCode":"def _resolve_send_config(account=None):\n    cfg = _load_config(account)\n    if _smtp_ready(cfg):\n        return account, cfg\n    if account:\n        raise ValueError(f\"Email account {cfg.get('account_name') or account} has no SMTP configured\")\n    for row in _list_accounts_raw():\n        selector = row.get(\"id\") or row.get(\"name\") or row.get(\"imap_user\")\n        trial = _load_config(selector)\n        if _smtp_ready(trial):\n            return selector, trial\n    raise ValueError(\"No SMTP-capable email account configured\")\n\n\ndef _smtp_connect(account=None, cfg=None):\n    \"\"\"Connect to SMTP server, returns logged-in connection.\"\"\"\n    cfg = cfg or _load_config(account)\n    if not _smtp_ready(cfg):\n        raise ValueError(f\"Email account {cfg.get('account_name') or account or 'default'} has no SMTP configured\")\n    port = int(cfg.get(\"smtp_port\") or 465)\n    security = str(cfg.get(\"smtp_security\") or \"\").strip().lower()\n    if security not in {\"ssl\", \"starttls\", \"none\"}:\n        security = \"starttls\" if port == 587 else \"ssl\"\n    if security == \"starttls\":\n        conn = smtplib.SMTP(\n            cfg[\"smtp_host\"],\n            port,\n            timeout=EMAIL_SOCKET_TIMEOUT,\n        )\n        try:\n            conn.starttls()\n        except Exception:\n            # Don't leak the open plain socket on a rejected STARTTLS. SMTP has\n            # no shutdown(); close() is the low-level socket close (no QUIT). (#3174)\n            try:\n                conn.close()\n            except Exception:","sourceCodeStart":1316,"sourceCodeEnd":1352,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/mcp_servers/email_server.py#L1316-L1352","documentation":"Raised by _smtp_connect when the cfg it received (loaded from the account argument or the default config) fails the same _smtp_ready() check. It is the low-level guard hit after _resolve_send_config already returned a cfg — typically when callers pass cfg directly.","triggerScenarios":"_smtp_connect(account, cfg) invoked with a cfg dict missing smtp_host/smtp_user/smtp_password, or _smtp_connect(account) where that account has no SMTP fields.","commonSituations":"Code paths that build a partial cfg from IMAP-only data and forward it to the send path; account rows where the encrypted SMTP password failed to decrypt into smtp_password.","solutions":["Ensure the cfg passed to _smtp_connect comes from _resolve_send_config (which validates and falls back), not a hand-built dict","Fill in the missing SMTP fields on the account/config","Check that SMTP password decryption succeeds — an empty decrypted value leaves smtp_password falsy"],"exampleFix":"# before\nconn = _smtp_connect(account, cfg=imap_only_cfg)\n# after\naccount, cfg = _resolve_send_config(account)\nconn = _smtp_connect(account, cfg=cfg)","handlingStrategy":"type-guard","validationCode":"if cfg is None or not _smtp_ready(cfg):\n    _, cfg = _resolve_send_config(account)","typeGuard":"def smtp_cfg_ready(cfg: dict) -> bool:\n    return bool(cfg) and _smtp_ready(cfg)","tryCatchPattern":"try:\n    _smtp_connect(account, cfg)\nexcept ValueError as e:\n    if 'no SMTP configured' in str(e):\n        account, cfg = _resolve_send_config(account)\n        _smtp_connect(account, cfg)\n    raise","preventionTips":["Always obtain send cfg from _resolve_send_config, never hand-build it","Assert _smtp_ready(cfg) before connecting","Test with a smoke-send after config changes"],"tags":["email","smtp","configuration"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}