From 0a2e1830edbbf3af7c36d37427bcaa1705e0db07 Mon Sep 17 00:00:00 2001 From: chenhuang Date: Mon, 13 Apr 2026 18:23:28 +0800 Subject: [PATCH] feat(mail): add recall tip after send and recall workflow documentation - Add buildSendResult helper that includes recall_available/recall_tip when backend returns recall_status in send response - Update +send, +reply, +reply-all, +forward to use buildSendResult - Add "Recall Email" section to mail skill template with recall and get_recall_detail command examples - Regenerate SKILL.md Change-Id: I44317ead8f8a65db81e874cfc3529ffeb21e1384 Co-Authored-By: AI --- shortcuts/mail/helpers.go | 17 +++++++++++++++++ shortcuts/mail/mail_forward.go | 5 +---- shortcuts/mail/mail_reply.go | 5 +---- shortcuts/mail/mail_reply_all.go | 5 +---- shortcuts/mail/mail_send.go | 5 +---- skill-template/domains/mail.md | 24 ++++++++++++++++++++++++ skills/lark-mail/SKILL.md | 31 +++++++++++++++++++++++++++++++ 7 files changed, 76 insertions(+), 16 deletions(-) diff --git a/shortcuts/mail/helpers.go b/shortcuts/mail/helpers.go index c528554d..0b1958d3 100644 --- a/shortcuts/mail/helpers.go +++ b/shortcuts/mail/helpers.go @@ -1933,6 +1933,23 @@ func validateConfirmSendScope(runtime *common.RuntimeContext) error { return nil } +// buildSendResult builds the output map for a successful send, including +// recall tip if the backend indicates the message is recallable. +func buildSendResult(resData map[string]interface{}, mailboxID string) map[string]interface{} { + result := map[string]interface{}{ + "message_id": resData["message_id"], + "thread_id": resData["thread_id"], + } + if recallStatus, ok := resData["recall_status"].(string); ok && recallStatus == "available" { + messageID, _ := resData["message_id"].(string) + result["recall_available"] = true + result["recall_tip"] = fmt.Sprintf( + `This message can be recalled within 24 hours. To recall: lark-cli mail user_mailbox.sent_messages recall --params '{"user_mailbox_id":"%s","message_id":"%s"}'`, + mailboxID, messageID) + } + return result +} + // validateFolderReadScope checks that the user's token includes the // mail:user_mailbox.folder:read scope. Called on-demand by listMailboxFolders // before hitting the folders API. System folders are resolved locally and diff --git a/shortcuts/mail/mail_forward.go b/shortcuts/mail/mail_forward.go index a270277b..2ef52034 100644 --- a/shortcuts/mail/mail_forward.go +++ b/shortcuts/mail/mail_forward.go @@ -222,10 +222,7 @@ var MailForward = common.Shortcut{ if err != nil { return fmt.Errorf("failed to send forward (draft %s created but not sent): %w", draftID, err) } - runtime.Out(map[string]interface{}{ - "message_id": resData["message_id"], - "thread_id": resData["thread_id"], - }, nil) + runtime.Out(buildSendResult(resData, mailboxID), nil) hintMarkAsRead(runtime, mailboxID, messageId) return nil }, diff --git a/shortcuts/mail/mail_reply.go b/shortcuts/mail/mail_reply.go index 53370d5f..0a449f9d 100644 --- a/shortcuts/mail/mail_reply.go +++ b/shortcuts/mail/mail_reply.go @@ -185,10 +185,7 @@ var MailReply = common.Shortcut{ if err != nil { return fmt.Errorf("failed to send reply (draft %s created but not sent): %w", draftID, err) } - runtime.Out(map[string]interface{}{ - "message_id": resData["message_id"], - "thread_id": resData["thread_id"], - }, nil) + runtime.Out(buildSendResult(resData, mailboxID), nil) hintMarkAsRead(runtime, mailboxID, messageId) return nil }, diff --git a/shortcuts/mail/mail_reply_all.go b/shortcuts/mail/mail_reply_all.go index f8d6a452..f08c4487 100644 --- a/shortcuts/mail/mail_reply_all.go +++ b/shortcuts/mail/mail_reply_all.go @@ -199,10 +199,7 @@ var MailReplyAll = common.Shortcut{ if err != nil { return fmt.Errorf("failed to send reply-all (draft %s created but not sent): %w", draftID, err) } - runtime.Out(map[string]interface{}{ - "message_id": resData["message_id"], - "thread_id": resData["thread_id"], - }, nil) + runtime.Out(buildSendResult(resData, mailboxID), nil) hintMarkAsRead(runtime, mailboxID, messageId) return nil }, diff --git a/shortcuts/mail/mail_send.go b/shortcuts/mail/mail_send.go index 6eb11e75..0680ce2e 100644 --- a/shortcuts/mail/mail_send.go +++ b/shortcuts/mail/mail_send.go @@ -149,10 +149,7 @@ var MailSend = common.Shortcut{ if err != nil { return fmt.Errorf("failed to send email (draft %s created but not sent): %w", draftID, err) } - runtime.Out(map[string]interface{}{ - "message_id": resData["message_id"], - "thread_id": resData["thread_id"], - }, nil) + runtime.Out(buildSendResult(resData, mailboxID), nil) return nil }, } diff --git a/skill-template/domains/mail.md b/skill-template/domains/mail.md index caba12c8..349cf0d4 100644 --- a/skill-template/domains/mail.md +++ b/skill-template/domains/mail.md @@ -117,6 +117,30 @@ lark-cli mail user_mailbox.messages send_status --params '{"user_mailbox_id":"me 返回每个收件人的投递状态(`status`):1=正在投递, 2=投递失败重试, 3=退信, 4=投递成功, 5=待审批, 6=审批拒绝。向用户简要报告结果,如有异常状态(退信/审批拒绝)需重点提示。 +### 撤回邮件 + +发送成功后,若响应中包含 `recall_available: true`,说明该邮件支持撤回(24 小时内已投递的邮件)。 + +**撤回操作:** +```bash +lark-cli mail user_mailbox.sent_messages recall --as user \ + --params '{"user_mailbox_id":"me","message_id":""}' +``` + +- 返回 `recall_status: available` 表示撤回请求已受理(异步执行) +- 返回 `recall_status: unavailable` 表示不可撤回,`recall_restriction_reason` 说明原因 + +**查询撤回进度:** +```bash +lark-cli mail user_mailbox.sent_messages get_recall_detail --as user \ + --params '{"user_mailbox_id":"me","message_id":""}' +``` + +- `recall_status: in_progress` — 撤回进行中,可稍后再查 +- `recall_status: done` — 撤回完成,查看 `recall_result`(`all_success` / `all_fail` / `some_fail`)和每个收件人的详情 + +**注意:** 撤回是异步操作,`recall` 返回成功仅表示请求已受理,实际结果需通过 `get_recall_detail` 查询。若响应中无 `recall_available` 字段,说明该邮件或应用不支持撤回,不要主动提及撤回。 + ### 正文格式:优先使用 HTML 撰写邮件正文时,**默认使用 HTML 格式**(body 内容会被自动检测)。仅当用户明确要求纯文本时,才使用 `--plain-text` 标志强制纯文本模式。 diff --git a/skills/lark-mail/SKILL.md b/skills/lark-mail/SKILL.md index 0a42fcdb..292edae0 100644 --- a/skills/lark-mail/SKILL.md +++ b/skills/lark-mail/SKILL.md @@ -131,6 +131,30 @@ lark-cli mail user_mailbox.messages send_status --params '{"user_mailbox_id":"me 返回每个收件人的投递状态(`status`):1=正在投递, 2=投递失败重试, 3=退信, 4=投递成功, 5=待审批, 6=审批拒绝。向用户简要报告结果,如有异常状态(退信/审批拒绝)需重点提示。 +### 撤回邮件 + +发送成功后,若响应中包含 `recall_available: true`,说明该邮件支持撤回(24 小时内已投递的邮件)。 + +**撤回操作:** +```bash +lark-cli mail user_mailbox.sent_messages recall --as user \ + --params '{"user_mailbox_id":"me","message_id":""}' +``` + +- 返回 `recall_status: available` 表示撤回请求已受理(异步执行) +- 返回 `recall_status: unavailable` 表示不可撤回,`recall_restriction_reason` 说明原因 + +**查询撤回进度:** +```bash +lark-cli mail user_mailbox.sent_messages get_recall_detail --as user \ + --params '{"user_mailbox_id":"me","message_id":""}' +``` + +- `recall_status: in_progress` — 撤回进行中,可稍后再查 +- `recall_status: done` — 撤回完成,查看 `recall_result`(`all_success` / `all_fail` / `some_fail`)和每个收件人的详情 + +**注意:** 撤回是异步操作,`recall` 返回成功仅表示请求已受理,实际结果需通过 `get_recall_detail` 查询。若响应中无 `recall_available` 字段,说明该邮件或应用不支持撤回,不要主动提及撤回。 + ### 正文格式:优先使用 HTML 撰写邮件正文时,**默认使用 HTML 格式**(body 内容会被自动检测)。仅当用户明确要求纯文本时,才使用 `--plain-text` 标志强制纯文本模式。 @@ -340,6 +364,11 @@ lark-cli mail [flags] # 调用 API - `modify` — 本接口提供修改邮件会话的能力,支持移动邮件会话的文件夹、给邮件会话添加和移除标签、标记邮件会话读和未读、移动邮件会话至垃圾邮件等能力。不支持移动邮件会话到已删除文件夹,如需,请使用删除邮件会话接口。至少填写add_label_ids、remove_label_ids、add_folder中的一个参数。 - `trash` — 移动指定的邮件会话到已删除文件夹 +### user_mailbox.sent_messages + + - `recall` — 撤回指定邮件。前置条件:邮件须已投递,且发送时间在 24 小时以内;搬家中的域名不支持撤回。返回说明:若用户或邮件不满足撤回条件,接口仍返回 200,响应体中 recall_status 为 unavailable,recall_restriction_reason 标明具体原因。返回成功仅表示撤回请求已受理,实际撤回结果请调用「查询邮件撤回进度」接口获取。 + - `get_recall_detail` — 查询指定邮件的撤回结果详情,包括整体撤回进度、成功/失败/处理中的收件人数量,以及每个收件人的撤回状态和失败原因。 + ## 权限表 | 方法 | 所需 scope | @@ -391,4 +420,6 @@ lark-cli mail [flags] # 调用 API | `user_mailbox.threads.list` | `mail:user_mailbox.message:readonly` | | `user_mailbox.threads.modify` | `mail:user_mailbox.message:modify` | | `user_mailbox.threads.trash` | `mail:user_mailbox.message:modify` | +| `user_mailbox.sent_messages.recall` | `mail:user_mailbox.message:modify` | +| `user_mailbox.sent_messages.get_recall_detail` | `mail:user_mailbox.message:readonly` |