# Smail 后端接口说明文档 > 本文档用于说明当前 Smail 后端 HTTP API 的实际调用方式,并对照原版 PMail《后端接口文档》核对接口是否仍然存在。 > > 核对基准:当前代码分支 `main`,提交 `6ec4628`,核对日期:2026-08-27。 ## 1. 文档范围和结论 原版 PMail 文档包含 9 个基础接口。当前 Smail 仍然保留其中 8 个接口的功能入口,但有以下路径或调用方式变化: | 编号 | 原版接口 | 当前实际接口 | 当前状态 | 主要差异 | |---|---|---|---|---| | 1 | `/api/ping` | `/api/ping` | 存在 | 当前返回统一 JSON,不再是纯文本 `pong` | | 2 | `/api/login` | `/api/login` | 存在 | 只接受 `POST`;使用会话 Cookie 和 CSRF Cookie,不使用原版文档所述的 `token` 请求头 | | 3 | `/api/group` | `/api/group` | 部分兼容 | 当前只接受 `GET`,原版写的 `POST` 不再可用;另有 `/api/group/list` | | 4 | `/api/email/list` | `/api/email/list` | 存在 | 只接受 `POST`,分页参数增加范围限制 | | 5 | `/api/detail` | `/api/email/detail` | 路径已变更 | 原路径不存在,必须使用 `/api/email/detail` | | 6 | `/api/email/send` | `/api/email/send` | 存在 | 只接受 `POST`,增加发件人校验、附件和收件人数限制 | | 7 | `/api/settings/modify_password` | `/api/settings/modify_password` | 存在 | 当前需要同时提交 `current_password` 和 `password` | | 8 | `/attachments/` | `/attachments/{邮件ID}/{内容标识}` | 存在 | 当前只接受 `GET`,用于内联展示附件内容 | | 9 | `/attachments/download/` | `/attachments/download/{邮件ID}/{附件序号}` | 存在 | 当前只接受 `GET`,最后一段是附件序号,不是附件数据库 ID | 当前路由注册以 `server/listen/http_server/http_server.go` 为准,接口处理逻辑以 `server/controllers` 和 `server/controllers/email` 为准。 ## 2. 通用调用约定 ### 2.1 基础地址 以下示例使用: ```text https://mail.example.com ``` 请替换为实际的 Smail Web 地址。开发环境也可以使用: ```text http://127.0.0.1:80 ``` 生产环境应优先使用 HTTPS。 ### 2.2 通用响应格式 除附件内容下载接口外,JSON 接口统一返回以下结构: ```json { "errorNo": 0, "errorMsg": "", "data": {} } ``` 字段说明: | 字段 | 类型 | 说明 | |---|---|---| | `errorNo` | number | `0` 表示成功;非 `0` 表示失败。常见值:`100` 参数错误、`403` 未登录、`405` 无权限、`500` 服务端错误 | | `errorMsg` | string | 错误说明。生产环境不应依赖错误文本判断业务分支,应优先判断 `errorNo` | | `data` | any | 成功数据或错误附加数据 | 请求方法不正确时,路由层返回 HTTP `405 Method Not Allowed`,并通过 `Allow` 响应头提示允许的方法;此时不一定返回上面的 JSON 结构。 ### 2.3 登录态和 CSRF 除探活、登录和公开的密码恢复流程外,邮件、分组、密码修改等接口都需要登录态。 登录成功后,服务端会设置: 1. 会话 Cookie,用于识别登录用户; 2. `smail_csrf` Cookie,用于保存 CSRF Token; 3. 后续状态修改请求需要在 `X-CSRF-Token` 请求头中提交与 `smail_csrf` Cookie 相同的值。 对于携带登录会话 Cookie 的状态修改请求,还需要使用同源的 `Origin`;跨站请求或 CSRF Token 不匹配时会被拒绝。 建议使用浏览器或 HTTP 客户端的 Cookie Jar 保存登录后的 Cookie,不要手工复制会话 Cookie 到代码或日志中。 ### 2.4 请求头 JSON 请求通常应携带: ```http Content-Type: application/json ``` 需要登录的状态修改请求示例: ```http Cookie: 会话Cookie; smail_csrf=CSRF_TOKEN X-CSRF-Token: CSRF_TOKEN Origin: https://mail.example.com ``` `Lang` 请求头可以指定返回语言,例如: ```http Lang: zh-CN ``` 未指定时由服务端使用默认语言处理。 ### 2.5 通用错误处理 调用方建议按以下顺序处理: 1. 先检查 HTTP 状态码; 2. 对 JSON 响应解析 `errorNo`、`errorMsg` 和 `data`; 3. 收到 `403` 或业务错误码表示登录失效时,重新登录; 4. 收到 `405` 时检查请求方法,不要重试相同方法; 5. 不要把密码、会话 Cookie、CSRF Token、完整邮箱地址写入日志。 ## 3. 原版基础接口对照和调用说明 ## 3.1 服务探活 ### 接口信息 | 项目 | 内容 | |---|---| | 原版路径 | `/api/ping` | | 当前路径 | `/api/ping` | | 当前方法 | `GET`、`POST` 均可到达处理器;建议使用 `GET` | | 是否需要登录 | 否 | | 请求体 | 无 | | 返回类型 | JSON | ### 调用示例 ```bash curl -X GET "https://mail.example.com/api/ping" ``` 也可以使用: ```bash curl -X POST "https://mail.example.com/api/ping" ``` ### 返回示例 ```json { "errorNo": 0, "errorMsg": "", "data": "pong" } ``` ### 与原版差异 原版文档把返回格式写成 `text`,示例为纯文本 `pong`。当前代码通过统一响应封装返回 JSON,实际数据位于 `data` 字段中。 ## 3.2 用户登录 ### 接口信息 | 项目 | 内容 | |---|---| | 原版路径 | `/api/login` | | 当前路径 | `/api/login` | | 方法 | `POST` | | 是否需要登录 | 否 | | 请求格式 | `application/json` | | 返回类型 | JSON | ### 请求参数 | 参数 | 类型 | 必填 | 说明 | |---|---|---:|---| | `identifier` | string | 否 | 登录标识。当前优先使用该字段,可填写账号或完整邮箱 | | `account` | string | 否 | 兼容原版字段。当 `identifier` 为空时使用该字段 | | `password` | string | 是 | 登录密码 | `identifier` 和 `account` 至少提供一个。为了兼容原版客户端,可以继续提交 `account`;新客户端建议使用 `identifier`。 ### 调用示例 兼容原版字段: ```bash curl -X POST "https://mail.example.com/api/login" \ -H "Content-Type: application/json" \ -c cookies.txt \ -d '{ "account": "admin", "password": "your-password" }' ``` 使用新的登录标识字段: ```bash curl -X POST "https://mail.example.com/api/login" \ -H "Content-Type: application/json" \ -c cookies.txt \ -d '{ "identifier": "admin@example.com", "password": "your-password" }' ``` ### 成功返回示例 普通登录直接完成时,返回: ```json { "errorNo": 0, "errorMsg": "", "data": { "account": "admin", "name": "管理员", "role": "super_admin", "is_admin": 1, "domains": [ "example.com" ] } } ``` 登录成功时,服务端同时通过 `Set-Cookie` 设置会话 Cookie 和 CSRF Cookie。客户端必须保存这两个 Cookie,后续接口才能继续调用。 ### 二次验证情况 如果当前账号启用了登录二次验证,密码正确后可能返回一个待完成的验证数据,而不是直接建立完整登录态。客户端应根据返回的 `data` 调用登录验证接口: ```text POST /api/security/login/verify ``` 该流程不属于原版 PMail 基础接口,详细接口以本文第 4 节的当前安全接口表和前端调用为准。 ### 失败返回示例 ```json { "errorNo": 100, "errorMsg": "参数错误", "data": "" } ``` 为了防止账号枚举,登录失败、账号不存在、密码错误和登录限流通常使用相同的业务错误响应,调用方不要根据错误文本判断账号是否存在。 ### 与原版差异 原版文档提到可以通过请求头 `token` 进行验签。当前代码路由和认证逻辑未发现该兼容方式,当前登录态以服务端会话 Cookie 为准,不应使用 `token` 请求头替代登录。 ## 3.3 获取邮件分组树 ### 接口信息 | 项目 | 内容 | |---|---| | 原版路径 | `/api/group` | | 当前路径 | `/api/group` | | 方法 | `GET` | | 原版方法 | `POST/GET` | | 是否需要登录 | 是 | | 请求体 | 无 | | 返回类型 | JSON | 当前 `/api/group` 只注册了 `GET`。使用原版文档中的 `POST` 调用会返回 HTTP `405`。 ### 调用示例 ```bash curl -X GET "https://mail.example.com/api/group" \ -b cookies.txt ``` 如果是浏览器外的状态修改场景,读取接口不需要提交 CSRF Token;但必须携带有效会话 Cookie。 ### 返回示例 ```json { "errorNo": 0, "errorMsg": "", "data": [ { "label": "收件箱", "tag": "{\"type\":0,\"status\":-1,\"group_id\":0}" }, { "label": "发件箱", "tag": "{\"type\":1,\"status\":-1}" }, { "label": "草稿箱", "tag": "{\"type\":0,\"status\":4}" } ] } ``` ### 当前相关分组接口 | 方法 | 路径 | 说明 | 请求体 | |---|---|---|---| | `GET` | `/api/group` | 获取前端使用的分组树 | 无 | | `GET` | `/api/group/list` | 获取默认分组和用户自定义分组列表 | 无 | | `POST` | `/api/group/add` | 创建自定义分组 | `{"name":"项目邮件","parent_id":0}` | | `POST` | `/api/group/del` | 删除自定义分组 | `{"id":1}` | `POST /api/group/add` 和 `POST /api/group/del` 属于状态修改接口,需要登录态和 CSRF Token。 ## 3.4 获取邮件列表 ### 接口信息 | 项目 | 内容 | |---|---| | 路径 | `/api/email/list` | | 方法 | `POST` | | 是否需要登录 | 是 | | 请求格式 | `application/json` | | 返回类型 | JSON | ### 请求参数 | 参数 | 类型 | 必填 | 默认值 | 说明 | |---|---|---:|---:|---| | `keyword` | string | 否 | 空字符串 | 邮件搜索关键词,最多 512 字节 | | `tag` | string | 否 | 全部邮件 | JSON 字符串形式的筛选条件 | | `current_page` | integer | 否 | `1` | 当前页,从 `1` 开始,最大 `10000` | | `page_size` | integer | 否 | `15` | 每页数量,最大 `100` | `tag` 是一个字符串字段,内容需要再次编码为 JSON,例如: ```json "{\"type\":0,\"status\":-1,\"group_id\":0}" ``` ### 调用示例 ```bash curl -X POST "https://mail.example.com/api/email/list" \ -H "Content-Type: application/json" \ -b cookies.txt \ -H "X-CSRF-Token: CSRF_TOKEN" \ -H "Origin: https://mail.example.com" \ -d '{ "keyword": "验证码", "tag": "{\"type\":0,\"status\":-1,\"group_id\":0}", "current_page": 1, "page_size": 15 }' ``` ### 返回示例 ```json { "errorNo": 0, "errorMsg": "", "data": { "current_page": 1, "total_page": 1, "list": [ { "id": 1, "title": "邮件标题", "desc": "邮件正文摘要", "datetime": "2026-08-27 08:44:00", "is_read": false, "sender": { "Name": "发件人", "EmailAddress": "sender@example.com" }, "to": [ { "Name": "收件人", "EmailAddress": "admin@example.com" } ], "dangerous": false, "error": "" } ] } } ``` ### 分页说明 - 不提交分页参数时使用 `current_page=1`、`page_size=15`; - `current_page` 小于 `1` 或大于 `10000` 会被拒绝; - `page_size` 小于 `1` 或大于 `100` 会被拒绝; - `keyword` 超过 512 字节会被拒绝; - `total_page` 在没有邮件时为 `0`。 ## 3.5 获取邮件详情 ### 接口信息 | 项目 | 内容 | |---|---| | 原版路径 | `/api/detail` | | 当前路径 | `/api/email/detail` | | 方法 | `POST` | | 是否需要登录 | 是 | | 请求格式 | `application/json` | | 返回类型 | JSON | ### 请求参数 | 参数 | 类型 | 必填 | 说明 | |---|---|---:|---| | `id` | integer | 是 | 邮件 ID,必须大于 `0` | ### 调用示例 ```bash curl -X POST "https://mail.example.com/api/email/detail" \ -H "Content-Type: application/json" \ -b cookies.txt \ -H "X-CSRF-Token: CSRF_TOKEN" \ -H "Origin: https://mail.example.com" \ -d '{ "id": 1 }' ``` ### 返回示例 ```json { "errorNo": 0, "errorMsg": "", "data": { "id": 1, "type": 0, "subject": "邮件标题", "reply_to": "", "from_name": "发件人", "from_address": "sender@example.com", "to": "[{\"Name\":\"收件人\",\"EmailAddress\":\"admin@example.com\"}]", "bcc": "[]", "cc": "[]", "sender": "{\"Name\":\"发件人\",\"EmailAddress\":\"sender@example.com\"}", "spf_check": 1, "dkim_check": 1, "status": 1, "send_time": "2026-08-27 08:44:00", "send_date": "2026-08-27 08:44:00", "update_time": "2026-08-27 08:44:00", "create_time": "2026-08-27 08:44:00", "text": "纯文本正文", "html": "
这是 HTML 正文。
", "attrs": [] }' ``` ### 成功返回 ```json { "errorNo": 0, "errorMsg": "", "data": "操作成功" } ``` 接口返回成功表示邮件已经通过参数校验并创建发送任务。实际投递结果可能在异步处理后才确定,失败信息会写入邮件状态,调用方应通过邮件列表或详情接口检查最终状态。 ### 当前限制 - `to` 至少包含一个收件人; - `to`、`cc`、`bcc`、`reply_to` 合计最多 `100` 个地址; - 附件最多 `20` 个; - 单个附件最大 `10 MiB`; - 附件总大小最大 `25 MiB`; - 邮件正文最大约 `8 MiB`; - `subject` 必填; - 普通用户不能伪造未授权的内部发件人地址; - 附件文件名会进行路径和控制字符清理。 ## 3.7 修改当前账号密码 ### 接口信息 | 项目 | 内容 | |---|---| | 原版路径 | `/api/settings/modify_password` | | 当前路径 | `/api/settings/modify_password` | | 方法 | `POST` | | 是否需要登录 | 是 | | 请求格式 | `application/json` | | 返回类型 | JSON | ### 请求参数 | 参数 | 类型 | 必填 | 说明 | |---|---|---:|---| | `current_password` | string | 是 | 当前密码 | | `password` | string | 是 | 新密码,长度为 8 到 256 字节 | ### 调用示例 ```bash curl -X POST "https://mail.example.com/api/settings/modify_password" \ -H "Content-Type: application/json" \ -b cookies.txt \ -H "X-CSRF-Token: CSRF_TOKEN" \ -H "Origin: https://mail.example.com" \ -d '{ "current_password": "old-password", "password": "new-password" }' ``` ### 成功返回 ```json { "errorNo": 0, "errorMsg": "", "data": "操作成功" } ``` 修改成功后,当前账号的安全凭据和登录会话会按服务端策略刷新或撤销。调用方如果收到会话失效,应重新登录并重新获取 CSRF Token。 ### 与原版差异 原版示例中的 `passowrd` 是拼写错误,当前代码使用正确字段 `password`。当前接口还要求提供 `current_password`,不能只提交新密码。 ## 3.8 内联获取附件内容 ### 接口信息 | 项目 | 内容 | |---|---| | 原版路径 | `/attachments/` | | 当前路径 | `/attachments/{邮件ID}/{内容标识}` | | 方法 | `GET` | | 原版方法 | 文档写成 `POST`,但请求示例使用 `GET` | | 是否需要登录 | 是 | | 返回类型 | 附件原始内容 | 其中: - `{邮件ID}` 是邮件 ID; - `{内容标识}` 是邮件详情中附件的内容标识,通常对应内联资源的 Content-ID; - 当前处理器会按路径读取这两个值,并检查当前用户是否有权访问该邮件附件。 ### 调用示例 ```bash curl -X GET "https://mail.example.com/attachments/1/image001.png" \ -b cookies.txt \ -o image001.png ``` ### 返回说明 成功时返回附件原始字节,并设置对应的 `Content-Type`,例如: ```http Content-Type: image/png ``` 失败时返回统一 JSON 错误响应。常见原因包括路径格式错误、附件不存在、附件不可用或当前用户无权访问。 ## 3.9 下载附件 ### 接口信息 | 项目 | 内容 | |---|---| | 原版路径 | `/attachments/download/` | | 当前路径 | `/attachments/download/{邮件ID}/{附件序号}` | | 方法 | `GET` | | 是否需要登录 | 是 | | 返回类型 | 文件流 | 其中 `{附件序号}` 是邮件详情 `attachments` 数组中的 `Index`,当前从 `0` 开始,不是附件数据库 ID。 ### 调用示例 ```bash curl -X GET "https://mail.example.com/attachments/download/1/0" \ -b cookies.txt \ -o report.pdf ``` ### 返回响应头 成功时服务端返回: ```http Content-Type: application/octet-stream X-Content-Type-Options: nosniff Content-Disposition: attachment; filename="attachment"; filename*=UTF-8''report.pdf ``` 服务端会清理文件名中的路径分隔符、换行符和控制字符,避免下载文件名被用于路径穿越或响应头注入。 ## 4. 当前新增安全和账户接口 这些接口不在原版 PMail 的 9 个基础接口中,但已经注册在当前 Smail 路由中。它们属于当前前端登录、安全设置和密码恢复流程的一部分。 ### 4.1 登录和恢复流程 | 方法 | 路径 | 是否公开 | 说明 | |---|---|---:|---| | `POST` | `/api/security/verification/request` | 否 | 请求验证信息 | | `POST` | `/api/security/verification/verify` | 否 | 校验验证信息 | | `POST` | `/api/security/login/verify` | 是 | 完成登录二次验证 | | `POST` | `/api/security/recovery/begin` | 是 | 开始密码恢复流程 | | `POST` | `/api/security/recovery/verify` | 是 | 校验密码恢复凭据 | | `POST` | `/api/security/recovery/complete` | 是 | 完成密码恢复 | | `POST` | `/api/security/recovery/webauthn/begin` | 是 | 开始 WebAuthn 恢复验证 | | `POST` | `/api/security/recovery/webauthn/verify` | 是 | 完成 WebAuthn 恢复验证 | | `POST` | `/api/logout` | 否 | 退出当前会话 | ### 4.2 WebAuthn 和恢复码 | 方法 | 路径 | 说明 | |---|---|---| | `POST` | `/api/security/webauthn/registration/begin` | 开始注册安全密钥 | | `POST` | `/api/security/webauthn/registration/finish` | 完成注册安全密钥 | | `GET` | `/api/security/webauthn/credentials` | 查询当前账号安全密钥 | | `POST` | `/api/security/webauthn/credentials/rename` | 重命名安全密钥 | | `POST` | `/api/security/webauthn/credentials/revoke` | 撤销安全密钥 | | `POST` | `/api/security/webauthn/login/begin` | 开始登录安全密钥验证 | | `POST` | `/api/security/webauthn/login/finish` | 完成登录安全密钥验证 | | `GET` | `/api/security/credentials/recovery-codes` | 查询恢复码状态,不返回历史恢复码原文 | | `POST` | `/api/security/credentials/recovery-codes/generate` | 生成新的恢复码 | | `POST` | `/api/security/credentials/recovery-codes/revoke` | 撤销恢复码 | | 未限制 | `/api/security/credentials/question` | 密保问题相关流程入口,实际方法由处理器判断 | ### 4.3 外部安全邮箱 | 方法 | 路径 | 说明 | |---|---|---| | `GET` | `/api/security/external-email/list` | 获取当前账号外部安全邮箱状态 | | `POST` | `/api/security/external-email/bind` | 绑定外部安全邮箱 | | `POST` | `/api/security/external-email/verify` | 验证外部安全邮箱 | | `POST` | `/api/security/external-email/disable` | 禁用外部安全邮箱 | 以上安全接口的请求参数和恢复状态机依赖当前安全设计,不应按照原版 PMail 的 `token` 模式调用。 ## 5. 当前邮件和账户管理接口补充 | 方法 | 路径 | 说明 | |---|---|---| | `POST` | `/api/email/del` | 删除邮件 | | `POST` | `/api/email/read` | 标记邮件已读 | | `POST` | `/api/email/move` | 移动邮件到分组 | | `POST` | `/api/rule/get` | 获取邮件规则 | | `POST` | `/api/rule/add` | 新增邮件规则 | | `POST` | `/api/rule/update` | 更新邮件规则 | | `POST` | `/api/rule/del` | 删除邮件规则 | | `POST` | `/api/user/create` | 创建用户,权限由当前角色控制 | | `POST` | `/api/user/edit` | 编辑用户 | | `POST` | `/api/user/info` | 获取用户信息 | | `POST` | `/api/user/list` | 获取用户列表 | | `GET` | `/api/plugin/list` | 获取插件列表 | | `GET`、`POST` | `/api/plugin/settings/{插件名}/{操作}` | 读取或保存插件设置 | 配置管理接口使用 `/api/v1/config/` 和 `/api/v1/audits/config` 路径,属于新的配置系统,不属于原版 PMail 基础接口;调用时需要按当前配置授权、审计和 CSRF 规则处理。 ## 6. 原版接口迁移清单 使用原版 PMail 客户端迁移到当前 Smail 时,至少需要修改以下内容: | 原版调用 | 当前调用 | |---|---| | `POST /api/detail` | `POST /api/email/detail` | | `POST /api/group` | `GET /api/group` | | `POST /attachments/{邮件ID}/{附件标识}` | `GET /attachments/{邮件ID}/{附件标识}` | | 仅提交 `password` 修改密码 | 同时提交 `current_password` 和 `password` | | 使用 `token` 请求头认证 | 保存登录会话 Cookie,并在状态修改请求中提交 `X-CSRF-Token` | | 将 `/api/ping` 当作纯文本读取 | 解析 JSON,并读取 `data` 中的 `pong` | ## 7. 代码核对位置 本次对照使用以下当前代码位置作为实现依据: - 路由注册:`server/listen/http_server/http_server.go` - 请求上下文、登录态和 CSRF 校验:`server/listen/http_server/https_server.go` - 登录:`server/controllers/login.go` - 分组:`server/controllers/group.go` - 密码修改:`server/controllers/settings.go` - 附件:`server/controllers/attachments.go` - 邮件列表:`server/controllers/email/list.go` - 邮件详情:`server/controllers/email/detail.go` - 邮件发送:`server/controllers/email/send.go` - 统一 JSON 响应:`server/dto/response/response.go` 原版对照来源为 PMail Wiki 的《后端接口文档》。原版文档仅作为历史兼容性参考,接口是否可调用以当前 Smail 路由和处理器实现为准。