File size: 9,846 Bytes
89419ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# Temp Mail 域名池与外部注册 API 说明

本文档说明本项目最近新增/补强的两部分能力:

1. `temp_mail` 邮箱服务支持域名池与 Worker 域名 API
2. 外部调用方可通过 HTTP API 创建注册批次、查询状态、取消任务

配套文档:

- [External API 调用示例](external-api-examples.md)
- [External API 第三方对接速览](external-api-quickstart.md)
- [External API 网站内管理与 API Key 生成方案](external-api-webui-management-plan.md)

---

## 1. Temp Mail 域名池

### 1.1 适用场景

项目内新增了 `temp_mail` 邮箱类型,用于接入你自建的临时邮箱服务,例如:

- `https://apmail.889110.xyz/`

服务端支持通过管理接口返回当前可用域名池,例如:

- `GET /admin/domains`

请求头:

- `x-admin-auth: <admin_password>`

### 1.2 支持的配置项

`temp_mail` 服务配置支持以下字段:

| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `base_url` | 是 | 临时邮箱服务地址,如 `https://apmail.889110.xyz` |
| `admin_password` | 是 | 管理接口密码,对应请求头 `x-admin-auth` |
| `domains` | 否 | 域名池,数组或逗号分隔字符串,优先级最高 |
| `domain` | 否 | 单个回退域名 |
| `enable_prefix` | 否 | 是否保留旧版 `tmp` 前缀,默认 `false` |
| `timeout` | 否 | 请求超时,默认 `30` |
| `max_retries` | 否 | 请求重试次数,默认 `3` |

### 1.3 域名解析优先级

创建邮箱时,域名来源按以下优先级解析:

1. `config.domains`
2. Worker `GET /admin/domains`
3. `config.domain`

如果三者都拿不到可用域名,则创建邮箱失败。

### 1.4 随机邮箱生成规则

- 默认生成**纯随机**本地名,不再强制 `tmp` 前缀
- 如果配置 `enable_prefix=true`,则继续使用旧版 `tmpxxxxx` 风格
- 最终会从当前可用域名池中随机选择一个域名拼接地址

### 1.5 可用服务展示

以下两个接口会展示 `temp_mail` 的域名摘要:

- Web UI: `GET /api/registration/available-services`
- 外部 API: `GET /api/external/capabilities`

返回中会包含:

- `domain_count`
- `domains_preview`
- `domain_source`

其中 `domain_source` 可能为:

- `config_domains`
- `worker_api`
- `config_fallback`
- `none`

> 注意:接口只暴露安全摘要,不会把 `admin_password` 等敏感信息返回给前端或外部调用方。

---

## 2. 外部注册 API

### 2.1 功能概览

外部 API 允许调用方不直接操作 Web UI,而是通过 HTTP 请求:

- 查询当前可用邮箱类型与上传目标
- 创建异步注册批次
- 查询批次状态
- 取消批次

### 2.2 鉴权方式

需要在系统设置中启用:

- `external_api_enabled`
- `external_api_key`

请求头使用:

```http
X-API-Key: <your_api_key>
```

未启用时返回 `403`;密钥错误时返回 `401`。

### 2.2.1 API Key 从哪里来

当前项目里,外部 API Key 不是“调用方自己申请”的,也不是接口动态下发的。

它来自系统管理员在项目配置中设置的:

- `external_api_enabled`
- `external_api_key`

也就是说:

1. 管理员先在系统里启用外部 API
2. 管理员在系统里填写一个 `external_api_key`
3. 再把这个值发给第三方调用方

第三方真正拿到的就是这个管理员配置的值,然后放到:

```http
X-API-Key: 你拿到的那个值
```

> 当前版本没有单独的“获取 API Key / 轮换 API Key”开放接口。  
> 如果要换 Key,需要由系统管理员修改配置后,再重新发给对接方。

### 2.3 能力查询

接口:

```http
GET /api/external/capabilities
```

用于返回:

- 支持的邮箱类型
- 当前可用邮箱服务
- 当前可用上传目标
- 外部 API 是否启用

其中邮箱类型包含数据库内启用的:

- `outlook` / `moe_mail` / `temp_mail` / `duck_mail` / `freemail` / `imap_mail`

其中临时邮箱能力在外部 API 中统一使用 `temp_mail` 枚举,不再暴露旧的 `tempmail` 占位。

能力接口返回的每个可选服务,都会带:

- `id`
- `name`
- `priority`

对于 `temp_mail`,还会带:

- `domain_count`
- `domains_preview`
- `domain_source`

### 2.3.1 service_id 不是 0/1 猜出来的

第三方在创建批次时看到的:

- `email.service_id`
- `upload.service_id`

都不是让你自己猜 “0 代表哪个、1 代表哪个”。

正确方式是:

1. 先调 `GET /api/external/capabilities`
2. 读取返回里的服务列表
3. 按 `name` 找到你想用的服务
4. 把该服务对应的 `id` 填回创建请求里的 `service_id`

也就是说,真正的映射关系来自 `capabilities` 返回值,而不是文档里写死。

例如能力返回里可能是:

```json
{
  "email_types": [
    {
      "type": "temp_mail",
      "services": [
        {"id": 3, "name": "Worker temp mail A"},
        {"id": 7, "name": "Worker temp mail B"}
      ]
    }
  ],
  "upload_providers": [
    {
      "provider": "sub2api",
      "services": [
        {"id": 1, "name": "sub2-main"},
        {"id": 4, "name": "sub2-backup"}
      ]
    }
  ]
}
```

那你在创建批次时:

- 传 `email.service_id=3`,表示使用 `Worker temp mail A`
- 传 `upload.service_id=4`,表示上传到 `sub2-backup`

> 结论:`service_id` 只认 `capabilities` 当前返回的 `id`,不要手写猜测,也不要假设某个数字永远对应某个服务。

### 2.4 创建注册批次

接口:

```http
POST /api/external/registration/batches
```

请求体示例:

```json
{
  "count": 5,
  "idempotency_key": "demo-batch-001",
  "email": {
    "type": "temp_mail",
    "service_id": 1
  },
  "upload": {
    "enabled": true,
    "provider": "sub2api",
    "service_id": 1
  },
  "execution": {
    "mode": "pipeline",
    "concurrency": 1,
    "interval_min": 5,
    "interval_max": 15
  }
}
```

字段约束:

- `count`: `1 ~ 100`
- `email.type`: 必填
- `email.service_id`: 可选
- `upload.provider`: 仅在 `upload.enabled=true` 时必填
- `upload.provider`: 仅支持 `sub2api` / `cpa` / `tm`
- `execution.mode`: `pipeline` 或 `parallel`
- `execution.concurrency`: `1 ~ 50`
- `execution.interval_max >= execution.interval_min`

关于 `service_id` 的默认行为:

- `email.service_id` 不传:系统按该邮箱类型的当前可用规则自动选
- `upload.service_id` 不传:系统按该上传 provider 当前启用且优先级最高的服务自动选

返回规则:

- 首次创建成功:HTTP `202`
- 如果 `idempotency_key` 命中历史批次:HTTP `200`

### 2.5 查询批次状态

接口:

```http
GET /api/external/registration/batches/{batch_uuid}
```

返回字段包含:

- `batch_uuid`
- `status`
- `requested_count`
- `completed_count`
- `success_count`
- `failed_count`
- `upload_success_count`
- `upload_failed_count`
- `failure_reason`
- `recent_errors`

状态可能包括:

- `pending`
- `running`
- `completed`
- `completed_partial`
- `failed`
- `cancelled`

### 2.6 取消批次

接口:

```http
POST /api/external/registration/batches/{batch_uuid}/cancel
```

规则:

- `pending` / `running` 可取消
- 已结束批次不可重复取消,返回 `400`
- 批次不存在返回 `404`

### 2.7 错误码约定

| 场景 | 状态码 |
| --- | --- |
| API 未启用 | `403` |
| API Key 错误 | `401` |
| 请求体验证失败 | `422` |
| 批次不存在 | `404` |
| 业务参数不合法 | `400` |
| 外部批次服务不可用 | `503` |

---

## 3. 外部批次执行行为

### 3.1 持久化

外部批次及其子项会持久化到数据库:

- `external_registration_batches`
- `external_registration_batch_items`

同时为每个子项生成独立 `registration_task_uuid`,便于复用现有注册执行链路。

### 3.2 幂等

如果创建批次时传入 `idempotency_key`,系统会优先复用已有批次,避免重复创建。

### 3.3 重启恢复

服务启动时会尝试恢复中断批次:

- `pending` / `running` 的外部批次会被标记为失败
- 失败原因记为 `service_restarted`

这样可以避免系统重启后外部调用方看到“永远卡在运行中”的脏状态。

### 3.4 上传行为

如果启用了上传:

- 注册成功后才会执行上传
- 会优先按注册结果里的 `email` 查账号
- 如找不到,再按 `account_id` 回退查找
- 上传结果会分别统计成功数和失败数

---

## 4. 当前测试覆盖

本轮功能已补充并验证以下覆盖:

- `temp_mail` 本地名生成与域名随机选择
- `temp_mail` 域名解析优先级与回退逻辑
- Worker 域名 API 集成路径
- `available-services` / `capabilities` 的域名摘要展示
- 外部 API 鉴权、参数校验、错误码映射
- 外部批次创建、幂等、状态汇总、取消、重启恢复
- 外部批次执行中的预失败项、上传成功/失败、`account_id` 回退查找
- 服务选择的拒绝分支与边界约束

全量测试验证结果:

- `134 passed`

---

## 5. 使用建议

### 5.1 如果你想让 `temp_mail` 自动从多个域名中随机取

建议只配置:

- `base_url`
- `admin_password`
- `domain`(仅作回退)

然后由 Worker `GET /admin/domains` 统一返回真实域名池。

### 5.2 如果你想固定只用部分域名

直接在该邮箱服务配置中填写:

```json
{
  "domains": ["a.example.com", "b.example.com"]
}
```

此时系统会优先使用配置内域名池,而不会再请求 Worker 域名接口。

### 5.3 如果你要给第三方系统对接

优先使用:

- `GET /api/external/capabilities`
- `POST /api/external/registration/batches`
- `GET /api/external/registration/batches/{batch_uuid}`
- `POST /api/external/registration/batches/{batch_uuid}/cancel`

这样调用方只负责“下发任务”和“查结果”,实际邮箱、上传、执行模式等策略仍由系统内配置统一兜底。