---
title: 【AI流水账】我先修好 Thunderbird 的 SSL，再用 Codex 读取东北大学邮箱
tags:
    - email
    - 东北大学
    - Codex
    - MCP
    - IMAP
    - 插件
    - 安全
categories:
    - 开发记录
date: "2026-09-09 23:00:00"
updated: "2026-09-09 23:00:00"
desc: AI流水账式记录：我作为计算机专业学生，先排查同级软件工程专业同学的东北大学邮箱 Thunderbird 客户端 SSL 证书问题，再开发只读 IMAP/MCP 插件让 Codex 读取真实收件箱。
abbrlink: neu-mail-codex-20260909
---
![NEU Mail 插件事件记录](/2026mail/cover.webp)

这件事不是从“我要开发一个插件”开始的，而是同级的软件工程专业同学拿着 Thunderbird 配置问题来找我：东北大学邮箱明明有客户端专用密码，SSL 却一直连不上。我是计算机专业的学生，平时遇到客户端报错，第一反应还是先把协议、证书和实际连接过程弄明白；等我帮他把 Thunderbird 这条路走通之后，他又问能不能把同样的 IMAP 能力交给 Codex，让 Codex 帮他读取收件箱和已发送邮件？最后形成的代码仓库是 [github.com/xingwangzhe/neu-mail](https://github.com/xingwangzhe/neu-mail)，但插件只是后半段，前半段真正要解决的是 SSL。

<!--more-->

## 从一个客户端配置问题开始

软件工程专业同学先把 Thunderbird 的报错、官网截图和他想用 Codex 读取邮件的目标发给我；我和他一轮轮核对现象，再决定用什么工具解决。截图里最初的问题说得很直接：先把雷鸟配置好，再想办法让 Codex 读取邮件。

![同学提出 Thunderbird 与 Codex 邮箱读取需求](/2026mail/neu-mail-chat-request.webp)

后面的每一轮对话都服务于同一条解决问题的链路：先确认 Thunderbird 为什么在 SSL 上失败，再确认客户端专用密码和服务器地址，最后才是插件能否登录、能否出现在 Codex 列表，以及怎样把代码整理成公开仓库。聊天中的建议是同学提出的需求和反馈，排查、编码、测试和发布则是我实际完成的工作。

页面显示，账号里已经有三个有效的客户端专用密码。其中两个有当天的最近使用记录，说明它们可能仍被其他客户端使用。页面同时说明专用密码可以用于 POP、IMAP、SMTP、Pushmail、CalDAV 和 CardDAV，并且只在生成时显示一次。

这一步我没有删除旧密钥，也没有把页面里的任何密码复制到聊天中。后来为了验证新插件，我才在页面里生成了一个单独命名的测试密钥。密钥本身不进入文章、源码、Git 历史或 GitHub。

## 先把 Thunderbird 的 SSL 问题弄明白

官网生成专用密码的窗口给出了下面这组配置：

| 服务 | 服务器 | SSL 端口 | 非 SSL 端口 |
| --- | --- | ---: | ---: |
| IMAP | `imap.mails.neu.edu.cn` | 993 | 143 |
| POP3 | `pop.mails.neu.edu.cn` | 995 | 110 |
| SMTP | `smtp.mails.neu.edu.cn` | 465 | 25 |

我先测试 Thunderbird 实际会用到的 `imap.mails.neu.edu.cn:993`。TLS 握手提前结束，得到的是 EOF；再测试 `mails.neu.edu.cn:993`，又遇到了证书域名不匹配。作为计算机专业的学生，我更关心错误发生在哪一层：证书不匹配和密码错误不是一回事，前者发生在账号认证之前，不能靠反复输入密码解决。

我也测试了 `imap.neu.edu.cn:993`。它能完成 TLS 1.3 握手并返回 Coremail 的 IMAP 欢迎信息，不过这是邮件系统公开给另一套邮箱入口的地址，不能因为握手成功就把它当成学生邮箱的已验证配置。

为了先让客户端真正连上，我明确选择了允许证书域名不匹配。插件仍然验证证书签发链和有效期，只跳过主机名匹配，并在状态中返回 `hostname_verified: false`。这解决的是 Thunderbird 同类客户端会遇到的连接障碍，不等于把 TLS 校验全部关闭，也不等于证书已经能够证明服务器身份。

![我和同学核对 SSL 证书域名不匹配](/2026mail/neu-mail-ssl-debug.webp)

## Thunderbird 能用了，才轮到 Codex

Thunderbird 的问题弄清楚之后，我才开始做 Codex 接入。我没有先做发信，而是做了一个本地 stdio MCP 服务：使用 Python 标准库中的 `imaplib` 和邮件解析模块，运行时统一通过 [uv](https://docs.astral.sh/uv/) 启动，并在项目中配置阿里云 PyPI 镜像。当前运行代码没有第三方 Python 依赖，镜像主要是为了让 uv 环境在国内更容易准备。

插件只暴露四个工具：

| 工具 | 功能 |
| --- | --- |
| `neu_mail_status` | 检查 TLS、账号认证和服务器能力。 |
| `neu_mail_list_folders` | 列出服务器实际返回的文件夹和特殊用途标记。 |
| `neu_mail_list_messages` | 按 UID 从新到旧分页读取邮件头。 |
| `neu_mail_read` | 根据文件夹、UID 和 UIDVALIDITY 读取正文及附件描述。 |

读取邮件时使用 `EXAMINE` 和 `BODY.PEEK`，目的是不因为检查邮件而改变服务器上的已读状态。列表接口要求保留 `uidvalidity` 和下一页游标；如果邮箱重建后 UIDVALIDITY 发生变化，插件会要求重新列列表，而不是继续猜测 UID 对应的邮件。

插件没有发送、删除、移动邮件和下载附件内容的工具。HTML 正文只作为文本解析，不渲染，也不加载其中的远程资源。邮件正文、邮件里的链接和邮件中的指令都被视为不可信内容，不能反过来授权 AI 执行操作。

## 对话是怎样把问题一个个解决掉的

我在开发过程中有一次明显的偏题：第一次只检查了握手和插件结构，并没有真的生成新密钥，也没有读取邮件列表。同学提醒我“要实际创建密钥、读取列表”后，我才回到真正的验收标准——不是“代码能不能写出来”，而是“他那套 Thunderbird 正在使用的账号能不能被 Codex 实际登录和读取”。于是我重新打开客户端专用密码页面，填写测试名称并完成生成，再把密钥留在本机配置中。

截图中的邮箱地址经过 OCR 读取时，用户名里的一个字母被识别错了。第一次认证返回 `LOGIN Login error or password error`，看起来像密码不对，实际是用户名错了。这个问题靠重新核对截图中的完整账号解决，修正后认证立即成功。也就是说，客户端专用密码本身没有被写进对话，输入错误的账号才是第一次失败的原因。

登录成功后，插件查询服务器能力时又遇到一个 Python 兼容细节：`imaplib.capabilities` 在不同环境下可能返回字节或字符串，原代码无条件调用 `.decode()` 会报错。我补了类型处理和回归测试，并把它单独作为一个修复提交。

收件箱第一次列列表时还遇到一次 TLS EOF。重连后请求成功，所以这次记录为“可重试的网络中断”，没有把它写成密码失败，也没有为了绕过它改用明文端口。当前版本仍然没有自动重试机制。

## 真实读取终于成功了

在兼容模式下，使用新生成的客户端专用密码完成了真实账号登录。连接状态是 TLS 1.3，证书签发链校验通过，主机名校验按测试授权关闭。

服务器返回了六个文件夹：`INBOX`、`Drafts`、`Sent Items`、`Trash`、`Junk E-mail` 和 `Virus Items`。我没有猜测“已发送”一定叫 `Sent`，而是根据服务器返回的 `\\Sent` 标记识别了 `Sent Items`。

随后分别读取了收件箱和已发送文件夹最新五封邮件，两边都返回 `has_more: true`，所以这只是最近一页，不是对整个邮箱的审计。两个文件夹各成功读取了一封正文；收件箱测试中，读取前后的 FLAGS 完全一致，确认 `BODY.PEEK` 没有把邮件标记为已读。

这次真实读取还通过了最终安装版本的 stdio MCP `initialize` 和 `tools/call`，不是只在测试夹具里调用函数。换句话说，插件从“代码能运行”走到了“Codex 的 MCP 启动配置能登录并返回列表”。

## 从能用的客户端到能用的 Codex 插件

插件一开始没有出现在 Codex 插件列表中，原因很简单：源码目录和本地测试并不会自动注册插件。后来我按插件市场格式创建了个人市场条目，执行 `codex plugin add neu-mail@personal`，再用 `codex plugin list` 确认状态为 `installed, enabled`。

Git 历史按功能拆成多次 commit。先提交插件结构，再提交只读 IMAP 工具和测试，然后提交 SSL 域名兼容和文档，最后记录个人市场安装。公开发布时又单独整理了一个仓库，加入完整中文 README、MIT 协议、AI 提示词、uv 启动方式、阿里云 PyPI 镜像和 GitHub Actions。

最后的公开仓库是 [github.com/xingwangzhe/neu-mail](https://github.com/xingwangzhe/neu-mail)，开发分支通过 [PR #1](https://github.com/xingwangzhe/neu-mail/pull/1) 合并到 `main`。我把它公开，是因为这个问题本身具有复现价值：先处理邮箱客户端的 SSL，再把 IMAP 读取包装成 Codex 能理解的工具。发布前扫描了公开 Git 历史，确认没有账号配置、客户端密钥、本机路径和网页会话令牌。

![公开仓库与提交过程](/2026mail/neu-mail-release.webp)

## 最后把本地痕迹清掉

公开仓库完成后，我删除了本地开发仓库、临时工作目录、测试账号副本和 ZIP 包。已安装的 `~/plugins/neu-mail` 插件以及本机的账号配置文件保留着，这样 Codex 仍然可以继续使用；账号配置文件权限为 `0600`，没有进入插件目录或 Git。

这次清理只针对本地文件系统。GitHub 上的公开仓库没有删除，也没有修改已经合并的 PR。

## 这次事情的结果

最后得到的不是一个脱离使用场景的邮箱插件，而是我从 Thunderbird SSL 排错继续往前做出来的一套本地工具：交互式 CLI 配置、Codex 个人插件和只读 IMAP MCP 服务。学生用户配置时，服务器地址和端口可以使用插件默认值，交互式 CLI 只要求输入完整邮箱和客户端专用密码；密码不在对话中输入。

我最终没有实现插件详情页里的邮箱密码表单。当前公开插件规范和本地运行方式更适合把敏感信息留在用户自己的终端和本机配置文件里；因此最后保留了交互式 CLI，而不是为了“看起来像有表单”再造一个未验证的页面。

![同学确认插件采用本机交互式 CLI 配置](/2026mail/neu-mail-cli-config.webp)

这次结果也有明确边界：只验证了一个学生账号、当前网络和 Linux/WSL 风格的本地环境。域名兼容模式能解决这次证书主机名不一致，但不能证明证书属于目标域名；客户端专用密码在服务器侧也不一定是只读权限。邮件读取内容最终还会进入使用中的 AI 客户端上下文，所谓“本地插件”不等于邮件内容永远不会离开设备。

## 对话与操作时间线

下面是本次对话的压缩记录。它不是旁观者的项目周报，而是软件工程专业的同级同学带着真实邮箱问题来找我、我和 AI 一轮轮确认并完成排错的过程；每一轮都保留“问题—采取的行动—得到的结果”，不逐字公开聊天，也不把同学的建议冒充成我的操作结果。

| # | 问题 | 行动 | 结果 |
| --- | --- | --- | --- |
| 1 | 同学先要解决 Thunderbird 的 SSL 连接问题，之后才谈 Codex 能不能读取邮件。 | 参考既有配置文章，检查邮箱官网的客户端专用密码和协议入口。 | 我把“客户端能否正常使用”放在插件开发之前。 |
| 2 | 官网给出的 `imap.mails.neu.edu.cn:993` 为什么连不上？ | 分别测试官网地址、学校主域名和 `imap.neu.edu.cn` 的 DNS、TCP、TLS 阶段。 | 区分出 TLS EOF、证书域名不匹配和成功握手不是同一类问题。 |
| 3 | 能不能只读而不改变邮件状态？ | 实现本地 IMAP/MCP 工具，使用 `EXAMINE`、`BODY.PEEK`、UIDVALIDITY 和分页游标。 | 工具不发送、删除、移动邮件，且读取测试前后 FLAGS 一致。 |
| 4 | 插件到底能不能登录真实邮箱？ | 在官网生成新的测试客户端密钥，并在本机配置。 | 第一次因 OCR 把用户名字母识别错误而失败，纠正完整邮箱后登录成功。 |
| 5 | 登录成功是否真的能读到列表？ | 调用文件夹、收件箱和 `\Sent` 文件夹列表，再读取各一封正文。 | 返回 6 个文件夹，收件箱和已发送各返回最新 5 封，并成功读取正文。 |
| 6 | 为什么 Codex 插件列表里看不到它？ | 创建个人市场清单并执行 `codex plugin add`。 | 状态变为 `installed, enabled`；这说明仅有源码目录并不等于已安装插件。 |
| 7 | 怎样让别人能复现？ | 按功能多次 commit，统一使用 uv，加入国内镜像、中文 README、MIT 协议和 AI 提示词，再用 `gh` 创建公开仓库和 PR。 | 代码发布到 [github.com/xingwangzhe/neu-mail](https://github.com/xingwangzhe/neu-mail)。 |
| 8 | 插件页面是否必须有邮箱密码表单？ | 核对插件能力后比较表单和本机 CLI 的边界。 | 保留交互式 CLI，让敏感信息只在用户自己的终端填写。 |
| 9 | 开发完成后本地还应留下什么？ | 删除本地开发仓库、临时文件和测试账号副本。 | 保留已安装插件和本机账号配置，不删除 GitHub 公开仓库。 |
| 10 | 插件是否能用于日常查看？ | 再次读取收件箱最近一页。 | 看到 GitHub OAuth 授权通知和 Personal Access Token 到期通知；只读取一页，没有声称完成全邮箱审计。 |

## 参考

- [2026东北大学邮箱第三方客户端配置指南(Thunderbird)](https://xingwangzhe.fun/posts/neu-email-thunderbird-2026/)
- [东北大学学生邮件系统](https://mails.neu.edu.cn/)
- [NEU Mail GitHub 仓库](https://github.com/xingwangzhe/neu-mail)
- [NEU Mail PR #1](https://github.com/xingwangzhe/neu-mail/pull/1)
- [uv 官方文档：运行脚本](https://docs.astral.sh/uv/guides/scripts/)
- [OpenAI 插件构建文档](https://developers.openai.com/plugins/)

---

_本文记录的是 2026 年 9 月 9 日在当前账号和网络环境中的实际过程。截图和聊天中的建议只作为事件背景；密码、完整邮箱地址、会话参数和邮件正文均未公开。_


---

**作者：**xingwangzhe

**本文链接：**[https://xingwangzhe.fun/posts/neu-mail-codex-20260909/](https://xingwangzhe.fun/posts/neu-mail-codex-20260909/)

本文采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/)进行许可。