SnappyMail 2.38.2 API 使用详解
SnappyMail 2.38.2 无独立 CLI,管理面板与前端全部通过 JSON Ajax API 驱动;本文实测管理端与用户端核心动作(登录、改密、设置、域名、文件夹、邮件列表、发信),给出请求语义、错误码与实测回显。
SnappyMail 2.38.2 API 使用详解
目标:SnappyMail 2.38.2 不提供独立 CLI 工具(官方发布包内无 console/bin 入口,本环境实测确认),管理面板与 Webmail 前端的所有操作都通过同一套 JSON Ajax API 完成。本文逐动作实测这套 API 的请求形态、鉴权语义、错误码与真实回显,可用于运维自动化(巡检、批量配置)与二次开发对接。
一、环境信息
- SnappyMail:2.38.2(
/var/www/webmail/snappymail),Ubuntu 22.04.5 LTS + Nginx 1.18.0 + PHP-FPM 8.1 - 邮件后端:Dovecot 2.3.16(IMAP 143)+ Postfix 3.6.4(SMTP 25),本机回环监听
- 测试账号:管理员
admin;用户demo@example.com/alice@example.com(密码q1w2e3r4,仅实验环境) - 入口:
http://127.0.0.1:8147(Nginx 80 端口站点) - CLI 缺失证据:发布包根目录与应用目录下不存在任何
console、bin、*.sh可执行入口(find /var/www/webmail/snappymail -maxdepth 2 -name "*.sh" -o -name "console" -o -name "cli*"无匹配)
二、API 体系与请求语义
2.1 端点结构
| 端点 | 角色 | 用途 |
|---|---|---|
GET /?/AppData | 用户端引导 | 返回 CSRF Token、站点配置、界面语言清单 |
POST /?/Json | 用户端动作 | 登录、读信、发信、文件夹、设置等 |
GET /?/admin/AppData | 管理端引导 | 管理面板数据 + CSRF Token(未登录时首次访问触发临时密码生成) |
POST /?/admin/Json | 管理端动作 | 管理员登录、改密、全局设置、域配置、插件等 |
GET /?admin | 管理面板页面 | HTML 壳,内部走上表 Ajax |
核心规律(源码核实 + 实测):POST Action 参数的值 = 服务端方法名去掉 Do 前缀,如 DoAdminLogin → Action=AdminLogin、DoFolders → Action=Folders、DoMessageList → Action=MessageList。Action 写错或对应方法不存在时返回 UnknownError[999](这是使用该 API 最常见的坑)。
2.2 鉴权与 CSRF 三要素
每个写操作请求需要三样东西,缺一不可(实测总结):
- 会话 Cookie:
smsession(会话)与smaccount(用户凭据密文,HttpOnly)。curl 场景必须全程-c jar -b jar成对携带,漏掉-c会拿到过期 Cookie; - XToken(CSRF):从对应 AppData 回显的
System.token获取,以表单字段XToken提交;不匹配报InvalidToken[101] "XToken mismatch"; - 正确的动作名:见 2.1 规律。
统一响应包裹:{"Action": "...", "Result": ..., "code": N, "message": "...", "epoch": ...};成功时 Result 为对象/true,失败 Result:false + 非 0 code。
2.3 错误码速查(本环境实测遇到)
| code | message | 含义与实测诱因 |
|---|---|---|
| 0 且 Result:false + code:999 | UnknownError | Action 名写错(方法不存在);或参数导致非数组响应 |
| 101 | InvalidToken[101] "XToken mismatch" | CSRF Token 与会话不匹配(跨会话复用、重放) |
| 102 | AuthError[102] | 未登录/会话失效/未带 Cookie(管理动作在未登录时调用) |
| 201 | CantGetMessageList[201] | MessageList 缺 folder 参数(注意全小写) |
| — | ValueError(message 为空,999 呈现) | SendMessage 缺 from 参数,Email::Parse 抛错 |
2.4 最小可用调用骨架
B=http://127.0.0.1:8147
rm -f sm.jar
# 1. 取 CSRF Token(同时建立会话)
T=$(curl -s -c sm.jar "$B/?/AppData" | grep -oE '"token":"[^"]+"' | cut -d'"' -f4)
# 2. 动作调用:Action + XToken + 业务参数,全程 -c/-b 保持会话
curl -s -c sm.jar -b sm.jar -X POST "$B/?/Json" \
-d "Action=<动作名>&XToken=$T&<业务参数>"
三、用户端 API 实测
3.1 登录(Action=Login)
T=$(curl -s -c sm.jar "$B/?/AppData" | grep -oE '"token":"[^"]+"' | cut -d'"' -f4)
curl -s -c sm.jar -b sm.jar -X POST "$B/?/Json" \
-d "Action=Login&XToken=$T&Email=demo@example.com&Password=q1w2e3r4&SignMe=0" \
| grep -oE '"Auth":(true|false)|"Email":"[^"]+"'
实测回显:
"Auth":true
"Email":"demo@example.com"
登录响应本身携带新 Token(响应内 System.token),后续动作优先用响应里的新 Token。配合全局配置 login.default_domain 后,Email=demo 也可登录(服务端自动补全为 demo@example.com,实测有效)。
3.2 文件夹列表(Action=Folders)
curl -s -c sm.jar -b sm.jar -X POST "$B/?/Json" -d "Action=Folders&XToken=$T"
实测回显(节选):
{"Action":"Folders","Result":{"@Object":"Collection\/FolderCollection","@Collection":[
{"@Object":"Object\/Folder","name":"INBOX","fullName":"INBOX","delimiter":"\/",
"attributes":[],"metadata":[],"uidNext":2,"totalEmails":1,"unreadEmails":0,
"id":null,"size":505,"role":"inbox","etag":"37388066e60622d398aba1461442a627",
"checkable":false}],
"quotaUsage":null,"quotaLimit":null,"namespace":"", ...
totalEmails:1 与此前 Postfix 投递的测试邮件一致——Folders 动作直接反映 IMAP 服务器真实状态。
3.3 邮件列表(Action=MessageList)
参数全小写:folder、offset、limit(注意不是 Folder,大小写写错报 201)。
curl -s -c sm.jar -b sm.jar -X POST "$B/?/Json" \
-d "Action=MessageList&XToken=$T&folder=INBOX&offset=0&limit=10"
实测回显(节选):
{"@Object":"Collection\/MessageCollection","@Collection":[{
"folder":"INBOX","uid":1,"hash":"8f22faed622157754a6b04aaa82c181c",
"subject":"SnappyMail Install Verification Test","encrypted":false,
"messageId":"<20261021032126.A06AF1587EDF@mail.example.com>",
"spamScore":0,"spamResult":"","isSpam":false,
"dateTimestamp":1792552886,"dateTimestampSource":"header",
"from":[{"@Object":"Object\/Email","name":"","email":"alice@example.com","dkimStatus":"none"}],
"to":[{"@Object":"Object\/Email","name":"","email":"demo@example.com",...
3.4 发送邮件(Action=SendMessage)
必需参数:from(缺失时 Email::Parse 对空串抛 ValueError,响应为 code:999 且 message 为空——实测踩坑)、to、subject、正文 text(纯文本)或 html(富文本)。
curl -s -c sm.jar -b sm.jar -X POST "$B/?/Json" \
--data-urlencode "text=Hello Alice, this message is sent from SnappyMail via its JSON API (SendMessage action)." \
--data-urlencode "subject=SnappyMail API Send Test" \
--data "Action=SendMessage&XToken=$T&to=alice@example.com&from=demo@example.com"
实测回显:
{"Action":"SendMessage","Result":true,"epoch":1792554652}
投递核验(同机 Postfix 已把邮件投进 alice 的邮箱,收发链路闭环):
-rw------- 1 alice mail 615 Oct 21 03:50 /var/mail/alice
3.5 退出登录
Action=Logout 清除服务端会话与账号 Cookie(界面右上角菜单同一动作),自动化场景建议脚本结束前调用。
四、管理端 API 实测
管理端动作走 /?/admin/Json,登录前后的 Token 要分两次取(登录会刷新会话)。
4.1 首次初始化(临时密码机制)
安装后首次 GET /?/admin/AppData 会触发两件事(实测回显):
- 生成一次性密码文件:
cat /var/www/webmail/snappymail/data/_data_/_default_/admin_password.txt
0c3SNaU63l4I
application.ini写入admin_login = "admin"与 bcrypt 哈希。用该临时密码走AdminLogin后立即改密(AdminPasswordUpdate),临时密码文件自动删除(实测ls回显No such file or directory),新密码复登Auth:true——这就是安装向导背后 API 层的完整闭环。
4.2 管理员登录(Action=AdminLogin)
rm -f adm.jar
T=$(curl -s -c adm.jar "$B/?/admin/AppData" | grep -oE '"token":"[^"]+"' | cut -d'"' -f4)
curl -s -c adm.jar -b adm.jar -X POST "$B/?/admin/Json" \
-d "Action=AdminLogin&XToken=$T&Login=admin&Password=q1w2e3r4" | head -c 120
实测回显:
{"Action":"AdminLogin","Result":{"Auth":true,"title":"SnappyMail Webmail",...
4.3 修改全局设置(Action=AdminSettingsSet)
表单编码按 config[节][键] 传参,布尔项必须走 JSON body 传 true/false(表单传字符串 "false" 会被 PHP 判为真值,实测验证过这一差异):
# 字符串项:改站点标题
T=$(curl -s -b adm.jar "$B/?/admin/AppData" | grep -oE '"token":"[^"]+"' | cut -d'"' -f4)
curl -s -c adm.jar -b adm.jar -X POST "$B/?/admin/Json" \
--data-urlencode 'config[webmail][title]=Quzuo Lab Mail' \
--data "Action=AdminSettingsSet&XToken=$T"
# 用户侧立即生效
curl -s "$B/?/AppData" | grep -oE '"title":"[^"]+"' | head -1
实测回显:
{"Action":"AdminSettingsSet","Result":true,"epoch":1792553791}
"title":"Quzuo Lab Mail"
布尔项 JSON 形态(关管理面板开关并验证 403):
curl -s -c adm.jar -b adm.jar -X POST "$B/?/admin/Json" -H 'Content-Type: application/json' \
-d "{\"Action\":\"AdminSettingsSet\",\"XToken\":\"$T\",\"config\":{\"security\":{\"allow_admin_panel\":false}}}"
curl -s -o /dev/null -w '%{http_code}\n' "$B/?admin"
curl -s -o /dev/null -w '%{http_code}\n' -b adm.jar "$B/?/admin/AppData"
实测回显:
{"Action":"AdminSettingsSet","Result":true,"epoch":1792553805}
403
403
注意:
allow_admin_panel=false后连管理 API 一起 403,唯一恢复途径是编辑application.ini(allow_admin_panel = Off→On,实测编辑后立即恢复 200)。自动化脚本关这个开关前务必想好恢复路径。
4.4 修改管理员密码(Action=AdminPasswordUpdate)
curl -s -c adm.jar -b adm.jar -X POST "$B/?/admin/Json" \
-d "Action=AdminPasswordUpdate&XToken=$T&Password=<旧密码>&newPassword=<新密码>&Login=admin"
实测回显(强度检查通过):
{"Result": {"Weak": false}}
4.5 域配置管理(Action=AdminDomainSave / AdminDomainList)
保存域配置(JSON body,结构与 domains/<域>.json 一致):
curl -s -c adm.jar -b adm.jar -X POST "$B/?/admin/Json" -H 'Content-Type: application/json' \
-d "{\"Action\":\"AdminDomainSave\",\"XToken\":\"$T\",\"name\":\"example.com\",\"create\":1,
\"IMAP\":{\"host\":\"127.0.0.1\",\"port\":143,\"type\":0,\"shortLogin\":true},
\"SMTP\":{\"host\":\"127.0.0.1\",\"port\":25,\"type\":0,\"useAuth\":false},
\"Sieve\":{\"host\":\"127.0.0.1\",\"port\":4190,\"type\":0,\"enabled\":false},
\"whiteList\":\"\"}"
实测回显:
{"Action":"AdminDomainSave","Result":true,"epoch":1792553906}
列表查询:
{"Action":"AdminDomainList","Result":[{"name":"gmail.com","enabled":false},
{"name":"hotmail.com","enabled":false},{"name":"mail.example.com","enabled":false},
{"name":"example.com","enabled":true},{"name":"*","enabled":false}]}
保存后域文件落盘(实测):data/_data_/_default_/domains/example.com.json。
4.6 其他管理动作
同端点可用:AdminPluginList / AdminPluginEnable / AdminPluginSettingsUpdate(插件管理)、AdminContactsTest(联系人后端连通性测试)、AdminClearCache(清缓存)、AdminBackup(data 目录打包备份)、AdminUpgradeCore(核心升级)、AdminAppData(面板引导数据)。写操作均遵循「AppData 取 Token → 动作提交」节奏,与 4.2-4.5 相同。
五、自动化对接要点(实测经验清单)
- Action 名去
Do前缀:写错就是 999。把源码Actions/*.php里的public function Do*清单当作 API 字典最可靠。 - Cookie 是生命线:curl 必须
-c jar -b jar全程成对;脚本里每个请求都带上。实测漏掉-c时改密后新会话 Cookie 丢失,得到AuthError[102]。 - Token 刷新节奏:登录/会话刷新会轮换 Token,每个响应体里都带新
System.token;优先用响应里的新 Token 链式下发,或每次动作前重新取。 - 参数大小写敏感:
MessageList的folder/offset/limit全小写;AdminSettingsSet的config[节][键]必须与application.ini节名键名完全一致。 - 布尔语义:
AdminSettingsSet改布尔配置项用 JSON body;表单编码传 "false" 字符串会被当真值(PHP 弱类型),实测验证。 - 必填参数缺失的表象:
SendMessage缺from表现为 999 且 message 为空(ValueError 被吞)——遇到 message 为空的 999,先开[logs] enable=On看日志里的真实异常(本例:ValueError in .../MailSo/Mime/Email.php:55)。 - HTTPS 建议:
smaccountCookie 是应用层加密凭据(密钥与会话 Token 绑定),生产环境走 HTTPS 是基线;纯 HTTP 回环仅建议用于本机巡检。 - 频控与安全:登录失败写 syslog(
logs.auth_logging=On默认开),可对接 Fail2ban;对外网开放管理 API 时建议配合security.admin_panel_host限定访问来源。
六、参考链接
- 官方仓库(API 实现权威来源,
Actions/*.php的Do*方法即动作字典):https://github.com/the-djmaze/snappymail - 服务路由实现:仓库内
snappymail/v/2.38.2/app/libraries/RainLoop/ServiceActions.php - Cookie/加密实现:仓库内
snappymail/v/2.38.2/app/libraries/snappymail/cookies.php、crypt.php - 本专栏姊妹篇:《Ubuntu 22.04 安装 SnappyMail 指南》、《SnappyMail 2.38.2 配置参数详解》