datawhalechina/hello-agents · error · ValueError

未配置邮箱信息

Error message

未配置邮箱信息

What it means

Raised by _fetch_via_imap() in the EmailDigestAgent notebook when _read_imap_config() returns an empty username or password. _read_imap_config() resolves credentials from explicit arguments, then an 'imap' section of a config file, falling back to empty strings (username="", password="") — it never raises for missing config, so the blank credentials surface here as ValueError('未配置邮箱信息'). It is a configuration error, not an IMAP protocol failure.

Source

Thrown at Co-creation-projects/Johnx-w-EmailDigestAgent/main.ipynb:288

    "        username = os.getenv(\"IMAP_USERNAME\") or \"\"\n",
    "        password = os.getenv(\"IMAP_PASSWORD\") or \"\"\n",
    "        if not (username and password and server):\n",
    "            try:\n",
    "                with open(\"config/email_config.json\", \"r\", encoding=\"utf-8\") as f:\n",
    "                    cfg = json.load(f)\n",
    "                imap_cfg = cfg.get(\"imap\", {})\n",
    "                server = server or imap_cfg.get(\"server\", \"imap.qq.com\")\n",
    "                port = port or imap_cfg.get(\"port\", 993)\n",
    "                username = username or imap_cfg.get(\"username\", \"\")\n",
    "                password = password or imap_cfg.get(\"password\", \"\")\n",
    "            except FileNotFoundError:\n",
    "                pass\n",
    "        return server or \"imap.qq.com\", port or 993, username, password\n",
    "\n",
    "    def _fetch_via_imap(self, max_emails: int = 50, hours: int = 24) -> list:\n",
    "        server, port, username, password = self._read_imap_config()\n",
    "        if not username or not password:\n",
    "            raise ValueError(\"未配置邮箱信息\")\n",
    "\n",
    "        console.print(f\"[dim]  连接 {server}:{port}  用户: {username}...[/dim]\")\n",
    "        mail = imaplib.IMAP4_SSL(server, port)\n",
    "        mail.login(username, password)\n",
    "\n",
    "        # 选择收件箱 — 依次尝试: 无引号, 带引号, readonly\n",
    "        select_status = None\n",
    "        for name, rdonly in [(\"INBOX\", False), ('\"INBOX\"', False), (\"INBOX\", True)]:\n",
    "            select_status, select_data = mail.select(name, readonly=rdonly)\n",
    "            console.print(f\"[dim]  select({name!r}, readonly={rdonly}) -> {select_status!r}[/dim]\")\n",
    "            if select_status == \"OK\":\n",
    "                break\n",
    "        if select_status != \"OK\":\n",
    "            raise ValueError(f\"无法选择 INBOX (状态: {select_status!r})\")\n",
    "\n",
    "        since_date = (datetime.now() - timedelta(hours=hours)).strftime(\"%d-%b-%Y\")\n",
    "        search_criteria = f'(UNSEEN SINCE {since_date})'\n",
    "        status, message_ids = mail.search(None, search_criteria)\n",

View on GitHub (pinned to 606a07d341)

Solutions

  1. Create the config file the notebook expects with an 'imap' section containing server/port/username/password keys, matching the exact key names read by _read_imap_config().
  2. For QQ Mail, enable IMAP in mailbox settings and use the 16-character authorization code (授权码) as the password, not the login password.
  3. Alternatively pass username= and password= directly to the tool/constructor call so they don't depend on file config.
  4. If you just want to see the agent run, set use_demo=True to use demo_emails and skip IMAP entirely.
  5. Verify the config file is being found: the code silently ignores FileNotFoundError, so confirm the exact path _read_imap_config() opens and that the notebook cwd contains it.

Example fix

// before (config missing -> silent empty credentials)
try:
    cfg = json.loads(cfg_path.read_text())
    imap_cfg = cfg.get("imap", {})
    username = username or imap_cfg.get("username", "")
    password = password or imap_cfg.get("password", "")
except FileNotFoundError:
    pass  # silently proceeds, later raises 未配置邮箱信息

// after (fail early with actionable message)
try:
    cfg = json.loads(cfg_path.read_text())
except FileNotFoundError:
    raise ValueError(f"未找到配置文件 {cfg_path},请复制模板并填写 imap 节点") from None
imap_cfg = cfg.get("imap", {})
username = username or imap_cfg.get("username", "")
password = password or imap_cfg.get("password", "")
Defensive patterns

Strategy: validation

Validate before calling

def has_imap_credentials(username: str, password: str, cfg_path: Path) -> bool:
    if username and password:
        return True
    if not cfg_path.exists():
        return False
    imap_cfg = json.loads(cfg_path.read_text()).get("imap", {})
    return bool(imap_cfg.get("username") and imap_cfg.get("password"))

# before agent.run(use_demo=False):
if not has_imap_credentials(username, password, cfg_path):
    raise SystemExit("缺少 IMAP 凭据:请在配置文件 imap 节点填写 username/password,或改用 use_demo=True")

Try / catch

try:
    result = agent.run(hours=24)
except ValueError as e:
    if "未配置邮箱信息" in str(e):
        # config problem: point user at the config file, never retry
        print(f"配置缺失: {e} — 请填写 imap username/password")
    raise

Prevention

When it happens

Trigger: Calling agent.run() with use_demo=False (or instantiating the email tool in live mode) when: no username/password arguments were passed, and the config file (e.g. config.json/.yaml read inside _read_imap_config) does not exist (FileNotFoundError is swallowed by 'except FileNotFoundError: pass') or its imap section lacks username/password keys. Defaults only cover server ('imap.qq.com') and port (993), never credentials.

Common situations: Fresh clone of the project without copying the config template; config file present but keys named differently (e.g. 'user'/'pass' instead of 'username'/'password'); QQ Mail users forgetting to generate an authorization code (QQ Mail requires an app-specific IMAP code, not the account password); running the notebook from a directory where the relative config path doesn't resolve.

Related errors


AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14). Data as JSON: /api/errors/62ca9395f1bd201b. Report an issue: GitHub.