SnappyMail 2.38.2 API 使用详解

SnappyMail 2.38.2 无独立 CLI,管理面板与前端全部通过 JSON Ajax API 驱动;本文实测管理端与用户端核心动作(登录、改密、设置、域名、文件夹、邮件列表、发信),给出请求语义、错误码与实测回显。

SnappyMailSnappyMail邮件服务器2026/10/21

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 三要素

每个写操作请求需要三样东西,缺一不可(实测总结):

  1. 会话 Cookie:smsession(会话)与 smaccount(用户凭据密文,HttpOnly)。curl 场景必须全程 -c jar -b jar 成对携带,漏掉 -c 会拿到过期 Cookie;
  2. XToken(CSRF):从对应 AppData 回显的 System.token 获取,以表单字段 XToken 提交;不匹配报 InvalidToken[101] "XToken mismatch";
  3. 正确的动作名:见 2.1 规律。

统一响应包裹:{"Action": "...", "Result": ..., "code": N, "message": "...", "epoch": ...};成功时 Result 为对象/true,失败 Result:false + 非 0 code。

2.3 错误码速查(本环境实测遇到)

codemessage含义与实测诱因
0 且 Result:false + code:999UnknownErrorAction 名写错(方法不存在);或参数导致非数组响应
101InvalidToken[101] "XToken mismatch"CSRF Token 与会话不匹配(跨会话复用、重放)
102AuthError[102]未登录/会话失效/未带 Cookie(管理动作在未登录时调用)
201CantGetMessageList[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 会触发两件事(实测回显):

  1. 生成一次性密码文件:
cat /var/www/webmail/snappymail/data/_data_/_default_/admin_password.txt
0c3SNaU63l4I
  1. 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 相同。

五、自动化对接要点(实测经验清单)

  1. Action 名去 Do 前缀:写错就是 999。把源码 Actions/*.php 里的 public function Do* 清单当作 API 字典最可靠。
  2. Cookie 是生命线:curl 必须 -c jar -b jar 全程成对;脚本里每个请求都带上。实测漏掉 -c 时改密后新会话 Cookie 丢失,得到 AuthError[102]。
  3. Token 刷新节奏:登录/会话刷新会轮换 Token,每个响应体里都带新 System.token;优先用响应里的新 Token 链式下发,或每次动作前重新取。
  4. 参数大小写敏感:MessageList 的 folder/offset/limit 全小写;AdminSettingsSet 的 config[节][键] 必须与 application.ini 节名键名完全一致。
  5. 布尔语义:AdminSettingsSet 改布尔配置项用 JSON body;表单编码传 "false" 字符串会被当真值(PHP 弱类型),实测验证。
  6. 必填参数缺失的表象:SendMessage 缺 from 表现为 999 且 message 为空(ValueError 被吞)——遇到 message 为空的 999,先开 [logs] enable=On 看日志里的真实异常(本例:ValueError in .../MailSo/Mime/Email.php:55)。
  7. HTTPS 建议:smaccount Cookie 是应用层加密凭据(密钥与会话 Token 绑定),生产环境走 HTTPS 是基线;纯 HTTP 回环仅建议用于本机巡检。
  8. 频控与安全:登录失败写 syslog(logs.auth_logging=On 默认开),可对接 Fail2ban;对外网开放管理 API 时建议配合 security.admin_panel_host 限定访问来源。

六、参考链接