23 KiB
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 基础地址
以下示例使用:
https://mail.example.com
请替换为实际的 Smail Web 地址。开发环境也可以使用:
http://127.0.0.1:80
生产环境应优先使用 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
除探活、登录和公开的密码恢复流程外,邮件、分组、密码修改等接口都需要登录态。
登录成功后,服务端会设置:
- 会话 Cookie,用于识别登录用户;
smail_csrfCookie,用于保存 CSRF Token;- 后续状态修改请求需要在
X-CSRF-Token请求头中提交与smail_csrfCookie 相同的值。
对于携带登录会话 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 通用错误处理
调用方建议按以下顺序处理:
- 先检查 HTTP 状态码;
- 对 JSON 响应解析
errorNo、errorMsg和data; - 收到
403或业务错误码表示登录失效时,重新登录; - 收到
405时检查请求方法,不要重试相同方法; - 不要把密码、会话 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. 代码核对位置
本次对照使用以下当前代码位置作为实现依据:
- 路由注册:
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 路由和处理器实现为准。