mirror of
https://git.openapi.site/https://github.com/desirecore/market.git
synced 2026-10-21 05:44:20 +08:00
fix(mail-operations): 按现行邮件服务路由修正技能文档(1.0.6) (#194)
## 背景 / Background
发票整理 Agent 在隔离开发实例里真机整理 QQ 邮箱时,按内置技能 `mail-operations` 的写法调用 IMAP
附件下载,拿到 400 `Missing required parameters`:技能写的请求体是 `{email, uid, folder,
partId}`,而 `POST /imap/attachment` 从 2026-03 创建起就只认 `{email, messageId,
attachmentId, folder?}`。顺着这一处,对照 desirecore origin/dev(`c1e2c5ac0`)的
`lib/mail-service/` 与 `MailOperations` 工具把整份技能逐条核对了一遍。
While a real-mailbox run of the invoice agent was downloading QQ Mail
attachments, the builtin `mail-operations` skill's IMAP attachment body
(`{email, uid, folder, partId}`) returned 400; the route has always
taken `{email, messageId, attachmentId, folder?}`. This PR re-audits the
whole skill against the current mail service routes.
## 改动 / Changes
共修正 27 处不一致:会直接导致调用失败 9 处、会误导能力判断 10 处、措辞 8 处。主要有:
- **调用入口**:改为 `MailOperations`(path 带 `/api`,capability
由工具注入);原文的固定端口、「无需认证」、`curl -k` 都不成立
- **附件**:body 字段更正;必须带 `save_to` 落盘(不带会被工具拒绝返回),回执给出路径、字节数、SHA-256
- **参数位置**:`messages/fetch`、`/sync`、IMAP 草稿发送的 `email`(以及 `/sync` 的
`provider`)放查询串;IMAP 草稿收件人是逗号分隔的 `to` / `cc`,更新草稿会换成新 uid
- **folder**:IMAP 单封操作要用 `GET /imap/folders` 返回的服务器路径,列表里的归一化值会 500
- **规则**:operator 是 `matches_regex`(未知 operator 恒为 false
且创建时不校验);`archive` / `move_to_folder` / `forward_to` / `star` 是空操作
- **搜索**:三个 provider 都支持,只查本地缓存、`q` 只匹配主题;Gmail 全文检索改用 `messages/fetch`
的 `query`
- **IMAP 仅收信账户**:`allowUnverifiedSmtp`、`smtp_verification_failed`、409
`smtp_unverified`、`verify-smtp`(失败也返回 200)
- **离线队列**:不会自动入队或处理,只改本地,`processed` 计数不代表执行
- 响应字段名(`toRecipients`、`bodyPreview` 等)、同步状态文件、`/imap/test` 返回形态等措辞修正
- 版本 1.0.6;新增 `market.required_client_version: 10.0.148` 与 sidecar
`compatibility.requiredClientVersion`——`MailOperations`(10.0.95)、`save_to`
与 Outlook/IMAP search(10.0.146)、`imap:<uid>` / fetch `offset` / folder
归一化(10.0.148)分版本上线,旧客户端按新文档操作会静默失败,门控让它们保留旧版
## 验证 / Verification
- 写补丁与审查由两个独立子代理完成:审查方对 9 处致命项全部、另外 13 处抽查逐条对照代码行,均成立;审查意见(最低客户端版本、IMAP
更新草稿换 uid、`/sync` 参数、Gmail 远程检索、`verify-smtp` 返回形态、离线队列计数、响应示例矛盾)已全部采纳
- 各项最低版本用 `git tag --contains` 对照发布 tag 复核
- `translate.py --check`:en-US human-locked 且 hash 新鲜(`source_hash` 按
`compute_source_hash` 重算)
- `validate-i18n`:0 error,123 warning(与 main 基线相同)
- `validate_catalog_metadata --require-complete`:0 error
- `git diff --check` 通过;新增行只有 `example.com` 占位地址,无真实邮箱、姓名、公司名或长数字编号
🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
@@ -10,7 +10,7 @@ description: >-
|
||||
Use when 用户提到 邮件、邮箱、收件箱、发邮件、回复邮件、查邮件、Gmail、
|
||||
Outlook、QQ邮箱、163邮箱、附件、标签、草稿、自动回复、邮件规则、
|
||||
转发、抄送、未读邮件、收信、发信、邮件同步、邮件搜索。
|
||||
version: 1.0.5
|
||||
version: 1.0.6
|
||||
type: procedural
|
||||
risk_level: medium
|
||||
status: enabled
|
||||
@@ -24,7 +24,7 @@ tags:
|
||||
- smtp
|
||||
metadata:
|
||||
author: desirecore
|
||||
updated_at: '2026-06-20'
|
||||
updated_at: '2026-09-29'
|
||||
i18n:
|
||||
default_locale: en-US
|
||||
source_locale: zh-CN
|
||||
@@ -45,7 +45,7 @@ metadata:
|
||||
description: >-
|
||||
Use this skill whenever the user wants to interact with email. This includes reading inbox, sending emails, replying, searching messages, managing labels and categories, downloading attachments, setting up auto-reply rules, or triggering agents to handle incoming emails. Supports Gmail, Outlook, and IMAP/SMTP (QQ Mail, 163, Yahoo, etc.) through DesireCore's local REST API. Use when the user mentions email, mailbox, inbox, sending email, replying, checking email, Gmail, Outlook, QQ Mail, 163 Mail, attachments, labels, drafts, auto-reply, email rules, forwarding, CC, unread email, receiving, sending, email sync, or email search.
|
||||
body: ./SKILL.md
|
||||
source_hash: sha256:0ee04ebff49d92ea
|
||||
source_hash: sha256:02d90959ea1d7e29
|
||||
translated_by: human
|
||||
translated_at: '2026-05-03'
|
||||
market:
|
||||
@@ -63,6 +63,7 @@ market:
|
||||
name: DesireCore Official
|
||||
verified: true
|
||||
channel: latest
|
||||
required_client_version: 10.0.148
|
||||
---
|
||||
|
||||
# mail-operations Skill
|
||||
@@ -75,7 +76,7 @@ Send and receive email, search, manage labels, and run automation rules via a lo
|
||||
|
||||
### Capability
|
||||
|
||||
mail-operations is a **Procedural Skill** that operates email systems through DesireCore's local REST API. It supports three mailbox types—Gmail (OAuth2), Outlook (MSAL), and IMAP/SMTP (QQ, 163, Yahoo, etc.)—covering send/receive, search, label management, attachment download, draft management, and automation rules.
|
||||
mail-operations is a **Procedural Skill** that operates email systems through DesireCore's local REST API (called via the built-in `MailOperations` tool). It supports three mailbox types—Gmail (OAuth2), Outlook (MSAL), and IMAP/SMTP (QQ, 163, Yahoo, etc.)—covering send/receive, search, label management, attachment download, draft management, and automation rules.
|
||||
|
||||
### Use Cases
|
||||
|
||||
@@ -105,11 +106,12 @@ Users can reach the email management interface via:
|
||||
|
||||
## API Basics
|
||||
|
||||
- **Base URL**: `https://127.0.0.1:62000/api`
|
||||
- **Authentication**: none required (local service)
|
||||
- **Content-Type**: `application/json`
|
||||
- **SSL**: use `curl -k` to skip self-signed certificate verification
|
||||
- **Response format**: success `{"code": 1, "msg": "Success", "result": ...}`, failure `{"code": 0, "msg": "error message"}`
|
||||
- **How to call**: use the built-in `MailOperations` tool. Its parameters are `path` (a relative path starting with `/api/`, with query parameters appended directly to it), `method` (default `GET`), `body` (a JSON object), and `save_to` (only for attachment downloads, see section 6). The tool locates the local Mail Service and attaches the access credential automatically; the Agent neither needs nor can supply a token.
|
||||
- **Path form**: the endpoints below omit the `/api` prefix; add it when calling. For example, `/imap/messages?email=me%40example.com&limit=20` becomes `path: "/api/imap/messages?email=me%40example.com&limit=20"`. The only exception is the health check `/ping` (no `/api`).
|
||||
- **Do not connect to the port directly**: do not `curl` `https://127.0.0.1:62000`. The port is allocated dynamically starting at 62000 (when several DesireCore instances run, 62000 may belong to another instance), and in strict capability mode direct requests without the credential always return 401.
|
||||
- **Parameter location**: GET / DELETE parameters always go in the query string; for POST / PUT follow the tables below. The `email` of endpoints such as `messages/fetch`, mark as read/unread, Outlook single-message operations, and IMAP draft send must be in the query string; putting it in the body returns 400 `Missing email parameter`. For `/sync`, both `provider` and `email` must be in the query string: `provider` in the body returns 400 `Invalid provider`, and `email` alone in the body raises no parameter error but the sync does not take effect.
|
||||
- **Content-Type**: `application/json` (set automatically by the tool when `body` is passed)
|
||||
- **Response format**: success `{"code": 1, "msg": "Success", "result": ...}`, failure `{"code": 0, "msg": "error message"}`; some errors also carry a stable error code field `error` (e.g. `smtp_unverified`)
|
||||
|
||||
---
|
||||
|
||||
@@ -117,14 +119,14 @@ Users can reach the email management interface via:
|
||||
|
||||
The following rules have the highest priority and must be obeyed on every operation.
|
||||
|
||||
### Rule 1: Operate Only Through the Local API
|
||||
### Rule 1: Operate Only Through the MailOperations Tool
|
||||
|
||||
All email operations **must and may only** be done through `https://127.0.0.1:62000`. **Never** call an external email client or browser.
|
||||
All email operations **must and may only** be done through the built-in `MailOperations` tool, which calls the local Mail Service. **Never** call an external email client or browser, and do not connect to the local port with `curl` / `HttpRequest` (except for the large-attachment streaming-to-disk method explicitly given in a `MailOperations` error message).
|
||||
|
||||
If the API returns **401 (authorization expired)**:
|
||||
1. Tell the user the account's authorization has expired
|
||||
2. Prompt the user to re-authorize in DesireCore
|
||||
3. Gmail: `POST /api/gmail/auth/initiate?loginHint={email}`, Outlook: `POST /api/outlook/auth/initiate`
|
||||
If a call returns **401**, distinguish the cause by `msg`:
|
||||
1. Gmail / Outlook returns `Not authorized` or `Authorization expired`: tell the user the account's authorization has expired and guide them to re-authorize on the DesireCore Emails page. To start it on their behalf, call Gmail `POST /api/gmail/auth/initiate?loginHint={email}` or Outlook `POST /api/outlook/auth/initiate` and give the returned `result.authUrl` to the user to open in a browser (the service does not open a browser by itself)
|
||||
2. IMAP returns `Account not configured`: the account is missing or its configuration is corrupted; this is not an expired authorization, and the user needs to add the account again (first `/imap/test`, then `/imap/accounts`)
|
||||
3. The response carries `error: "unauthorized"` (missing or invalid Mail Service capability): the request did not go through `MailOperations`; retry with that tool
|
||||
|
||||
### Rule 2: Confirm the Account Before Operating
|
||||
|
||||
@@ -136,7 +138,7 @@ Before performing any operation, **you must first call `GET /api/accounts`** to
|
||||
### Rule 3: Auto-Sync When the Query Is Empty
|
||||
|
||||
When a query returns an empty list or cannot find the specified data:
|
||||
1. Call `POST /{provider}/messages/fetch` to sync remote data
|
||||
1. Call `POST /{provider}/messages/fetch?email={email}` to sync remote data (`email` goes in the query string)
|
||||
2. **Automatically retry** the original query
|
||||
3. Only inform the user if it is still empty
|
||||
|
||||
@@ -152,18 +154,20 @@ After a write operation (send, reply, delete, mark, label change, etc.) succeeds
|
||||
|------|-------|---------|------|
|
||||
| Authorization | OAuth2 | OAuth2 (MSAL) | password / app password |
|
||||
| Provider path | `/gmail/` | `/outlook/` | `/imap/` |
|
||||
| Message detail | path param `/{id}` | query param `?id={id}` | UID `/{uid}?folder=` |
|
||||
| Search | supported | not supported | not supported |
|
||||
| Drafts | supported (incl. list/detail) | supported (create/send) | supported (create/send) |
|
||||
| Message detail | path param `/{id}` | query param `?id={id}` | path param `/{uid}` (the list's `imap:<uid>` or a bare numeric UID) + `?folder=` |
|
||||
| Search (local cache) | supported | supported | supported |
|
||||
| Remote fetch paging | `pageToken` (no `offset`) | `skip` / `offset` | `offset` |
|
||||
| Drafts | supported (incl. list/detail) | supported (create/update/delete/send) | supported (create/update/delete/send) |
|
||||
| Attachment download | supported | supported | supported |
|
||||
| Labels/categories | native labels | Categories | local labels only |
|
||||
| Sending prerequisite | authorized | authorized | SMTP verified (`smtpStatus: verified`) |
|
||||
| Auto-rules | supported | supported | supported |
|
||||
|
||||
---
|
||||
|
||||
## Core Operations
|
||||
|
||||
In the following, `{p}` denotes the provider (`gmail`, `outlook`, `imap`); `{email}` must be URL-encoded.
|
||||
In the following, `{p}` denotes the provider (`gmail`, `outlook`, `imap`); `{email}` must be URL-encoded (`@` → `%40`, `+` → `%2B`).
|
||||
|
||||
### 1. Account Management
|
||||
|
||||
@@ -181,31 +185,41 @@ In the following, `{p}` denotes the provider (`gmail`, `outlook`, `imap`); `{ema
|
||||
| Operation | Method | Endpoint |
|
||||
|------|------|------|
|
||||
| Get preset mailbox configs | GET | `/imap/presets` — returns server configs for QQ/163/Yahoo, etc. |
|
||||
| Test IMAP connection | POST | `/imap/test` — body: `{email, password, imap: {host, port, secure}, smtp: {host, port, secure}}` |
|
||||
| Add IMAP account | POST | `/imap/accounts` — same body as above plus `displayName` |
|
||||
| Test IMAP connection | POST | `/imap/test` — body: `{email, password, imap?: {host, port, secure}, smtp?: {host, port, secure}}`; `imap` / `smtp` may be omitted when the domain has a preset. **A failed connection still returns HTTP 200**; check `result.imap.success` and `result.smtp.success` |
|
||||
| Add IMAP account | POST | `/imap/accounts` — same body as above, optionally plus `displayName` and `allowUnverifiedSmtp`; success returns 201 with `{email, smtpStatus, smtp}`. If the SMTP test fails it returns 400 by default (`error: smtp_verification_failed`); pass `allowUnverifiedSmtp: true` only when the user agrees to a receive-only account |
|
||||
| Re-verify SMTP | POST | `/imap/accounts/verify-smtp` — body: `{email}`; a failed verification also returns 200, so check `result.smtp.success` and `result.smtpStatus`: on success `smtpStatus` returns to `verified`; on failure the user needs to update the authorization code and verify again |
|
||||
|
||||
### 2. Message List and Sync
|
||||
|
||||
| Operation | Method | Endpoint | Parameters |
|
||||
|------|------|------|------|
|
||||
| Query local cache | GET | `/{p}/messages` | `email, offset, limit, folder` |
|
||||
| Remote sync | POST | `/{p}/messages/fetch` | `email, limit, folder` |
|
||||
| Manually trigger sync | POST | `/sync` | `provider, email` |
|
||||
| Query local cache | GET | `/{p}/messages` | query string: `email, offset, limit, folder`; returns `{messages, total, offset, limit}` |
|
||||
| Remote sync | POST | `/{p}/messages/fetch?email=` | `email` must be in the query string; body (or query string): `folder`, `limit` (1–1000, default 20); paging: IMAP `offset`, Outlook `skip` / `offset`, Gmail `pageToken` (Gmail returns 400 for `offset`); returns `messages` plus paging info `pagination` |
|
||||
| Manually trigger sync | POST | `/sync?provider=&email=` | parameters in the query string; **gmail / outlook only** — IMAP returns 400, use `messages/fetch` instead |
|
||||
|
||||
**folder values**: Gmail/Outlook: `inbox, sent, drafts, trash, spam, archive`; IMAP: `INBOX, Sent, Drafts, Trash`, etc.
|
||||
**IMAP remote sync is slow**: every message is downloaded and parsed in full (about 0.65 messages/second measured), so keep each batch's `limit` at 100 or less and page through older mail with `offset` (how many of the newest messages to skip).
|
||||
|
||||
**folder values**:
|
||||
- Local cache list and search: `inbox, sent, drafts, trash, spam, archive, other`, case-insensitive; server names (e.g. `INBOX`, `Sent Messages`) are accepted too. IMAP custom folders are all stored locally as `other`.
|
||||
- Remote sync: generic names (`inbox`, `sent`, ...) are mapped automatically to the actual IMAP folder, the Outlook folder, or the Gmail label; for IMAP you may also pass a `path` returned by `GET /imap/folders`. Gmail has no label for `archive`, so it fetches without filtering.
|
||||
- **IMAP single-message operations** (detail on a cache miss, mark as read/unread, delete, reply, attachment download) pass `folder` verbatim to the IMAP server, so it must be a real `path` returned by `GET /imap/folders` (e.g. `INBOX`, `Sent Messages`); it defaults to `INBOX` when omitted. The `folder` field of a list item is a normalized value (e.g. `sent`) and cannot be used as an IMAP folder name directly.
|
||||
|
||||
**Response format** (message list item):
|
||||
```json
|
||||
{
|
||||
"id": "messageID", "subject": "subject",
|
||||
"id": "messageID (imap:<uid> for IMAP)", "provider": "gmail",
|
||||
"subject": "subject",
|
||||
"from": {"name": "...", "address": "..."},
|
||||
"to": [{"name": "...", "address": "..."}],
|
||||
"date": "ISO8601", "snippet": "snippet",
|
||||
"toRecipients": [{"name": "...", "address": "..."}],
|
||||
"receivedDateTime": "ISO8601", "sentDateTime": "ISO8601",
|
||||
"bodyPreview": "snippet",
|
||||
"isRead": true, "hasAttachments": false,
|
||||
"labels": ["INBOX"], "folder": "inbox"
|
||||
"folder": "inbox", "labelIds": ["INBOX"]
|
||||
}
|
||||
```
|
||||
|
||||
`labelIds` appears only on Gmail messages and `categories` only on Outlook messages.
|
||||
|
||||
### 3. Single Message Operations
|
||||
|
||||
| Operation | Gmail | Outlook | IMAP |
|
||||
@@ -215,9 +229,9 @@ In the following, `{p}` denotes the provider (`gmail`, `outlook`, `imap`); `{ema
|
||||
| Mark as unread | POST `/{id}/unread?email=` | POST `/message/unread?id={id}&email=` | POST `/{uid}/unread?email=&folder=` |
|
||||
| Delete | DELETE `/{id}?email=` | DELETE `/message?id={id}&email=` | DELETE `/{uid}?email=&folder=` |
|
||||
|
||||
> All paths are prefixed with `/api/{provider}/messages` (Gmail/IMAP) or `/api/outlook/` (Outlook's special routing).
|
||||
> All paths are prefixed with `/api/{provider}/messages` (Gmail/IMAP) or `/api/outlook/` (Outlook's special routing). For IMAP, `{uid}` may be the list's `id` (`imap:<uid>`) as is; see section 2 for `folder`, which may be omitted for INBOX.
|
||||
|
||||
**Extra fields in message detail**: `body: {content, contentType}`, `cc`, `attachments: [{id, filename, mimeType, size}]`
|
||||
**Extra fields in message detail**: `body: {content, contentType}`, `ccRecipients`, `attachments: [{id, filename, contentType, size}]`. For IMAP, an attachment's `id` is its index within the message as a string (`"0"`, `"1"`, ...).
|
||||
|
||||
### 4. Send and Reply
|
||||
|
||||
@@ -235,26 +249,35 @@ In the following, `{p}` denotes the provider (`gmail`, `outlook`, `imap`); `{ema
|
||||
}
|
||||
```
|
||||
|
||||
Each `attachments` item is `{filename, content (base64), contentType}`. Gmail requires at least one non-empty recipient. When an IMAP account's `smtpStatus` is `unverified` (receive-only), send, reply, and draft send all return 409 (`error: smtp_unverified`); guide the user to re-verify SMTP first.
|
||||
|
||||
**Reply to an email**:
|
||||
|
||||
| Provider | Endpoint | Body |
|
||||
|----------|------|------|
|
||||
| Gmail | POST `/gmail/reply` | `{email, messageId, body, contentType}` |
|
||||
| Outlook | POST `/outlook/message/reply?id={id}&email=` | `{body, contentType}` |
|
||||
| IMAP | POST `/imap/reply` | `{email, uid, folder, body, contentType}` |
|
||||
| IMAP | POST `/imap/reply` | `{email, uid, folder, body, contentType}` (`uid` may be `imap:<uid>`; `folder` defaults to `INBOX`) |
|
||||
|
||||
### 5. Search (Gmail only)
|
||||
### 5. Search (All Three Providers)
|
||||
|
||||
`GET /api/gmail/search?email={email}&q={keyword}`
|
||||
`GET /api/{p}/search?email={email}&q={keyword}` (`{p}` is `gmail`, `outlook`, or `imap`)
|
||||
|
||||
Search reads only the **local cache** and never contacts the mail server; if nothing is found, sync remotely per Rule 3 and search again.
|
||||
|
||||
| Parameter | Description |
|
||||
|------|------|
|
||||
| `q` | keyword (searches subject, body, sender) |
|
||||
| `from` | sender address |
|
||||
| `dateFrom` / `dateTo` | date range YYYY-MM-DD |
|
||||
| `hasAttachment` | true/false |
|
||||
| `isUnread` | true/false |
|
||||
| `offset` / `limit` | pagination |
|
||||
| `q` | keyword, **matches the subject only** (case-insensitive) |
|
||||
| `from` | sender address (substring match) |
|
||||
| `dateFrom` / `dateTo` | date range YYYY-MM-DD (local time zone, both days inclusive) |
|
||||
| `hasAttachment` | `true` returns only messages with attachments (`false` does not filter) |
|
||||
| `isUnread` | `true` returns only unread messages (`false` does not filter) |
|
||||
| `folder` | folder filter, same values as the local cache list |
|
||||
| `offset` / `limit` | pagination, `limit` at most 200 |
|
||||
|
||||
Parameter names must match exactly; unknown parameters (e.g. `query`, `hasAttachments`) return 400. Returns `{messages, total, offset, limit}`.
|
||||
|
||||
To run a full-text search on the Gmail server by body or sender, use the `query` parameter of `POST /gmail/messages/fetch?email=` instead: it is passed straight to the Gmail API, supports Gmail search syntax (such as `from:` and `has:attachment`), and the results are also written to the local cache. Outlook and IMAP have no equivalent remote search parameter. If an older client returns 404 for `/outlook/search` or `/imap/search`, page through `GET /{p}/messages` and filter the results yourself.
|
||||
|
||||
### 6. Attachment Download
|
||||
|
||||
@@ -262,9 +285,23 @@ In the following, `{p}` denotes the provider (`gmail`, `outlook`, `imap`); `{ema
|
||||
|----------|------|------|------|
|
||||
| Gmail | POST | `/gmail/messages/{messageId}/attachment` | `{email, attachmentId}` |
|
||||
| Outlook | POST | `/outlook/attachment` | `{email, messageId, attachmentId}` |
|
||||
| IMAP | POST | `/imap/attachment` | `{email, uid, folder, partId}` |
|
||||
| IMAP | POST | `/imap/attachment` | `{email, messageId, attachmentId, folder?}` |
|
||||
|
||||
The response `result.data` is base64-encoded; decode it and save to a file.
|
||||
- `attachmentId` comes from `attachments[].id` in the message detail; call the detail endpoint first to get the attachment list.
|
||||
- IMAP: use the list's `id` (e.g. `imap:12345`) as `messageId`; `attachmentId` is the attachment index as a string (the first attachment is `"0"`); `folder` is the real folder path on the server and may be omitted for INBOX.
|
||||
- **Always pass `save_to` to `MailOperations` when downloading an attachment** (the target file path): the tool decodes the base64 in process and writes the file, returning only the absolute path, byte count, and SHA-256; without `save_to` the tool refuses to return the attachment response. The target path must be inside a writable working directory.
|
||||
- The endpoint's raw response is `result: {data: <base64>, size}`; the tool refuses to parse responses larger than about 32 MB — in that case follow the streaming-to-disk method given in the tool's error message, or ask the user to export the attachment manually from the Emails page.
|
||||
|
||||
Example (IMAP):
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "/api/imap/attachment",
|
||||
"method": "POST",
|
||||
"body": {"email": "me@example.com", "messageId": "imap:12345", "attachmentId": "0"},
|
||||
"save_to": "./attachments/report.pdf"
|
||||
}
|
||||
```
|
||||
|
||||
> Gmail uses POST because the attachmentId may exceed URL length limits.
|
||||
|
||||
@@ -276,7 +313,7 @@ The response `result.data` is base64-encoded; decode it and save to a file.
|
||||
|------|------|------|
|
||||
| List drafts | GET | `/gmail/drafts?email=&limit=` |
|
||||
| Get draft detail | GET | `/gmail/drafts/{draftId}?email=` |
|
||||
| Create draft | POST | `/gmail/drafts` — body: `{email, to, cc, subject, body, contentType}` |
|
||||
| Create draft | POST | `/gmail/drafts` — body: `{email, to, cc, subject, body, contentType}` (`to` / `cc` are comma-separated address strings) |
|
||||
| Update draft | PUT | `/gmail/drafts/{draftId}` — same body as create |
|
||||
| Delete draft | DELETE | `/gmail/drafts/{draftId}?email=` |
|
||||
|
||||
@@ -293,10 +330,10 @@ The response `result.data` is base64-encoded; decode it and save to a file.
|
||||
|
||||
| Operation | Method | Endpoint |
|
||||
|------|------|------|
|
||||
| Create draft | POST | `/imap/drafts` — body: `{email, toRecipients, subject, body, contentType}` |
|
||||
| Update draft | PUT | `/imap/drafts/{uid}` — body: `{email, folder, toRecipients, subject, body, contentType}` |
|
||||
| Delete draft | DELETE | `/imap/drafts/{uid}?email=&folder=` |
|
||||
| Send draft | POST | `/imap/drafts/{uid}/send` — body: `{email, folder}` |
|
||||
| Create draft | POST | `/imap/drafts` — body: `{email, to, cc, subject, body, contentType}` (`to` / `cc` are comma-separated address strings, not a `toRecipients` array); returns `{uid, id}` |
|
||||
| Update draft | PUT | `/imap/drafts/{uid}` — same body as create; it deletes the old draft and creates a new one, returning a **new** `{uid, id}` — use the new uid to send or delete afterwards (the old uid gets 404 `Draft not found`). If the server does not support UIDPLUS the returned uid is 0; re-read the draft list before the next operation |
|
||||
| Delete draft | DELETE | `/imap/drafts/{uid}?email=` |
|
||||
| Send draft | POST | `/imap/drafts/{uid}/send?email=` — `email` in the query string, no body needed |
|
||||
|
||||
### 8. Label Management (Unified Interface)
|
||||
|
||||
@@ -311,7 +348,9 @@ The response `result.data` is base64-encoded; decode it and save to a file.
|
||||
| Add label to email | POST | `/mails/{p}/{email}/labels?mailId=` — body: `{"labelId": "..."}` |
|
||||
| Bulk set labels | PUT | `/mails/{p}/{email}/labels?mailId=` — body: `{"labelIds": [...]}` |
|
||||
| Remove label from email | DELETE | `/mails/{p}/{email}/labels?mailId=&labelId=` |
|
||||
| Get emails under a label | GET | `/labels/{labelId}/mails?provider=&email=&limit=&offset=` |
|
||||
| Get emails under a label | GET | `/labels/{labelId}/mails?provider=&email=&limit=&offset=` — returns `mailIds` (no message content) |
|
||||
|
||||
The unified label interface changes only DesireCore's local data and does not sync to the mail server. To change labels on the server, use the Gmail native labels below or the Outlook categories in section 9.
|
||||
|
||||
**Gmail native labels**:
|
||||
- List labels: `GET /api/gmail/labels?email=`
|
||||
@@ -355,46 +394,47 @@ Outlook uses Categories instead of Labels.
|
||||
"email": "xxx@gmail.com",
|
||||
"enabled": true,
|
||||
"conditions": [
|
||||
{"field": "from|to|subject|body|has_attachment", "operator": "contains|equals|regex|...", "value": "..."}
|
||||
{"field": "from|to|subject|body|has_attachment", "operator": "contains|not_contains|equals|not_equals|starts_with|ends_with|matches_regex|is_true|is_false", "value": "..."}
|
||||
],
|
||||
"conditionLogic": "and",
|
||||
"actions": [
|
||||
{"type": "add_label|remove_label|mark_as_read|mark_as_unread|archive|delete|auto_reply|agent_handle", "value": "..."}
|
||||
{"type": "add_label|remove_label|mark_as_read|mark_as_unread|delete|auto_reply|agent_handle", "value": "..."}
|
||||
],
|
||||
"priority": 1,
|
||||
"stopOnMatch": false
|
||||
}
|
||||
```
|
||||
|
||||
The regex operator is `matches_regex` (`regex` is treated as an unknown operator and never matches); `is_true` / `is_false` are only for `has_attachment`.
|
||||
|
||||
**Action types**:
|
||||
|
||||
| type | value | Description |
|
||||
|------|-------|------|
|
||||
| `add_label` | label ID | add a label |
|
||||
| `remove_label` | label ID | remove a label |
|
||||
| `mark_as_read` | omit | mark as read |
|
||||
| `mark_as_unread` | omit | mark as unread |
|
||||
| `archive` | omit | archive |
|
||||
| `delete` | omit | delete |
|
||||
| `add_label` | label ID | add a local label |
|
||||
| `remove_label` | label ID | remove a local label |
|
||||
| `mark_as_read` | omit | mark as read in the local cache only |
|
||||
| `mark_as_unread` | omit | mark as unread in the local cache only |
|
||||
| `delete` | omit | delete from the local cache only; the message on the server is untouched |
|
||||
| `auto_reply` | reply text | auto-reply with fixed content |
|
||||
| `agent_handle` | Agent ID | trigger an Agent to handle the email |
|
||||
|
||||
> Rules are **executed automatically** when the polling engine detects a new email; no manual call is required. `auto_reply` and `agent_handle` support all three mailbox types.
|
||||
> Rules are **executed automatically** when the polling engine detects a new email; no manual call is required (for IMAP only new INBOX mail is covered). `auto_reply` and `agent_handle` support all three mailbox types; `agent_handle` binds the Agent Service connection that is current when the rule is created or updated. `archive`, `move_to_folder`, `forward_to`, and `star` can be saved but currently do nothing when executed; do not promise these effects to the user.
|
||||
|
||||
### 11. Authorization Management
|
||||
|
||||
| Operation | Method | Endpoint |
|
||||
|------|------|------|
|
||||
| Gmail OAuth | POST | `/gmail/auth/initiate?loginHint={email}` — opens browser for authorization |
|
||||
| Gmail OAuth | POST | `/gmail/auth/initiate?loginHint={email}` — returns `result.authUrl` for the user to open in a browser to authorize |
|
||||
| Gmail status | GET | `/gmail/auth/status?email=` |
|
||||
| Outlook OAuth | POST | `/outlook/auth/initiate` — opens browser for authorization |
|
||||
| Outlook OAuth | POST | `/outlook/auth/initiate` — returns `result.authUrl` for the user to open in a browser to authorize |
|
||||
| Outlook status | GET | `/outlook/auth/status?email=` |
|
||||
|
||||
### 12. Folders
|
||||
|
||||
| Operation | Method | Endpoint |
|
||||
|------|------|------|
|
||||
| IMAP folder list | GET | `/imap/folders?email=` |
|
||||
| IMAP folder list | GET | `/imap/folders?email=` — returns `[{name, path, specialUse, flags}]`; use `path` as the `folder` of IMAP single-message operations |
|
||||
| Outlook folder list | GET | `/outlook/folders?email=` |
|
||||
|
||||
> Gmail folders are fixed: inbox, sent, drafts, trash, spam, archive.
|
||||
@@ -405,11 +445,11 @@ Outlook uses Categories instead of Labels.
|
||||
|
||||
The email system uses **local cache + periodic polling**:
|
||||
|
||||
- **Write operations** (send, mark, delete, label): update local and remote simultaneously, no delay
|
||||
- **Write operations** (send, mark, delete, Gmail native labels, Outlook categories): the provider endpoints update local and remote simultaneously, no delay; the unified label interface (section 8) and the mark, delete, and label actions of auto-rules change local data only
|
||||
- **Read operations** (query, search): return the local cache; may be delayed (default 30-second polling)
|
||||
- **Remote changes** (the user operates from the official web UI): wait for the next polling cycle to sync
|
||||
- **Remote changes** (the user operates from the official web UI): Gmail / Outlook wait for the next polling cycle to sync; IMAP polling only pulls new INBOX mail, other folders require an explicit `messages/fetch`, and read or delete changes made on the server to already-cached messages are not written back locally
|
||||
|
||||
**Storage path**: `${DESIRECORE_ROOT}/mail/{provider}/{email}/` (index.json, messages/, sync.json)
|
||||
**Storage path**: `${DESIRECORE_ROOT}/mail/{provider}/{email}/` (meta.json, index.json, messages/)
|
||||
|
||||
---
|
||||
|
||||
@@ -417,9 +457,10 @@ The email system uses **local cache + periodic polling**:
|
||||
|
||||
| Status code | Reason | Handling |
|
||||
|--------|------|------|
|
||||
| 400 | parameter error | check the request parameters |
|
||||
| 401 | authorization expired | **handle per Rule 1**; do not try other channels |
|
||||
| 400 | parameter error (including a parameter in the wrong place, e.g. a query-string parameter put in the body) | check parameter names and locations against `msg` |
|
||||
| 401 | Gmail/Outlook authorization expired, IMAP account not configured, or missing Mail Service capability | **handle per Rule 1**; do not try other channels |
|
||||
| 404 | resource not found | sync first and retry (Rule 3) |
|
||||
| 500 | internal error | tell the user to retry later |
|
||||
| 409 | IMAP account SMTP not verified (`error: smtp_unverified`) | guide the user to re-verify SMTP (`POST /imap/accounts/verify-smtp`) |
|
||||
| 500 | internal error (common IMAP cause: `folder` is not a folder that exists on the server) | check `folder` first; if it still fails, tell the user to retry later |
|
||||
|
||||
**IMAP note**: domestic Chinese mailboxes (QQ, 163) require an "app password" instead of the login password. Use `/imap/test` to validate the configuration in advance.
|
||||
**IMAP note**: domestic Chinese mailboxes (QQ, 163) require an "app password" instead of the login password. Use `/imap/test` to validate the configuration in advance and check `result.imap.success` and `result.smtp.success` in the response.
|
||||
|
||||
Reference in New Issue
Block a user