# LanQin Email API LanQin Email exposes versioned integration APIs under `/api/open/v1`. The original `/api/open` paths remain compatibility aliases. 这些接口用于外部系统集成,稳定版本入口为 `/api/open/v1`。原 `/api/open` 路径继续作为兼容别名。它们不是匿名公开接口,只接受 API Token,不接受浏览器登录 Session Cookie。 Machine-readable OpenAPI 3.1 contract: [`docs/openapi.json`](./openapi.json). 机器可读的 OpenAPI 3.1 契约见 [`docs/openapi.json`](./openapi.json)。 ## Base URL All API endpoints are relative to your LanQin Email instance: 所有接口地址都相对于你的 LanQin Email 实例: ``` https://your-instance.example.com ``` ## HTTP Status Codes The API uses standard HTTP status codes: 接口使用标准 HTTP 状态码: | Code 状态码 | Meaning 含义 | |------|---------| | `200 OK` | Request succeeded / 请求成功 | | `201 Created` | Resource created successfully / 资源创建成功 | | `400 Bad Request` | Invalid request parameters or validation error / 请求参数无效或校验失败 | | `401 Unauthorized` | Missing or invalid API token / 缺少或无效的 API Token | | `403 Forbidden` | Token lacks required permissions / Token 缺少所需权限 | | `404 Not Found` | Resource does not exist / 资源不存在 | | `409 Conflict` | Idempotency key conflict or concurrent status change / 幂等键冲突或状态并发变化 | | `429 Too Many Requests` | Rate limit exceeded / 超过频率限制 | | `500 Internal Server Error` | Server error / 服务器错误 | Validation failures — including a duplicate domain name — return `400 Bad Request`, not `409`. 校验失败(包括域名重复)会返回 `400 Bad Request`,而不是 `409`。 ## Error Responses All API errors return JSON with this structure: 所有接口的错误都以如下 JSON 结构返回: ```json { "error": "error message" } ``` **Important**: The API uses `DisallowUnknownFields()` for JSON parsing. Sending fields not defined in the request schema will result in a `400 Bad Request` error. **重要提示**:接口在解析 JSON 时启用了 `DisallowUnknownFields()`。如果请求体中包含 schema 未定义的字段,将返回 `400 Bad Request` 错误。 Examples: 示例: ```json { "error": "invalid token" } ``` ```json { "error": "domain not found" } ``` ```json { "error": "localPart is required" } ``` ## Authentication Open API requests must use a Bearer API Token: Open API 请求必须使用 Bearer API Token: ```http Authorization: Bearer lq_xxx ``` Create tokens in **Profile / API Token**. The plain token is shown only once after creation, so store it securely and revoke it if it may have leaked. 请在 **个人中心 / API Token** 中创建 Token。明文 Token 只会在创建后显示一次,请安全保存;如果怀疑泄露,应立即撤销并重新创建。 Created token example: 创建后的 Token 示例: ```json { "token": "lq_xxx" } ``` Tokens created without a custom expiration default to 90 days. You can disable or revoke tokens from the same profile page. 如果没有自定义到期时间,Token 默认 90 天后过期。你可以在同一个个人中心页面中禁用或撤销 Token。 Each token has independent scopes. Scopes only reduce the permissions of the owning user; they never grant permissions the user does not already have. Existing tokens created before scope support are migrated to `*` for compatibility. 每个 Token 都有独立 scope。scope 只会收缩 Token 所属用户已有的权限,不会授予用户原本没有的权限。scope 功能上线前创建的 Token 会迁移为 `*`,以保持兼容。 | Scope | Purpose | |---|---| | `domains:read` / `domains:write` | View or manage sending domains | | `mailboxes:read` / `mailboxes:write` | View or manage mailboxes; password reset is a write operation | | `messages:read` / `messages:send` / `messages:manage` | Read messages/status, send, or retry/cancel | | `aliases:read` / `aliases:write` | View or manage aliases | | `dns:read` / `dns:check` | View required records or execute DNS checks | | `*` | Compatibility wildcard; avoid for new integrations | ## Permissions All Open API endpoints require an API token with appropriate permissions and role requirements: 所有 Open API 接口都需要具备相应权限和角色的 API Token: | Endpoint group | Required scope | Role | |---|---|---| | Domains | `domains:read` or `domains:write` | admin | | Mailboxes | `mailboxes:read` or `mailboxes:write` | admin | | DNS | `dns:read` or `dns:check` | admin | | Aliases | `aliases:read` or `aliases:write` | admin | | Send / status / messages | `messages:send`, `messages:read`, or `messages:manage` | user or admin | **Notes:** - Admin endpoints check for `requireAdminAccess` (role must be `admin`). - Mail sending/reading endpoints work for regular users but only for mailboxes they own. - Users can only read messages from their own active mailboxes. **说明:** - 域名和邮箱管理接口会检查 `requireAdminAccess`(角色必须为 `admin`)。 - 发信/读信接口对普通用户也可用,但只能操作自己拥有的邮箱。 - 用户只能读取自己拥有的 active 邮箱中的邮件。 ## Domains ### List domains ```http GET /api/open/v1/domains Authorization: Bearer lq_xxx ``` **Status:** `200 OK` **Response:** ```json { "items": [ { "id": "dom_xxx", "name": "example.com", "status": "active", "dkimSelector": "lanqin", "dkimPublicKey": "v=DKIM1; k=rsa; p=MIIBIjANBgkq...", "dnsStatus": "unchecked", "dnsCheckedAt": null, "createdAt": "2026-06-29T00:00:00Z" } ] } ``` **Field descriptions:** - `status`: `active` or `disabled` - `dnsStatus`: `unchecked` (initial), `ok` (all DNS records verified), or `error` (verification failed) - `dnsCheckedAt`: Timestamp of last DNS check (nullable) - `dkimPublicKey`: Public key for DKIM signing (omitted in some contexts) **字段说明:** - `status`:`active` 或 `disabled` - `dnsStatus`:`unchecked`(初始)、`ok`(所有 DNS 记录校验通过)或 `error`(校验失败) - `dnsCheckedAt`:上次 DNS 检查的时间戳(可为 null) - `dkimPublicKey`:用于 DKIM 签名的公钥(部分场景下会省略) **Note:** This endpoint returns all domains without pagination. **注意:** 该接口一次性返回所有域名,不分页。 ### Create domain ```http POST /api/open/v1/domains Authorization: Bearer lq_xxx Content-Type: application/json { "name": "example.com" } ``` **Status:** `201 Created` **Response:** ```json { "id": "dom_xxx", "name": "example.com", "status": "active", "dkimSelector": "lanqin", "dkimPublicKey": "v=DKIM1; k=rsa; p=MIIBIjANBgkq...", "dnsStatus": "unchecked", "dnsCheckedAt": null, "createdAt": "2026-06-29T00:00:00Z" } ``` **Notes:** - Domain name is automatically normalized to lowercase - DKIM keys are generated automatically - Initial `dnsStatus` is `unchecked` **说明:** - 域名会自动规范化为小写 - DKIM 密钥会自动生成 - 初始 `dnsStatus` 为 `unchecked` ### Get domain ```http GET /api/open/v1/domains/{id} Authorization: Bearer lq_xxx ``` **Status:** `200 OK` or `404 Not Found` **Response:** Same as domain object in list response. **响应:** 与列表接口中的 domain 对象结构相同。 ### Update domain status ```http POST /api/open/v1/domains/{id} Authorization: Bearer lq_xxx Content-Type: application/json { "status": "active" } ``` **Status:** `200 OK` or `404 Not Found` **Request body:** - `status`: Must be `active` or `disabled` **请求体:** - `status`:必须为 `active` 或 `disabled` **Response:** Updated domain object. **响应:** 更新后的 domain 对象。 **Note:** This endpoint uses `POST` (not `PATCH`/`PUT`) for simplicity in client implementations. **注意:** 该接口使用 `POST`(而非 `PATCH`/`PUT`),以简化客户端实现。 ### Delete domain ```http DELETE /api/open/v1/domains/{id} Authorization: Bearer lq_xxx ``` **Status:** `200 OK`, `404 Not Found`, or `400 Bad Request` **Response:** ```json { "ok": true } ``` **Error cases:** - `400`: Domain still has mailboxes (must delete mailboxes first) - `404`: Domain not found **错误情况:** - `400`:域名下仍有邮箱(需先删除邮箱) - `404`:域名不存在 ## Mailboxes ### List mailboxes ```http GET /api/open/v1/mailboxes Authorization: Bearer lq_xxx ``` **Status:** `200 OK` **Response:** ```json { "items": [ { "id": "mbx_xxx", "userId": "usr_xxx", "userEmail": "alice@example.com", "domainId": "dom_xxx", "localPart": "alice", "address": "alice@example.com", "displayName": "Alice", "quotaMb": 1024, "status": "active", "createdAt": "2026-06-29T00:00:00Z" } ] } ``` **Note:** This endpoint returns all mailboxes without pagination. **注意:** 该接口一次性返回所有邮箱,不分页。 ### Create mailbox ```http POST /api/open/v1/mailboxes Authorization: Bearer lq_xxx Content-Type: application/json { "domainId": "dom_xxx", "localPart": "alice", "displayName": "Alice", "password": "Password123!", "quotaMb": 1024, "ownerEmail": "alice@example.com" } ``` **Status:** `201 Created` **Request fields:** | Field 字段 | Required 必填 | Description 说明 | |-------|----------|-------------| | `domainId` | Yes | ID of an existing domain / 已存在域名的 ID | | `localPart` | Yes | Local part of the address. Normalized to lowercase; only `a-z 0-9 . _ % + -` are kept, other characters stripped / 地址本地部分。会规范化为小写,仅保留 `a-z 0-9 . _ % + -`,其余字符会被移除 | | `password` | Yes | At least 8 characters. Used as the mailbox password / 至少 8 位,用作邮箱密码 | | `displayName` | No | Defaults to the mailbox address if omitted / 省略时默认使用邮箱地址 | | `quotaMb` | No | Mailbox quota in MB / 邮箱配额(MB) | | `ownerEmail` | No | Owner's email. See owner resolution below / 拥有者邮箱,见下方拥有者解析规则 | | `userId` | No | Bind to an existing user by ID. Takes precedence over `ownerEmail` / 绑定到已有用户的 ID,优先级高于 `ownerEmail` | **Owner resolution:** - If `userId` is provided, the mailbox is bound to that existing user (must be an active user). - Otherwise, if `ownerEmail` is provided, LanQin Email looks up an active user with that email. - If `ownerEmail` is omitted, the mailbox address is used as the owner email. - If no active user with that email exists, a new user is created automatically. **拥有者解析规则:** - 如果传了 `userId`,邮箱会绑定到该已有用户(必须是启用状态的用户)。 - 否则,如果传了 `ownerEmail`,系统会查找该邮箱对应的启用用户。 - 如果省略 `ownerEmail`,则使用邮箱地址作为拥有者邮箱。 - 如果不存在对应的启用用户,系统会自动创建一个新用户。 **Response:** Created mailbox object (same shape as list response). **响应:** 创建后的 mailbox 对象(结构与列表接口相同)。 ### Get mailbox ```http GET /api/open/v1/mailboxes/{id} Authorization: Bearer lq_xxx ``` **Status:** `200 OK` or `404 Not Found` **Response:** Mailbox object (same shape as list response). **响应:** mailbox 对象(结构与列表接口相同)。 ### Update mailbox ```http POST /api/open/v1/mailboxes/{id} Authorization: Bearer lq_xxx Content-Type: application/json { "displayName": "Alice Work", "quotaMb": 2048, "status": "active", "userId": "usr_xxx" } ``` **Status:** `200 OK` or `404 Not Found` All fields are optional. Omitted (or empty / non-positive) fields keep their current value. `status` can be `active` or `disabled`. When `userId` is provided, the target user must exist and be active. 所有字段均可选。省略(或为空 / 非正数)的字段会保留原值。`status` 可为 `active` 或 `disabled`。如果传了 `userId`,目标用户必须存在且处于启用状态。 **Response:** Updated mailbox object. **响应:** 更新后的 mailbox 对象。 ### Delete mailbox ```http DELETE /api/open/v1/mailboxes/{id} Authorization: Bearer lq_xxx ``` **Status:** `200 OK`, `404 Not Found`, or `400 Bad Request` **Response:** ```json { "ok": true } ``` **Notes:** - Deleting a mailbox also deletes all of its messages. - If the token owner is deleting their own mailbox, it cannot be their last remaining mailbox (returns `400`). **说明:** - 删除邮箱会同时删除该邮箱下的所有邮件。 - 如果 Token 拥有者删除的是自己的邮箱,则不能删除最后一个邮箱(会返回 `400`)。 ## Send Mail ```http POST /api/open/v1/send Authorization: Bearer lq_xxx Idempotency-Key: invoice-2026-0001 Content-Type: application/json { "mailboxId": "mbx_xxx", "to": ["bob@example.com"], "cc": [], "bcc": [], "subject": "Hello", "text": "Plain text body", "html": "
HTML body
", "attachments": [ { "filename": "report.pdf", "contentType": "application/pdf", "contentBase64": "JVBERi0xLjQK..." } ] } ``` **Status:** `201 Created` **Request fields:** | Field 字段 | Required 必填 | Description 说明 | |-------|----------|-------------| | `mailboxId` | Yes | Sending mailbox ID (must be owned by the token user) / 发信邮箱 ID(必须属于 Token 拥有者) | | `to` | Yes | Recipient addresses (at least one recipient across to/cc/bcc) / 收件人地址(to/cc/bcc 至少需有一个收件人) | | `cc` | No | CC addresses / 抄送地址 | | `bcc` | No | BCC addresses / 密送地址 | | `subject` | No | Message subject / 邮件主题 | | `text` | No | Plain text body / 纯文本正文 | | `html` | No | HTML body / HTML 正文 | | `attachments` | No | List of attachments (see below) / 附件列表(见下方) | **Attachment fields:** | Field 字段 | Description 说明 | |-------|-------------| | `filename` | Attachment file name / 附件文件名 | | `contentType` | MIME type, e.g. `application/pdf` / MIME 类型,如 `application/pdf` | | `contentBase64` | Base64-encoded file content / Base64 编码的文件内容 | Total attachment size is limited by the sender's permission group (`maxAttachmentMb`, default 25 MB). Exceeding it returns `400`. 附件总大小受发信人所在权限组限制(`maxAttachmentMb`,默认 25 MB)。超出会返回 `400`。 **Response:** ```json { "id": "mail_xxx", "queueId": "snd_xxx", "status": "queued", "messageId": "mail_xxx", "rfcMessageId": "