Smail/docs/后端接口说明文档.md
smail 8ee5f6083a
Some checks are pending
Security Checks / security (push) Waiting to run
统一开发端口说明与运行探活
2026-09-07 16:16:33 +08:00

23 KiB
Raw Blame History

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

当前路由注册以 backend/listen/http_server/http_server.go 为准,接口处理逻辑以 backend/controllers 和 backend/controllers/email 为准。

2. 通用调用约定

2.1 基础地址

以下示例使用:

https://mail.example.com

请替换为实际的 Smail Web 地址。开发环境通过前端 HTTPS 入口访问:

https://xuans.com

开发后端 API 独立监听 http://127.0.0.1:5201,由前端开发服务代理 /api 和 /attachments 请求。浏览器不应绕过前端入口直接访问后端端口。生产环境应始终使用正式 HTTPS 入口。

2.2 通用响应格式

除附件内容下载接口外,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 请求通常应携带:

Content-Type: application/json

需要登录的状态修改请求示例:

Cookie: 会话Cookie; smail_csrf=CSRF_TOKEN
X-CSRF-Token: CSRF_TOKEN
Origin: https://mail.example.com

Lang 请求头可以指定返回语言,例如:

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

调用示例

curl -X GET "https://mail.example.com/api/ping"

也可以使用:

curl -X POST "https://mail.example.com/api/ping"

返回示例

{
  "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。

调用示例

兼容原版字段:

curl -X POST "https://mail.example.com/api/login" \
  -H "Content-Type: application/json" \
  -c cookies.txt \
  -d '{
    "account": "admin",
    "password": "your-password"
  }'

使用新的登录标识字段:

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"
  }'

成功返回示例

普通登录直接完成时,返回:

{
  "errorNo": 0,
  "errorMsg": "",
  "data": {
    "account": "admin",
    "name": "管理员",
    "role": "super_admin",
    "is_admin": 1,
    "domains": [
      "example.com"
    ]
  }
}

登录成功时,服务端同时通过 Set-Cookie 设置会话 Cookie 和 CSRF Cookie。客户端必须保存这两个 Cookie,后续接口才能继续调用。

二次验证情况

如果当前账号启用了登录二次验证,密码正确后可能返回一个待完成的验证数据,而不是直接建立完整登录态。客户端应根据返回的 data 调用登录验证接口:

POST /api/security/login/verify

该流程不属于原版 PMail 基础接口,详细接口以本文第 4 节的当前安全接口表和前端调用为准。

失败返回示例

{
  "errorNo": 100,
  "errorMsg": "参数错误",
  "data": ""
}

为了防止账号枚举,登录失败、账号不存在、密码错误和登录限流通常使用相同的业务错误响应,调用方不要根据错误文本判断账号是否存在。

与原版差异

原版文档提到可以通过请求头 token 进行验签。当前代码路由和认证逻辑未发现该兼容方式,当前登录态以服务端会话 Cookie 为准,不应使用 token 请求头替代登录。

3.3 获取邮件分组树

接口信息

项目 内容
原版路径 /api/group
当前路径 /api/group
方法 GET
原版方法 POST/GET
是否需要登录 是
请求体 无
返回类型 JSON

当前 /api/group 只注册了 GET。使用原版文档中的 POST 调用会返回 HTTP 405。

调用示例

curl -X GET "https://mail.example.com/api/group" \
  -b cookies.txt

如果是浏览器外的状态修改场景,读取接口不需要提交 CSRF Token;但必须携带有效会话 Cookie。

返回示例

{
  "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,例如:

"{\"type\":0,\"status\":-1,\"group_id\":0}"

调用示例

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
  }'

返回示例

{
  "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

调用示例

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
  }'

返回示例

{
  "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": "<div>HTML正文</div>",
    "error": "",
    "attachments": [
      {
        "Filename": "report.pdf",
        "ContentType": "application/pdf",
        "Index": 0
      }
    ]
  }
}

与原版差异

原版文档使用 /api/detail。当前该路径没有注册,必须改为 /api/email/detail。邮件详情请求会标记邮件为已读,因此调用该接口不是纯读取操作;使用 POST 也符合当前路由约束。

3.6 发送邮件

接口信息

项目 内容
路径 /api/email/send
方法 POST
是否需要登录 是
请求格式 application/json
返回类型 JSON
请求体上限 40 MiB

请求参数

参数 类型 必填 说明
reply_to array 否 回复地址列表,每项包含 name、email
from object 否 发件人,包含 name、email;邮箱为空时使用当前账号地址
to array 是 收件人列表,每项包含 name、email
bcc array 否 密送列表
cc array 否 抄送列表
subject string 是 邮件标题
text string 否 纯文本正文
html string 否 HTML 正文
sender object 否 SMTP Envelope Sender;为空时使用 from
read_receipt array 否 已读回执地址列表
attrs array 否 附件列表,每项包含 name、data

attrs[].data 必须是 Data URL 格式的 Base64 数据,例如:

data:application/pdf;base64,BASE64_DATA

调用示例

curl -X POST "https://mail.example.com/api/email/send" \
  -H "Content-Type: application/json" \
  -b cookies.txt \
  -H "X-CSRF-Token: CSRF_TOKEN" \
  -H "Origin: https://mail.example.com" \
  -d '{
    "from": {
      "name": "管理员",
      "email": "admin@example.com"
    },
    "to": [
      {
        "name": "收件人",
        "email": "user@example.com"
      }
    ],
    "cc": [],
    "bcc": [],
    "subject": "测试邮件",
    "text": "这是纯文本正文。",
    "html": "<p>这是 HTML 正文。</p>",
    "attrs": []
  }'

成功返回

{
  "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 到 12 字节

调用示例

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"
  }'

成功返回

{
  "errorNo": 0,
  "errorMsg": "",
  "data": "操作成功"
}

修改成功后,当前账号的安全凭据和登录会话会按服务端策略刷新或撤销。调用方如果收到会话失效,应重新登录并重新获取 CSRF Token。

与原版差异

原版示例中的 passowrd 是拼写错误,当前代码使用正确字段 password。当前接口还要求提供 current_password,不能只提交新密码。

3.8 内联获取附件内容

接口信息

项目 内容
原版路径 /attachments/
当前路径 /attachments/{邮件ID}/{内容标识}
方法 GET
原版方法 文档写成 POST,但请求示例使用 GET
是否需要登录 是
返回类型 附件原始内容

其中:

  • {邮件ID} 是邮件 ID;
  • {内容标识} 是邮件详情中附件的内容标识,通常对应内联资源的 Content-ID;
  • 当前处理器会按路径读取这两个值,并检查当前用户是否有权访问该邮件附件。

调用示例

curl -X GET "https://mail.example.com/attachments/1/image001.png" \
  -b cookies.txt \
  -o image001.png

返回说明

成功时返回附件原始字节,并设置对应的 Content-Type,例如:

Content-Type: image/png

失败时返回统一 JSON 错误响应。常见原因包括路径格式错误、附件不存在、附件不可用或当前用户无权访问。

3.9 下载附件

接口信息

项目 内容
原版路径 /attachments/download/
当前路径 /attachments/download/{邮件ID}/{附件序号}
方法 GET
是否需要登录 是
返回类型 文件流

其中 {附件序号} 是邮件详情 attachments 数组中的 Index,当前从 0 开始,不是附件数据库 ID。

调用示例

curl -X GET "https://mail.example.com/attachments/download/1/0" \
  -b cookies.txt \
  -o report.pdf

返回响应头

成功时服务端返回:

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. 代码核对位置

本次对照使用以下当前代码位置作为实现依据:

  • 路由注册:backend/listen/http_server/http_server.go
  • 请求上下文、登录态和 CSRF 校验:backend/listen/http_server/https_server.go
  • 登录:backend/controllers/login.go
  • 分组:backend/controllers/group.go
  • 密码修改:backend/controllers/settings.go
  • 附件:backend/controllers/attachments.go
  • 邮件列表:backend/controllers/email/list.go
  • 邮件详情:backend/controllers/email/detail.go
  • 邮件发送:backend/controllers/email/send.go
  • 统一 JSON 响应:backend/dto/response/response.go

原版对照来源为 PMail Wiki 的《后端接口文档》。原版文档仅作为历史兼容性参考,接口是否可调用以当前 Smail 路由和处理器实现为准。