feat: add user send mail and sent box (#1122)

* feat: add user send mail client

* fix: align user mail navigation

* fix: shorten address credential action

* test: cover user mail ownership boundaries

* fix: address user mail review feedback

* fix: disambiguate user mail e2e heading

* fix: minimize shared sent box changes

* refactor: isolate user send mail page

* refactor: reuse bound address lookup

* fix: clarify user sent box naming

* refactor: decouple user send API from roles

* fix: align user send mail behavior

* fix: align user send role and rate limits

* test: isolate user send rate limits

* test: initialize rate limit worker database

* refactor: simplify user send rate limit

* refactor: inline user send rate limit path

* refactor: simplify user send limiter key

* refactor: keep existing rate limit behavior

* style: simplify user send rate limit condition

* style: group user send rate limit condition

* fix: bind user role token to account

* fix: keep user sender selection available
This commit is contained in:
Dream Hunter
2026-08-25 14:14:42 +08:00
committed by GitHub
parent dccca92928
commit 5dbb6107dd
20 changed files with 1483 additions and 19 deletions
@@ -2,12 +2,13 @@
## Send Email via HTTP API
There are two HTTP API endpoints for sending emails:
There are three HTTP API endpoints for sending emails:
| Endpoint | Authentication | Use Case |
|----------|---------------|----------|
| `/api/send_mail` | `Authorization: Bearer <address_JWT>` header | Internal calls, requires cookie / header auth |
| `/external/api/send_mail` | `token` field in request body | External system integration, no header auth needed |
| `/user_api/address/:address_id/send_mail` | `x-user-token: <user_JWT>` header | Signed-in users sending from one of their bound addresses |
::: tip What is "Address JWT"?
The Address JWT is the `jwt` field returned when creating an email address via `/api/new_address` or `/admin/new_address`.
@@ -59,6 +60,44 @@ res = requests.post(
)
```
### Method 3: User JWT (`/user_api/address/:address_id/send_mail`)
Obtain `address_id` from the paginated `GET /user_api/bind_address` response. The backend verifies that the address belongs to the current user; clients cannot choose an arbitrary sender address.
If the site grants unlimited sending to the current user's role through `NO_LIMIT_SEND_ROLE`, also send the `access_token` returned by `GET /user_api/settings`. The frontend handles this token automatically.
```python
send_body = {
"from_name": "Sender Name",
"to_name": "Recipient Name",
"to_mail": "Recipient Address",
"subject": "Email Subject",
"is_html": False,
"content": "Email content",
}
res = requests.post(
"https://your_worker_domain/user_api/address/123/send_mail",
json=send_body,
headers={
"x-user-token": "<user_JWT>",
# "x-user-access-token": "<user_access_token>", # Required for role permissions
"Content-Type": "application/json",
},
)
```
The same user-address API group also provides:
| Method | Endpoint | Description |
| --- | --- | --- |
| `GET` | `/user_api/address/:address_id/settings` | Get the address and remaining send balance |
| `POST` | `/user_api/address/:address_id/request_send_mail_access` | Request send access for the address |
| `GET` | `/user_api/sendbox?limit=20&offset=0&address=optional-address` | List the current user's sent items, optionally filtered by a bound address |
| `DELETE` | `/user_api/sendbox/:mail_id` | Delete one sent item owned by the current user |
All endpoints require a User JWT. Address-scoped endpoints verify that `address_id` is bound to the current user, while user-level sent-item endpoints only return or delete records for the user's bound addresses. The user access token is only used to apply optional role permissions.
## Send Email via SMTP
Please first refer to [Configure SMTP Proxy](/en/guide/feature/config-smtp-proxy.html).
@@ -2,12 +2,13 @@
## 通过 HTTP API 发送邮件
种 HTTP API 端点可以发送邮件,区别如下:
种 HTTP API 端点可以发送邮件,区别如下:
| 端点 | 认证方式 | 适用场景 |
|------|---------|---------|
| `/api/send_mail` | `Authorization: Bearer <地址JWT>` header | 内部调用,需要先通过 cookie / header 鉴权 |
| `/external/api/send_mail` | 请求体中的 `token` 字段 | 外部系统集成,无需 header 鉴权 |
| `/user_api/address/:address_id/send_mail` | `x-user-token: <用户JWT>` header | 已登录用户使用自己的绑定邮箱发信 |
::: tip 什么是"地址 JWT"
地址 JWT 是通过 `/api/new_address``/admin/new_address` 创建邮箱地址时返回的 `jwt` 字段。
@@ -59,6 +60,44 @@ res = requests.post(
)
```
### 方式三:使用用户 JWT`/user_api/address/:address_id/send_mail`
`address_id` 可从分页接口 `GET /user_api/bind_address` 的结果中获取。后端会验证该地址属于当前用户,客户端不能自行指定发件邮箱。
如果站点通过 `NO_LIMIT_SEND_ROLE` 为当前用户角色配置了无限发信额度,还需要传入 `GET /user_api/settings` 返回的 `access_token`。前端会自动处理该令牌。
```python
send_body = {
"from_name": "发件人名字",
"to_name": "收件人名字",
"to_mail": "收件人地址",
"subject": "邮件主题",
"is_html": False,
"content": "邮件内容",
}
res = requests.post(
"https://你的worker域名/user_api/address/123/send_mail",
json=send_body,
headers={
"x-user-token": "<用户JWT>",
# "x-user-access-token": "<用户访问令牌>", # 使用角色权限时需要
"Content-Type": "application/json",
},
)
```
同一组用户地址接口还包括:
| 方法 | 端点 | 说明 |
| --- | --- | --- |
| `GET` | `/user_api/address/:address_id/settings` | 获取地址和剩余发信额度 |
| `POST` | `/user_api/address/:address_id/request_send_mail_access` | 为该地址申请发信权限 |
| `GET` | `/user_api/sendbox?limit=20&offset=0&address=可选地址` | 分页获取当前用户的发件箱,可按绑定地址过滤 |
| `DELETE` | `/user_api/sendbox/:mail_id` | 删除当前用户的一条发件记录 |
以上接口都需要用户 JWT。地址级接口验证 `address_id` 是否绑定到当前用户,用户级发件箱接口只返回或删除当前用户绑定地址的记录;用户访问令牌仅用于应用可选的角色权限。
## 通过 SMTP 发送邮件
请先参考 [配置 SMTP 代理](/zh/guide/feature/config-smtp-proxy.html)。