API 说明

# Bettermail Post Office — API 设计与调用手册

面向第三方集成的完整 API 文档。邮局以 Docker 独立部署,负责:

- 邮箱域名管理
- 邮箱地址生成(兼容 **0–9 级**随机子域名)
- 接收 Cloudflare Worker 推送的入站邮件
- 邮件查询 / 消费(兼容原 Bettermail `/mails` 语义)

Worker 侧只需配置:

| 变量 | 作用 |
|------|------|
| `POST_OFFICE_URL` | 指向本服务入站地址,如 `https://mail.example.com/v1/inbound` |
| `POST_OFFICE_TOKEN` | 与本服务 `INBOUND_TOKEN`(或 `API_TOKEN`)一致 |

当 Worker **设置了** `POST_OFFICE_URL`:推送模式,Worker **本地不存储**。  
当 Worker **未设置** `POST_OFFICE_URL`:沿用旧 CF 邮局(D1/KV + `/mails`)。

---

## 0. 5 分钟速查:9 级子域邮箱 + 收信

> 目标:生成形如 `user@a.b.c.d.e.f.g.h.i.example.com` 的地址,并轮询取信。

**前提**

1. 域名已在邮局登记,且 `maxSubdomainLevels >= 9`(见 §4)
2. Cloudflare Email Routing 已把该域(含深子域/通配)指到 Worker
3. Worker 已配置 `POST_OFFICE_URL` / `POST_OFFICE_TOKEN` 指向本邮局
4. 调用方使用 `Authorization: Bearer <API_TOKEN>`

### A. 登记域名并允许 9 级子域

```bash
curl -s -X POST {BASE}/v1/domains \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"example.com","enabled":true,"maxSubdomainLevels":9}'
```

若域名已存在,改为更新上限:

```bash
curl -s -X PATCH {BASE}/v1/domains/{id} \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"maxSubdomainLevels":9,"enabled":true}'
```

### B. 生成 **随机 9 级子域** 邮箱

```bash
curl -s -X POST {BASE}/v1/mailboxes/generate \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "example.com",
    "count": 1,
    "subdomainLevels": 9,
    "localLength": 8,
    "labelLength": 4
  }'
```

成功时从 `data.list[0].address` 取完整地址,例如:

```text
k8m2p1qx@a1b2.c3d4.e5f6.g7h8.i9j0.k1l2.m3n4.o5p6.q7r8.example.com
```

| 关键字段 | 含义 |
|----------|------|
| `subdomainLevels: 9` | @ 右侧 **9 段**随机子域 + 根域名 |
| `localLength` | 本地部分(@ 左侧)随机长度 |
| `labelLength` | 每一级子域 label 长度 |

> 若返回 `subdomainLevels exceeds domain max`:域名 `maxSubdomainLevels` 不够,先做 §A。

### C. 收取邮件(三种常用方式)

**1)浏览列表(不删,摘要)— 推荐管理端**

```bash
curl -s "{BASE}/v1/mails?to=刚才的完整地址&page=1&pageSize=20&sortBy=receivedAt&sortOrder=desc" \
  -H "Authorization: Bearer $API_TOKEN"
```

**2)看正文(不删)**

```bash
curl -s "{BASE}/v1/mails/{邮件id}" \
  -H "Authorization: Bearer $API_TOKEN"
# 正文在 data.text / data.html
```

**3)拉信即删(验证码场景)**

```bash
curl -s "{BASE}/v1/mails?to=刚才的完整地址&limit=10&consume=1" \
  -H "Authorization: Bearer $API_TOKEN"
# 或兼容旧客户端:
curl -s "{BASE}/mails?to=刚才的完整地址&limit=10" \
  -H "Authorization: Bearer $API_TOKEN"
```

**推送模式注意**:客户端 **只打邮局** `{BASE}/v1/mails` 或 `{BASE}/mails`;  
Worker 的 `/mails` 在 `POST_OFFICE_URL` 模式下返回 **503**。

更细的参数见 §5(生成)、§7(读信)。

---

## 1. 部署与基础信息

### 1.1 快速启动

```bash
cd post-office
cp .env.example .env
# 编辑 API_TOKEN / INBOUND_TOKEN
docker compose up -d --build
```

健康检查:

```bash
curl -s http://127.0.0.1:8080/health
```

### 1.2 Base URL

```
https://mail.example.com
```

下文用 `{BASE}` 表示。

### 1.3 统一响应格式

```json
{
  "code": 0,
  "message": "ok",
  "data": {}
}
```

| 字段 | 说明 |
|------|------|
| `code` | `0` 成功;非 0 业务错误(通常与 HTTP 状态一致) |
| `message` | 人类可读说明 |
| `data` | 载荷;失败时为 `null` |

### 1.4 鉴权

除 `GET /health`、`GET /v1/config` 外,管理类接口需要 **API Token**。

支持三种方式(任选其一):

```http
Authorization: Bearer <API_TOKEN>
```

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

```http
GET /v1/mails?to=a@b.com&token=<API_TOKEN>
```

| 环境变量 | 用途 |
|----------|------|
| `API_TOKEN` | 第三方管理 / 读信 API |
| `INBOUND_TOKEN` | Worker 推送 `POST /v1/inbound`;为空则回退到 `API_TOKEN` |

未设置 `API_TOKEN` 时接口开放(仅建议本地调试)。

---

## 2. 接口一览

| 方法 | 路径 | 鉴权 | 说明 |
|------|------|------|------|
| GET | `/health` | 无 | 健康检查 |
| GET | `/v1/config` | 无 | 运行时配置探测 |
| GET | `/v1/domains` | API | 域名列表 |
| POST | `/v1/domains` | API | 创建域名 |
| GET | `/v1/domains/{id}` | API | 域名详情 |
| PATCH | `/v1/domains/{id}` | API | 更新域名 |
| DELETE | `/v1/domains/{id}` | API | 删除域名 |
| POST | `/v1/mailboxes/generate` | API | **生成邮箱**(含 1–9 级子域) |
| GET | `/v1/mailboxes` | API | 邮箱列表 |
| GET | `/v1/mailboxes/{id}` | API | 邮箱详情 |
| GET | `/v1/mailboxes/by-address/{address}` | API | 按地址查邮箱 |
| DELETE | `/v1/mailboxes/{id}` | API | 删除邮箱记录 |
| POST | `/v1/inbound` | Inbound | **Worker 推送入口** |
| GET/POST | `/v1/mails` | API | 列信(可选 consume) |
| GET | `/v1/mails/{id}` | API | 单封详情(不删除) |
| DELETE | `/v1/mails/{id}` | API | 删除单封 |
| DELETE | `/v1/mails` | API | 清空邮箱/全部 |
| GET/POST | `/mails` | API | **兼容 Bettermail**:拉信即删 |

---

## 3. 健康与配置

### 3.1 `GET /health`

```bash
curl -s {BASE}/health
```

```json
{
  "status": "ok",
  "service": "bettermail-post-office",
  "time": "2026-07-22T00:00:00.000Z"
}
```

### 3.2 `GET /v1/config`

```bash
curl -s {BASE}/v1/config
```

```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "needsAuth": true,
    "needsInboundAuth": true,
    "allowListAll": false,
    "acceptUnknownMailbox": true,
    "autoCreateMailbox": true,
    "mailTtlSeconds": 604800,
    "maxSubdomainLevels": 9
  }
}
```

---

## 4. 域名管理

收信域名需先登记(用于生成邮箱与域名启停)。

### 4.1 创建域名 `POST /v1/domains`

```bash
curl -s -X POST {BASE}/v1/domains \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"example.com","enabled":true,"maxSubdomainLevels":9,"note":"prod"}'
```

**Body**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `name` | string | 是 | 根域名,如 `example.com` |
| `enabled` | boolean | 否 | 默认 `true` |
| `maxSubdomainLevels` | int | 否 | 该域允许的最大随机子域级数 **0–9**,默认 `9` |
| `note` | string\|null | 否 | 备注 |

> 生成时 `subdomainLevels` **不能超过** 域名的 `maxSubdomainLevels`。要做 9 级随机邮箱,登记/更新时必须 `maxSubdomainLevels: 9`。

**成功 `201`**

```json
{
  "code": 0,
  "message": "created",
  "data": {
    "id": "uuid",
    "name": "example.com",
    "enabled": true,
    "note": "prod",
    "maxSubdomainLevels": 9,
    "createdAt": "...",
    "updatedAt": "..."
  }
}
```

### 4.2 列表 / 详情 / 更新 / 删除

```bash
# 列表
curl -s {BASE}/v1/domains -H "Authorization: Bearer $API_TOKEN"

# 详情
curl -s {BASE}/v1/domains/{id} -H "Authorization: Bearer $API_TOKEN"

# 禁用
curl -s -X PATCH {BASE}/v1/domains/{id} \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled":false}'

# 删除
curl -s -X DELETE {BASE}/v1/domains/{id} -H "Authorization: Bearer $API_TOKEN"
```

---

## 5. 邮箱生成(核心)

### 5.1 `POST /v1/mailboxes/generate`

生成一个或多个邮箱地址。`subdomainLevels` 控制 **@ 右侧随机子域名级数**(0–9)。

| 级数 | 地址形态 |
|------|----------|
| `0` | `abc123@example.com` |
| `1` | `abc123@x7k2.example.com` |
| `3` | `abc123@a1.b2.c3.example.com` |
| `9` | `abc123@l1.l2.…l9.example.com` |

```bash
# 示例:生成 1 个「9 级随机子域」邮箱
curl -s -X POST {BASE}/v1/mailboxes/generate \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "example.com",
    "count": 1,
    "subdomainLevels": 9,
    "localLength": 8,
    "labelLength": 4,
    "prefix": "",
    "ttlSeconds": 86400,
    "tags": ["campaign-a"]
  }'
```

```bash
# 示例:一次生成 3 个「2 级子域」邮箱
curl -s -X POST {BASE}/v1/mailboxes/generate \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "example.com",
    "count": 3,
    "subdomainLevels": 2,
    "localLength": 8,
    "labelLength": 4
  }'
```

**Body**

| 字段 | 类型 | 必填 | 默认 | 说明 |
|------|------|------|------|------|
| `domain` | string | 是 | — | 已登记域名 |
| `count` | int | 否 | 1 | 生成数量 1–100 |
| `subdomainLevels` | int | 否 | 0 | 随机子域级数 **0–9** |
| `localLength` | int | 否 | 8 | 本地部分随机长度 3–32(未指定 `localPart` 时) |
| `labelLength` | int | 否 | 4 | 每一级子域 label 长度 2–16 |
| `localPart` | string | 否 | 随机 | 固定本地名(仍可随机子域) |
| `prefix` | string | 否 | `""` | 本地名前缀 |
| `ttlSeconds` | int\|null | 否 | null | 邮箱过期秒数;过期后拒绝新信 |
| `tags` | string[] | 否 | `[]` | 业务标签 |

**成功 `201`**

```json
{
  "code": 0,
  "message": "generated",
  "data": {
    "count": 1,
    "list": [
      {
        "id": "uuid",
        "address": "k8m2p1qx@a3b1.c9d2.example.com",
        "domain": "example.com",
        "localPart": "k8m2p1qx",
        "subdomain": "a3b1.c9d2",
        "subdomainLevels": 2,
        "tags": ["campaign-a"],
        "expiresAt": "2026-07-23T00:00:00.000Z",
        "createdAt": "...",
        "lastMailAt": null,
        "mailCount": 0
      }
    ]
  }
}
```

> **DNS 注意**:多级子域要能收信,需在 Cloudflare 配置对应 MX / 通配。  
> `*.example.com` 通常只覆盖一层;深层级请按实际级数配置 DNS,或把随机性主要放在 `localPart`。

### 5.2 列表与查询

```bash
# 分页列表
curl -s "{BASE}/v1/mailboxes?domain=example.com&limit=50&offset=0" \
  -H "Authorization: Bearer $API_TOKEN"

# 模糊搜索
curl -s "{BASE}/v1/mailboxes?q=k8m2" -H "Authorization: Bearer $API_TOKEN"

# 按完整地址
curl -s "{BASE}/v1/mailboxes/by-address/k8m2p1qx%40a3b1.c9d2.example.com" \
  -H "Authorization: Bearer $API_TOKEN"
```

### 5.3 删除邮箱记录

```bash
curl -s -X DELETE {BASE}/v1/mailboxes/{id} -H "Authorization: Bearer $API_TOKEN"
```

仅删除邮箱元数据,不自动清空已收邮件(可用 `DELETE /v1/mails?to=`)。

---

## 6. Worker 入站推送

### 6.1 `POST /v1/inbound`

Cloudflare Worker 在 `POST_OFFICE_URL` 模式下,将解析后的邮件 POST 到此接口。  
第三方也可模拟推送做联调。

**鉴权**:`INBOUND_TOKEN` 或 `API_TOKEN`。

```bash
curl -s -X POST {BASE}/v1/inbound \
  -H "Authorization: Bearer $INBOUND_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "11111111-1111-1111-1111-111111111111",
    "receivedAt": "2026-07-22T12:00:00.000Z",
    "from": "sender@gmail.com",
    "to": "k8m2p1qx@a3b1.c9d2.example.com",
    "subject": "验证码 123456",
    "messageId": "<msg@gmail.com>",
    "date": "Wed, 22 Jul 2026 12:00:00 +0000",
    "text": "您的验证码是 123456",
    "html": "<p>您的验证码是 123456</p>",
    "headers": { "subject": "验证码 123456" },
    "attachments": [],
    "rawSize": 1024
  }'
```

**Body(与 Bettermail `StoredEmail` 一致)**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `id` | string | 否 | UUID;缺省由服务端生成;重复 id 幂等 |
| `receivedAt` | string | 否 | ISO8601 |
| `from` | string | 建议 | 发件人 |
| `to` | string | **是** | 收件地址 |
| `subject` | string | 否 | 主题 |
| `messageId` | string\|null | 否 | Message-ID |
| `date` | string\|null | 否 | 原始 Date |
| `text` | string\|null | 否 | 纯文本 |
| `html` | string\|null | 否 | HTML |
| `headers` | object | 否 | 头字段 map |
| `attachments` | array | 否 | 见下 |
| `rawBase64` | string | 否 | 原始 MIME base64(有大小上限) |
| `rawSize` | number | 否 | 原始字节数 |

**attachments[]**

| 字段 | 类型 |
|------|------|
| `filename` | string\|null |
| `mimeType` | string |
| `size` | number |
| `contentId` | string\|null |
| `contentBase64` | string(可选,小附件) |

**成功**

```json
{
  "code": 0,
  "message": "accepted",
  "data": {
    "id": "11111111-1111-1111-1111-111111111111",
    "to": "k8m2p1qx@a3b1.c9d2.example.com",
    "created": true,
    "receivedAt": "2026-07-22T12:00:00.000Z"
  }
}
```

| 环境变量 | 行为 |
|----------|------|
| `ACCEPT_UNKNOWN_MAILBOX=1` | 未预生成的地址也可入库(默认) |
| `AUTO_CREATE_MAILBOX=1` | 未知地址自动建邮箱记录(默认) |
| `ACCEPT_UNKNOWN_MAILBOX=0` | 仅预生成邮箱可收;否则 `403` |

---

## 7. 邮件读写(第三方)

邮件持久化在 **SQLite**(`DB_PATH`,默认 `/data/post-office.sqlite`)。  
Worker 推送 / 模拟入站写入后,可按邮箱查询列表、看正文、删除。

### 7.1 列表查询 `GET|POST /v1/mails`

默认 **不删除**,返回摘要列表(`fields=summary`),支持分页 / 检索 / 排序。

```bash
# 第 1 页,每页 20,按时间倒序,关键字搜索
curl -s "{BASE}/v1/mails?to=user@example.com&page=1&pageSize=20&q=验证码&sortBy=receivedAt&sortOrder=desc" \
  -H "Authorization: Bearer $API_TOKEN"

# offset 分页 + 发件人精确过滤
curl -s "{BASE}/v1/mails?to=user@example.com&limit=20&offset=40&from=noreply@github.com" \
  -H "Authorization: Bearer $API_TOKEN"

# 需要列表即带全文时
curl -s "{BASE}/v1/mails?to=user@example.com&fields=full" \
  -H "Authorization: Bearer $API_TOKEN"
```

### 7.2 列表并消费 `consume=1`

与旧 Bettermail 一致:返回 **完整** `StoredEmail` 后从 SQLite 删除(队列语义,默认最旧优先)。

```bash
curl -s "{BASE}/v1/mails?to=user@example.com&limit=50&consume=1" \
  -H "Authorization: Bearer $API_TOKEN"
```

或 POST:

```bash
curl -s -X POST {BASE}/v1/mails \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"to":"user@example.com","limit":50,"consume":true}'
```

**Query / Body**

| 字段 | 说明 |
|------|------|
| `to` / `email` / `mailbox` | 收件地址;`ALLOW_LIST_ALL=1` 时可省略拉全站 |
| `from` | 发件人精确匹配(规范化小写) |
| `q` / `search` | 模糊检索:主题 / 发件人 / 收件人 / 正文 |
| `limit` / `pageSize` | 每页条数,默认 50,最大见配置 |
| `offset` | 跳过条数(与 `page` 二选一,`page` 优先) |
| `page` | 从 1 起的页码;设置后 `offset=(page-1)*pageSize` |
| `sortBy` / `sort` | `receivedAt`(默认)\| `from` \| `subject` \| `to` |
| `sortOrder` / `order` | `asc` \| `desc`;浏览默认 `desc`,consume 默认 `asc` |
| `fields` | `summary`(默认列表)\| `full`(完整邮件);consume 强制 full |
| `consume` | `1`/`true` 时读后删 |

**列表响应(summary)**

```json
{
  "code": 0,
  "message": "ok",
  "data": {
    "list": [
      {
        "id": "uuid",
        "receivedAt": "2026-07-22T12:00:00.000Z",
        "from": "a@b.com",
        "to": "user@example.com",
        "subject": "验证码 123456",
        "messageId": "<...>",
        "date": "...",
        "rawSize": 1024,
        "hasHtml": true,
        "hasText": true,
        "attachmentCount": 0,
        "preview": "您的验证码是 123456"
      }
    ],
    "to": "user@example.com",
    "from": null,
    "q": "验证码",
    "total": 42,
    "limit": 20,
    "offset": 0,
    "page": 1,
    "pageSize": 20,
    "pageCount": 3,
    "hasMore": true,
    "sortBy": "receivedAt",
    "sortOrder": "desc",
    "fields": "summary",
    "consume": false
  }
}
```

### 7.3 邮件内容 `GET /v1/mails/{id}`

返回完整 `StoredEmail`(含 `text` / `html` / `headers` / `attachments` / 可选 `rawBase64`),**不删除**。

```bash
curl -s {BASE}/v1/mails/{id} -H "Authorization: Bearer $API_TOKEN"
```

### 7.4 删除

```bash
# 删一封
curl -s -X DELETE {BASE}/v1/mails/{id} -H "Authorization: Bearer $API_TOKEN"

# 批量删除
curl -s -X DELETE {BASE}/v1/mails \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ids":["id1","id2"]}'

# 或 query: ?ids=id1,id2
curl -s -X DELETE "{BASE}/v1/mails?ids=id1,id2" \
  -H "Authorization: Bearer $API_TOKEN"

# 清空某邮箱全部邮件
curl -s -X DELETE "{BASE}/v1/mails?to=user@example.com" \
  -H "Authorization: Bearer $API_TOKEN"
```

### 7.5 兼容旧客户端 `GET|POST /mails`

固定 **consume=true**(拉走即删),响应含 `list/to/hasMore`。

```bash
curl -s "{BASE}/mails?to=user@example.com&limit=20" \
  -H "Authorization: Bearer $API_TOKEN"
```

---

## 8. 第三方集成示例

### 8.1 Python

```python
import requests

BASE = "https://mail.example.com"
TOKEN = "your-api-token"
H = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}

# 1) 登记域名
requests.post(f"{BASE}/v1/domains", headers=H, json={"name": "example.com"}).json()

# 2) 生成 9 级随机子域邮箱
r = requests.post(f"{BASE}/v1/mailboxes/generate", headers=H, json={
    "domain": "example.com",
    "count": 1,
    "subdomainLevels": 9,
    "localLength": 8,
    "labelLength": 4,
})
addr = r.json()["data"]["list"][0]["address"]
print("mailbox:", addr)

# 3a) 列表浏览(不删)
page = requests.get(
    f"{BASE}/v1/mails",
    headers=H,
    params={"to": addr, "page": 1, "pageSize": 20, "sortBy": "receivedAt", "sortOrder": "desc"},
).json()
print("total:", page["data"]["total"])

# 3b) 轮询消费验证码信(拉走即删)
mails = requests.get(
    f"{BASE}/v1/mails",
    headers=H,
    params={"to": addr, "limit": 10, "consume": 1},
).json()
for m in mails["data"]["list"]:
    print(m["from"], m["subject"], m["text"])
```

### 8.2 Node.js / fetch

```js
const BASE = "https://mail.example.com";
const token = process.env.API_TOKEN;
const headers = {
  Authorization: `Bearer ${token}`,
  "Content-Type": "application/json",
};

async function generateMailbox(domain, levels = 2) {
  const res = await fetch(`${BASE}/v1/mailboxes/generate`, {
    method: "POST",
    headers,
    body: JSON.stringify({ domain, count: 1, subdomainLevels: levels }),
  });
  const j = await res.json();
  if (j.code !== 0) throw new Error(j.message);
  return j.data.list[0].address;
}

async function consumeMails(to, limit = 20) {
  const q = new URLSearchParams({ to, limit: String(limit), consume: "1" });
  const res = await fetch(`${BASE}/v1/mails?${q}`, { headers });
  const j = await res.json();
  if (j.code !== 0) throw new Error(j.message);
  return j.data.list;
}
```

### 8.3 Go

```go
// POST /v1/mailboxes/generate
// GET  /v1/mails?to=...&consume=1
// Header: Authorization: Bearer <API_TOKEN>
```

### 8.4 与旧 Bettermail SDK 兼容

若已有代码只打 `GET /mails?to=`:

1. 将 Base URL 从 Worker 域名改为邮局域名;或  
2. 继续用 Worker 旧模式(不设 `POST_OFFICE_URL`)。

邮局提供同名 `/mails` 消费接口,token 语义兼容(Bearer / X-API-Key / `?token=`)。

---

## 9. Worker 对接清单

1. Docker 邮局上线,记下公网 URL 与 `INBOUND_TOKEN`。  
2. CF Worker **Variables**:
   - `POST_OFFICE_URL` = `https://mail.example.com/v1/inbound`  
     (也可只写 `https://mail.example.com`,Worker 会自动补 `/v1/inbound`)
   - `POST_OFFICE_TOKEN` = 与 `INBOUND_TOKEN` 相同  
3. **不要**依赖 Worker 的 `/mails`(推送模式下返回 503)。  
4. 客户端改读邮局 `{BASE}/v1/mails` 或 `{BASE}/mails`。  
5. Email Routing Catch-all 仍指向该 Worker。  
6. 关闭推送:删除 `POST_OFFICE_URL`,恢复绑定 D1/KV 即可回到本地邮局。

---

## 10. 错误码约定

| HTTP | code | 典型 message |
|------|------|----------------|
| 400 | 400 | 参数错误、无效域名/地址 |
| 401 | 401 | unauthorized |
| 403 | 403 | mailbox not registered / expired / domain disabled |
| 404 | 404 | not found |
| 409 | 409 | domain already exists |
| 500 | 500 | internal error |

---

## 11. 环境变量完整表

| 变量 | 默认 | 说明 |
|------|------|------|
| `HOST` | `0.0.0.0` | 监听地址 |
| `PORT` | `8080` | 端口 |
| `DATA_DIR` | `/data` | 数据目录 |
| `DB_PATH` | `/data/post-office.sqlite` | SQLite 路径 |
| `API_TOKEN` | 空 | 管理/读信鉴权 |
| `INBOUND_TOKEN` | =API_TOKEN | Worker 入站鉴权 |
| `ALLOW_LIST_ALL` | `0` | 允许不传 `to` 列全站邮件 |
| `ACCEPT_UNKNOWN_MAILBOX` | `1` | 接受未登记地址 |
| `AUTO_CREATE_MAILBOX` | `1` | 未登记时自动建邮箱 |
| `MAIL_TTL_SECONDS` | `604800` | 邮件保留秒数;`0` 永不过期 |
| `DEFAULT_LIMIT` | `50` | 默认分页 |
| `MAX_LIMIT` | `200` | 最大分页 |
| `MAX_ATTACHMENT_BYTES` | `524288` | 附件内容保留阈值 |
| `MAX_RAW_BASE64_CHARS` | `3145728` | rawBase64 最大字符数 |

---

## 12. 推荐业务流(第三方)

```
1. POST /v1/domains  { name, maxSubdomainLevels: 9 }
2. POST /v1/mailboxes/generate  { domain, subdomainLevels: 9, count: 1 }
   → data.list[0].address
3. 把 address 填到注册页 / 表单
4. 外站发信 → CF Email Routing → Worker → POST /v1/inbound(SQLite 落库)
5. 收信:
   - GET /v1/mails?to=address&page=1          (列表,不删)
   - GET /v1/mails/{id}                       (正文,不删)
   - GET /v1/mails?to=address&consume=1       (验证码:拉走即删)
   - GET /mails?to=address                    (兼容旧客户端,拉走即删)
```

机器可读契约见同目录上级:`openapi.yaml`。