Skip to content

feat(mail): add draft preview URL to draft operations#438

Merged
chanthuang merged 15 commits intolarksuite:mainfrom
qiooo:feat/mail-send-preview
Apr 21, 2026
Merged

feat(mail): add draft preview URL to draft operations#438
chanthuang merged 15 commits intolarksuite:mainfrom
qiooo:feat/mail-send-preview

Conversation

@qiooo
Copy link
Copy Markdown
Contributor

@qiooo qiooo commented Apr 13, 2026

Summary

This PR improves client-side mail output for draft and send flows. It surfaces the draft open link (reference) when a command produces a draft, and it flattens automation interception details in send results so agents and users can act on them directly.

Changes

  • Return reference from draft create/update flows and propagate it through draft-related shortcuts when available.
  • Keep draft-save outputs consistent across +send, +reply, +reply-all, and +forward, while keeping +draft-create intentionally minimal because it only creates a draft and does not suggest an immediate send step.
  • Flatten automation_send_disable into top-level output fields:
    • automation_send_disable_reason
    • automation_send_disable_reference
  • Update lark-mail skill/reference docs so agents prefer showing the returned draft preview link and do not treat user_mailbox.drafts get as the source of that link.
  • Fix documentation to use the actual field name reference for draft preview links.

Security / Behavior Notes

  • The CLI still defaults to saving drafts unless --confirm-send is provided.
  • The skill/docs now explicitly allow direct --confirm-send only when the user has already explicitly confirmed recipients and content.
  • This is a documentation/policy clarification for agent behavior, not a server-side rollout or interception strategy change.
  • When send results include automation_send_disable_reason / automation_send_disable_reference, callers should treat the message as intercepted by mailbox automation and guide the user with the returned draft link.

Test Plan

  • go test ./shortcuts/mail/draft ./shortcuts/mail -run 'TestExtractReference|TestMailSendConfirmSendOutputsAutomationDisable|TestMailSendSaveDraftOutputsReference'
  • Manual verification of the generated QA package showed lark-cli version 1.0.15 and confirmed the expected embedded endpoint set during packaging work.

Related Issues

  • None

@github-actions github-actions bot added domain/mail PR touches the mail domain size/M Single-domain feat or fix with limited business impact labels Apr 13, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Commands and draft service were updated to return and propagate a new DraftResult (DraftID, Reference). A helper builds send outputs including optional fields. Draft create/update now return richer results; multiple mail shortcuts use DraftResult and the new output builder. Tests added for reference extraction and send output.

Changes

Cohort / File(s) Summary
Draft model & service
shortcuts/mail/draft/model.go, shortcuts/mail/draft/service.go
Added exported DraftResult (DraftID, Reference). CreateWithRaw now returns (DraftResult, error); UpdateWithRaw now returns (DraftResult, error). Added extractReference(data map[string]interface{}) string to read reference from responses.
Send output helper
shortcuts/mail/helpers.go
Added unexported buildDraftSendOutput(resData map[string]interface{}) map[string]interface{} to produce output with message_id, thread_id and optional recall_status / automation_send_disable.
Mail draft commands (capture DraftResult)
shortcuts/mail/mail_draft_create.go, shortcuts/mail/mail_draft_edit.go, shortcuts/mail/mail_forward.go, shortcuts/mail/mail_reply.go, shortcuts/mail/mail_reply_all.go, shortcuts/mail/mail_send.go
Capture full draftResult from CreateWithRaw/UpdateWithRaw, use draftResult.DraftID (and Reference when present) for outputs, tips, hints, errors, and to call draftpkg.Send. On send success, use runtime.Out(buildDraftSendOutput(resData), nil) instead of emitting raw map.
Tests
shortcuts/mail/draft/service_test.go, shortcuts/mail/mail_send_confirm_output_test.go
Added unit tests for extractReference (top-level, nested, nil) and an integration-style test verifying buildDraftSendOutput includes optional automation_send_disable when present and that MailSend --confirm-send emits the structured output.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(200,220,255,0.5)
  participant CLI
  end
  rect rgba(200,255,200,0.5)
  participant DraftService
  end
  rect rgba(255,230,200,0.5)
  participant ExternalAPI
  end
  rect rgba(255,200,255,0.5)
  participant Runtime
  end

  CLI->>DraftService: CreateWithRaw(mailboxID, rawEML)
  DraftService->>ExternalAPI: POST /drafts (raw EML)
  ExternalAPI-->>DraftService: response (includes draft_id, maybe reference)
  DraftService-->>CLI: DraftResult{DraftID, Reference}

  alt confirmSend == true
    CLI->>DraftService: Send(draftResult.DraftID)
    DraftService->>ExternalAPI: POST /drafts/{id}/send
    ExternalAPI-->>DraftService: send response (message_id, thread_id, optional meta)
    DraftService-->>CLI: resData
    CLI->>Runtime: runtime.Out(buildDraftSendOutput(resData))
  else
    CLI->>Runtime: runtime.OutFormat({"draft_id": draftResult.DraftID, ...})
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • infeng
  • chanthuang

Poem

🐰 I hopped through drafts both new and old,
I fetched the ID and kept the gold.
A reference found, tucked in the flow,
Send outputs sing what APIs show.
Hooray — small changes, big delight 🥕✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title mentions 'draft preview URL' but the summary shows changes focus on extracting reference fields and restructuring draft result returns, not preview URLs. Update title to accurately reflect the main change: refactor draft operations to return structured DraftResult with reference field extraction, or verify if preview_url changes were included in the summary.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description covers all required template sections with comprehensive details about motivation, changes, testing, and related issues.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 13, 2026

CLA assistant check
All committers have signed the CLA.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@shortcuts/mail/mail_forward.go`:
- Around line 215-227: The human-readable output writes unsanitized tip text
from out["tip"] (set near draftID/mailboxID) which can reintroduce
terminal-control injection; before passing to runtime.OutFormat's writer (the
fmt.Fprintf that prints out["tip"]) sanitize or escape control characters (or
format as a quoted/escaped string) and replace the raw write of out["tip"] with
the sanitized value so only safe characters are printed; update the code that
sets/prints out["tip"] (and any direct uses of draftID/mailboxID in the tip) to
use the sanitizer/escaped string when calling runtime.OutFormat and when
populating the out map.

In `@shortcuts/mail/mail_reply_all.go`:
- Around line 192-204: The printed tip value (out["tip"]) is emitted raw in the
runtime.OutFormat writer inside mail_reply_all.go which can leak control
characters; before passing to OutFormat or writing with fmt.Fprintf, sanitize or
escape control characters from the tip string (e.g., remove/escape control
bytes, normalize newlines) and replace out["tip"] with the sanitized string so
both the structured output and the human-readable fmt.Fprintf calls use the safe
value; update references around the draft save block where draftID and out map
are created and used (the runtime.OutFormat callback and the fmt.Fprintf that
prints out["tip"]) to read and print the sanitized tip instead of the raw one.

In `@shortcuts/mail/mail_reply.go`:
- Around line 178-190: The tip string placed in the out map and printed to
terminal (key "tip", produced in the block around runtime.OutFormat in
mail_reply.go, using mailboxID and draftID) may contain control characters;
sanitize it before storing/printing: create or call a sanitizer (e.g.,
sanitizeString or escape control chars using a safe printer) and use the
sanitized value for out["tip"] and for the fmt.Fprintf that prints the tip;
ensure any direct fmt.Fprintf or runtime.OutFormat output uses the sanitizedTip
variable so mailboxID/draftID cannot inject terminal control sequences.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a86fdf85-1d87-42cf-be7f-04226d8942aa

📥 Commits

Reviewing files that changed from the base of the PR and between 3917b77 and b7743a9.

📒 Files selected for processing (7)
  • shortcuts/mail/draft_preview_url_test.go
  • shortcuts/mail/helpers.go
  • shortcuts/mail/mail_draft_create.go
  • shortcuts/mail/mail_draft_edit.go
  • shortcuts/mail/mail_forward.go
  • shortcuts/mail/mail_reply.go
  • shortcuts/mail/mail_reply_all.go

Comment thread shortcuts/mail/mail_forward.go Outdated
Comment thread shortcuts/mail/mail_reply_all.go Outdated
Comment thread shortcuts/mail/mail_reply.go Outdated
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 13, 2026

Greptile Summary

This PR adds a shareable draft preview URL to the draft-save output of five mail commands (draft-create, draft-edit, forward, reply, reply-all) and introduces well-tested helpers (draftPreviewURL, draftPreviewURLForBrand, addDraftPreviewURL) for URL generation. The forward/reply/reply_all commands are also refactored from runtime.Out to runtime.OutFormat to support a human-readable --format pretty output path.

  • P1 — tip duplication in --format pretty mode: mail_forward.go, mail_reply.go, and mail_reply_all.go now print the "draft saved / how to send" instruction both in the OutFormat pretty callback (stdout) and again via hintSendDraft (stderr), so terminal users see it twice when --format pretty is used.
  • P2 — mail_send.go inconsistency: addDraftPreviewURL is called but runtime.Out (not runtime.OutFormat) is used, so preview_url is absent from the human-readable output while it appears in all the other commands.

Confidence Score: 4/5

Safe to merge after resolving the tip duplication in --format pretty mode; the preview URL feature itself is well-tested and correct.

One P1 finding (tip printed twice in pretty mode) and one P2 inconsistency (mail_send.go not using OutFormat) prevent a full 5/5. The core URL generation logic is solid and well-covered by tests.

shortcuts/mail/mail_forward.go, shortcuts/mail/mail_reply.go, shortcuts/mail/mail_reply_all.go (tip duplication), shortcuts/mail/mail_send.go (OutFormat consistency)

Important Files Changed

Filename Overview
shortcuts/mail/helpers.go Adds draftPreviewURL, draftPreviewURLForBrand, draftPreviewOriginForBrand, and addDraftPreviewURL helpers; draftPreviewOriginForBrand uses fragile string replacement to derive www-origin from open-API URL.
shortcuts/mail/mail_forward.go Refactored draft-save path from runtime.Out to runtime.OutFormat with pretty callback; introduces tip duplication in --format pretty mode (printed both in the callback and by hintSendDraft).
shortcuts/mail/mail_reply.go Same tip-duplication issue as mail_forward.go: OutFormat callback prints tip to stdout, hintSendDraft still writes it to stderr in pretty mode.
shortcuts/mail/mail_reply_all.go Same tip-duplication issue as mail_forward.go; consistent with the reply pattern but shares the stdout/stderr duplication problem in pretty mode.
shortcuts/mail/mail_send.go Adds addDraftPreviewURL and sanitizeForTerminal but keeps runtime.Out instead of OutFormat, so preview_url is invisible in --format pretty mode unlike the other commands.
shortcuts/mail/mail_draft_create.go Cleanly adds addDraftPreviewURL call and preview_url output in the OutFormat callback; no issues.
shortcuts/mail/mail_draft_edit.go Adds preview URL to both the edit and inspect paths; implementation is consistent and correct.
shortcuts/mail/draft_preview_url_test.go Good unit test coverage for all new helpers including nil-safety, brand variants, URL structure, and special characters in draft ID.

Sequence Diagram

sequenceDiagram
    participant User
    participant Command as Draft Command
    participant API as Lark API
    participant Helpers as helpers.go
    participant Runtime as RuntimeContext

    User->>Command: run (--confirm-send=false)
    Command->>API: CreateWithRaw(mailboxID, rawEML)
    API-->>Command: draftID
    Command->>Helpers: addDraftPreviewURL(runtime, out, draftID)
    Helpers->>Helpers: draftPreviewURL(runtime, draftID)
    Helpers->>Helpers: draftPreviewOriginForBrand(brand)
    Helpers-->>Command: out["preview_url"] set
    Command->>Runtime: OutFormat(out, nil, prettyFn)
    alt --format pretty
        Runtime->>User: stdout: draft_id + preview_url + tip
    else --format json (default)
        Runtime->>User: stdout: JSON envelope
    end
    Command->>Runtime: hintSendDraft(runtime, mailboxID, draftID)
    Runtime->>User: stderr: tip: draft saved. To send...
Loading

Reviews (3): Last reviewed commit: "fix(mail): derive draft preview URL orig..." | Re-trigger Greptile

Comment thread shortcuts/mail/mail_forward.go
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
shortcuts/mail/mail_forward.go (1)

220-227: ⚠️ Potential issue | 🟠 Major

Sanitize terminal-bound values at print time too.

Line 222 still prints raw draftID, and Line 226 writes out["tip"] without sink sanitization. Keep the formatter sink hardened as well to avoid control-sequence leakage in human-readable mode.

Suggested hardening
 			runtime.OutFormat(out, nil, func(w io.Writer) {
 				fmt.Fprintln(w, "Draft saved.")
-				fmt.Fprintf(w, "draft_id: %s\n", draftID)
+				fmt.Fprintf(w, "draft_id: %s\n", sanitizeForTerminal(draftID))
 				if previewURL, _ := out["preview_url"].(string); previewURL != "" {
 					fmt.Fprintf(w, "preview_url: %s\n", previewURL)
 				}
-				fmt.Fprintf(w, "%s\n", out["tip"])
+				fmt.Fprintf(w, "%s\n", sanitizeForTerminal(fmt.Sprint(out["tip"])))
 			})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/mail/mail_forward.go` around lines 220 - 227, The human-readable
output block in runtime.OutFormat prints raw draftID and out["tip"] which can
leak control sequences; update the closure passed to runtime.OutFormat to pass
draftID and the tip value through the repository's terminal-sanitization helper
(e.g., sanitizeTerminal or the existing runtime/ansi sanitizer) before writing
them to w, and also ensure preview_url is likewise sanitized when printed;
target the variables draftID and out["tip"] inside the anonymous func to apply
the sanitizer so all terminal-bound strings are escaped/stripped before
fmt.Fprintln/Fprintf.
shortcuts/mail/mail_reply_all.go (1)

197-204: ⚠️ Potential issue | 🟠 Major

Apply sink-side terminal sanitization in formatted output.

Line 199 outputs raw draftID, and Line 203 writes out["tip"] directly. Please sanitize at the write site as well for consistent terminal-safety.

Suggested hardening
 			runtime.OutFormat(out, nil, func(w io.Writer) {
 				fmt.Fprintln(w, "Draft saved.")
-				fmt.Fprintf(w, "draft_id: %s\n", draftID)
+				fmt.Fprintf(w, "draft_id: %s\n", sanitizeForTerminal(draftID))
 				if previewURL, _ := out["preview_url"].(string); previewURL != "" {
 					fmt.Fprintf(w, "preview_url: %s\n", previewURL)
 				}
-				fmt.Fprintf(w, "%s\n", out["tip"])
+				fmt.Fprintf(w, "%s\n", sanitizeForTerminal(fmt.Sprint(out["tip"])))
 			})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/mail/mail_reply_all.go` around lines 197 - 204, In the
runtime.OutFormat callback, avoid writing draftID and out["tip"] (and
preview_url) raw to the terminal; pass them through your terminal-sanitization
helper before calling fmt.Fprintln / fmt.Fprintf. Update the
fmt.Fprintf/Fprintln calls that reference draftID and out["tip"] (and the
previewURL extraction) to use the sanitizer (e.g., sanitizeForTerminal(...)) so
control characters and unsafe sequences are removed/escaped; if a sanitizer
function does not exist, add a small helper (sanitizeForTerminal) that strips or
escapes control/ANSI sequences and use it on draftID, previewURL and out["tip"]
before writing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@shortcuts/mail/mail_forward.go`:
- Around line 220-227: The human-readable output block in runtime.OutFormat
prints raw draftID and out["tip"] which can leak control sequences; update the
closure passed to runtime.OutFormat to pass draftID and the tip value through
the repository's terminal-sanitization helper (e.g., sanitizeTerminal or the
existing runtime/ansi sanitizer) before writing them to w, and also ensure
preview_url is likewise sanitized when printed; target the variables draftID and
out["tip"] inside the anonymous func to apply the sanitizer so all
terminal-bound strings are escaped/stripped before fmt.Fprintln/Fprintf.

In `@shortcuts/mail/mail_reply_all.go`:
- Around line 197-204: In the runtime.OutFormat callback, avoid writing draftID
and out["tip"] (and preview_url) raw to the terminal; pass them through your
terminal-sanitization helper before calling fmt.Fprintln / fmt.Fprintf. Update
the fmt.Fprintf/Fprintln calls that reference draftID and out["tip"] (and the
previewURL extraction) to use the sanitizer (e.g., sanitizeForTerminal(...)) so
control characters and unsafe sequences are removed/escaped; if a sanitizer
function does not exist, add a small helper (sanitizeForTerminal) that strips or
escapes control/ANSI sequences and use it on draftID, previewURL and out["tip"]
before writing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dd078882-081a-4114-9462-1cc1bddfe329

📥 Commits

Reviewing files that changed from the base of the PR and between b7743a9 and 41c88ff.

📒 Files selected for processing (6)
  • shortcuts/mail/draft_preview_url_test.go
  • shortcuts/mail/helpers.go
  • shortcuts/mail/mail_forward.go
  • shortcuts/mail/mail_reply.go
  • shortcuts/mail/mail_reply_all.go
  • shortcuts/mail/mail_send.go
✅ Files skipped from review due to trivial changes (1)
  • shortcuts/mail/draft_preview_url_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • shortcuts/mail/mail_reply.go
  • shortcuts/mail/helpers.go

@qiooo qiooo force-pushed the feat/mail-send-preview branch from 41c88ff to 16312db Compare April 13, 2026 10:27
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
shortcuts/mail/mail_reply.go (1)

185-185: ⚠️ Potential issue | 🟠 Major

Sanitize draft_id before writing to terminal output.

draftID is still printed raw at Line 185, so control characters could reach terminal output despite tip hardening.

🔧 Suggested fix
-				fmt.Fprintf(w, "draft_id: %s\n", draftID)
+				fmt.Fprintf(w, "draft_id: %s\n", sanitizeForTerminal(draftID))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/mail/mail_reply.go` at line 185, The code writes draftID raw to
terminal via fmt.Fprintf(w, "draft_id: %s\n", draftID); sanitize or escape
control characters before writing to avoid terminal control injection. Replace
the direct write with a sanitized value (e.g., call a new helper
sanitizeDraftID(string) that strips or escapes non-printable/control runes, or
use strconv.Quote to safely escape) and use that sanitized value in the
fmt.Fprintf call; update or add sanitizeDraftID near the existing handler that
references draftID in mail_reply.go so future uses are safe.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@shortcuts/mail/mail_reply.go`:
- Line 185: The code writes draftID raw to terminal via fmt.Fprintf(w,
"draft_id: %s\n", draftID); sanitize or escape control characters before writing
to avoid terminal control injection. Replace the direct write with a sanitized
value (e.g., call a new helper sanitizeDraftID(string) that strips or escapes
non-printable/control runes, or use strconv.Quote to safely escape) and use that
sanitized value in the fmt.Fprintf call; update or add sanitizeDraftID near the
existing handler that references draftID in mail_reply.go so future uses are
safe.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 122a09fd-2214-4f08-9833-9b93af406dc4

📥 Commits

Reviewing files that changed from the base of the PR and between 41c88ff and 16312db.

📒 Files selected for processing (6)
  • shortcuts/mail/draft_preview_url_test.go
  • shortcuts/mail/helpers.go
  • shortcuts/mail/mail_forward.go
  • shortcuts/mail/mail_reply.go
  • shortcuts/mail/mail_reply_all.go
  • shortcuts/mail/mail_send.go
✅ Files skipped from review due to trivial changes (2)
  • shortcuts/mail/mail_reply_all.go
  • shortcuts/mail/draft_preview_url_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • shortcuts/mail/mail_send.go
  • shortcuts/mail/mail_forward.go
  • shortcuts/mail/helpers.go

@qiooo qiooo force-pushed the feat/mail-send-preview branch from 16312db to 356ff4f Compare April 15, 2026 12:17
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
shortcuts/mail/draft/service.go (1)

71-73: Consider using callDraftAPI for consistency.

Send still uses runtime.CallAPI directly while other functions (GetRaw, CreateWithRaw, UpdateWithRaw) now use the centralized callDraftAPI helper. This inconsistency means Send won't benefit from the unified error handling and meta extraction in extractAPIDataAndMeta.

♻️ Optional: Refactor to use callDraftAPI
 func Send(runtime *common.RuntimeContext, mailboxID, draftID string) (map[string]interface{}, error) {
-	return runtime.CallAPI("POST", mailboxPath(mailboxID, "drafts", draftID, "send"), nil, nil)
+	data, _, err := callDraftAPI(runtime, "POST", mailboxPath(mailboxID, "drafts", draftID, "send"), nil, nil)
+	return data, err
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/mail/draft/service.go` around lines 71 - 73, The Send function
currently calls runtime.CallAPI directly which bypasses the centralized handling
in callDraftAPI; update Send to invoke callDraftAPI with the same HTTP method
and path (use mailboxPath(mailboxID, "drafts", draftID, "send")) and the same
payload/params (nil, nil) so it benefits from extractAPIDataAndMeta and unified
error handling; locate the Send function and replace the runtime.CallAPI call
with callDraftAPI(runtime, "POST", mailboxPath(...), nil, nil) ensuring the
return types remain map[string]interface{}, error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@shortcuts/mail/draft/service.go`:
- Around line 103-128: The extractAPIDataAndMeta function currently ignores the
boolean return from util.ToFloat64(resultMap["code"]) and treats missing or
malformed code as success; update extractAPIDataAndMeta to check the ok flag
returned by util.ToFloat64 and return a clear error (using action context) when
the code field is missing or not a number, e.g. validate the presence/type of
resultMap["code"] before casting to int and only proceed with success/error
handling when ok is true, referencing extractAPIDataAndMeta and util.ToFloat64
to locate the change.

---

Nitpick comments:
In `@shortcuts/mail/draft/service.go`:
- Around line 71-73: The Send function currently calls runtime.CallAPI directly
which bypasses the centralized handling in callDraftAPI; update Send to invoke
callDraftAPI with the same HTTP method and path (use mailboxPath(mailboxID,
"drafts", draftID, "send")) and the same payload/params (nil, nil) so it
benefits from extractAPIDataAndMeta and unified error handling; locate the Send
function and replace the runtime.CallAPI call with callDraftAPI(runtime, "POST",
mailboxPath(...), nil, nil) ensuring the return types remain
map[string]interface{}, error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f1f5e270-cbe3-4f1d-bc5f-1b3fd9431704

📥 Commits

Reviewing files that changed from the base of the PR and between 16312db and 48a2b27.

📒 Files selected for processing (11)
  • shortcuts/mail/draft/model.go
  • shortcuts/mail/draft/service.go
  • shortcuts/mail/draft/service_test.go
  • shortcuts/mail/helpers.go
  • shortcuts/mail/mail_draft_create.go
  • shortcuts/mail/mail_draft_edit.go
  • shortcuts/mail/mail_forward.go
  • shortcuts/mail/mail_reply.go
  • shortcuts/mail/mail_reply_all.go
  • shortcuts/mail/mail_send.go
  • shortcuts/mail/mail_send_confirm_output_test.go
✅ Files skipped from review due to trivial changes (2)
  • shortcuts/mail/mail_draft_create.go
  • shortcuts/mail/helpers.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • shortcuts/mail/mail_forward.go
  • shortcuts/mail/mail_draft_edit.go
  • shortcuts/mail/mail_send.go
  • shortcuts/mail/mail_reply_all.go

Comment thread shortcuts/mail/draft/service.go Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@shortcuts/mail/draft/service.go`:
- Around line 107-116: The extractReference function only looks for "reference"
at the current level and inside "draft", so it misses cases where the URL is
nested under a "meta" object (or draft.meta). Update extractReference to also
check if data["meta"] is a map and if meta["reference"] (or meta["url"] if
backend uses that) is a non-empty string, returning its trimmed value;
additionally, when recursing into draft (in the branch that checks
data["draft"]), also examine draft["meta"] the same way so nested draft.meta
references are captured.

In `@shortcuts/mail/mail_forward.go`:
- Around line 213-219: The non-confirm-send branch of SendForward currently
returns draft_id and tip but omits the draftResult.Reference (preview URL);
update the branch inside the if !confirmSend block to include
draftResult.Reference in the runtime.Out payload (e.g., add a "reference" or
"preview_url" field), and ensure hintSendDraft or its call receives/passes that
reference so clients can open the generated draft directly; modify the map
passed to runtime.Out and the hintSendDraft invocation to include
draftResult.Reference while keeping existing draft_id and tip fields.

In `@shortcuts/mail/mail_send.go`:
- Around line 140-146: The save-draft branch that handles !confirmSend currently
emits only draft_id and tip and omits draftResult.Reference (the preview URL
returned by CreateWithRaw); update the branch in mail_send.go so the runtime.Out
payload includes draftResult.Reference (e.g., add "reference":
draftResult.Reference) and ensure hintSendDraft/runtime.Out still behave as
before so callers receive the preview URL when --confirm-send is false.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3321e630-d28e-4811-8c3a-b83c2863c227

📥 Commits

Reviewing files that changed from the base of the PR and between 48a2b27 and b915d68.

📒 Files selected for processing (9)
  • shortcuts/mail/draft/model.go
  • shortcuts/mail/draft/service.go
  • shortcuts/mail/draft/service_test.go
  • shortcuts/mail/mail_draft_create.go
  • shortcuts/mail/mail_draft_edit.go
  • shortcuts/mail/mail_forward.go
  • shortcuts/mail/mail_reply.go
  • shortcuts/mail/mail_reply_all.go
  • shortcuts/mail/mail_send.go
✅ Files skipped from review due to trivial changes (1)
  • shortcuts/mail/draft/model.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • shortcuts/mail/draft/service_test.go
  • shortcuts/mail/mail_draft_edit.go
  • shortcuts/mail/mail_reply_all.go
  • shortcuts/mail/mail_reply.go

Comment thread shortcuts/mail/draft/service.go
Comment thread shortcuts/mail/mail_forward.go
Comment thread shortcuts/mail/mail_send.go
@infeng
Copy link
Copy Markdown
Collaborator

infeng commented Apr 16, 2026

配套的 skill 是不是也需要更新,让模型引导用户打开发信预览

Comment thread shortcuts/mail/draft/service.go
@github-actions github-actions bot added size/L Large or sensitive change across domains or core paths and removed size/M Single-domain feat or fix with limited business impact labels Apr 21, 2026
Copy link
Copy Markdown
Collaborator

@chanthuang chanthuang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

代码实现质量不错,reply/forward/reply-all/send 四个 shortcut 的改动一致性好,extractReferencebuildDraftSendOutput 的设计合理,测试覆盖也比较完整。但有几个问题需要修复后再合入。


1. PR 描述与实际代码严重不匹配

PR body 描述的是服务端邮件发送拦截策略(灰度放量、默认拦截、配置项切换),但代码做的是客户端输出增强

  • 在 draft 操作中 surface reference(草稿打开链接)
  • 在 send 输出中 surface automation_send_disable(拦截原因+链接)

建议:重写 PR body 准确描述客户端侧的变更,服务端策略应该放在关联文档/ticket 中说明。

2. 文档中字段名错误 — lark-mail-send.md

如果返回中带有 `draft_url` / `open_url`,应把链接与 `draft_id` 一并返回

实际代码输出的字段名是 reference,不是 draft_url/open_url。其他文档(lark-mail-draft-create.mdlark-mail-draft-edit.md)都正确使用了 reference,只有 lark-mail-send.md 写错了。Skill 如果照文档找这两个字段名会找不到。

3. mail_draft_create.go 没有使用 buildDraftSavedOutput — 输出格式不一致

PR 新增了 buildDraftSavedOutput helper 并在 mail_send.gomail_reply.gomail_forward.gomail_reply_all.go 中统一使用,但 mail_draft_create.go 仍然手动构建输出且缺少 tip 字段。结果是 +draft-create 输出不带 tip,而 +send(草稿模式)、+reply+forward+reply-all 输出都带 tip

如果这是有意为之(+draft-create 专门创建草稿不需要发送提示),建议加注释说明;否则应统一使用 helper。

4. grantMailSendScope 与已有 mailShortcutTestFactoryWithSendScope 功能重复

仓库中已有 mailShortcutTestFactoryWithSendScopemail_send_time_integration_test.go)。新函数的做法是先调 mailShortcutTestFactory 再调 grantMailSendScope 覆盖 token,相比直接用已有 factory 更脆弱(依赖调用顺序)。建议直接复用 mailShortcutTestFactoryWithSendScope

5. 安全策略变更未在 PR 中明确标注

文档从"禁止跳过 Step 1 直接使用 --confirm-send"改为允许"方式 B — 用户已确认时可直接 --confirm-send"。这是安全策略的放宽,涉及 lark-mail-reply.mdlark-mail-reply-all.mdlark-mail-forward.mdlark-mail-send.mdSKILL.md。PR description 完全没有提及,建议显式说明变更原因和风险评估。

6. lark-mail-draft-create.md 中的矛盾注释

-# 2. 在飞书邮件 UI 中预览草稿,或通过 API 获取:
+# 2. 不要把 `user_mailbox.drafts get` 当作拿草稿打开链接的来源
 lark-cli mail user_mailbox.drafts get --params '...'

注释说"不要用 drafts get 拿链接",但紧跟着就是 drafts get 的代码示例。应该移除该示例或改为其他用途说明。

7. buildDraftSendOutput 在 automation_send_disable 场景下仍输出 message_id

文档说当 automation_send_disable 存在时"邮件未真正发出",但代码无论如何都会输出 message_id/thread_id(可能为空或无意义)。考虑当 automation_send_disable 存在且 message_id 为空时,不输出 message_id 以避免下游误判。

@qiooo
Copy link
Copy Markdown
Contributor Author

qiooo commented Apr 21, 2026

  1. PR body 已重写
    现在描述的是这次客户端侧改动:
  • draft/create/update/save-draft 链路透出 reference
  • send 结果透出 automation_send_disable_reason / automation_send_disable_reference
  • 同时补充了 --confirm-send 的行为边界说明
    不再把服务端拦截策略写在这个 PR body 里。
  1. lark-mail-send.md 字段名已修正
    把文档里的 draft_url / open_url 改成了实际输出字段 reference

  2. mail_draft_create.go 保持单独逻辑,但已补注释说明
    这里是有意不复用 buildDraftSavedOutput,因为 +draft-create 只负责创建草稿,不希望附带发送提示;已经在代码里补了注释说明这个设计。

  3. 测试 helper 已调整
    原来的 grantMailSendScope 已去掉,改成直接使用一个带 send scope 的测试 factory,避免“先建 factory 再覆写 token”的脆弱写法。

  4. PR body 里已补充安全策略说明
    现在已经明确写了:

  • 默认仍是保存草稿
  • 只有在用户已经明确确认收件人和内容时,才允许直接 --confirm-send
  • 这是 agent / CLI 侧行为约定澄清,不是服务端策略切换说明
  1. lark-mail-draft-create.md 的矛盾示例已删
    去掉了紧跟在“不要把 drafts get 当作拿链接来源”后面的那段 drafts get 示例。

  2. automation_send_disable 时保留 message_id / thread_id
    这条这次没有改。当前约定里即使出现 automation_send_disable_*,返回里仍可能保留这两个字段;对调用方来说应以 automation_send_disable_reason / automation_send_disable_reference 判断邮件未真正发出,而不是仅凭 message_id 是否存在来判断发送成功。

chanthuang
chanthuang previously approved these changes Apr 21, 2026
Copy link
Copy Markdown
Collaborator

@chanthuang chanthuang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All feedback addressed. LGTM.

  • PR body 准确描述了客户端侧变更
  • 文档字段名已修正为 reference
  • +draft-create 保持独立输出并补了注释,合理
  • 测试 helper 改用独立 factory,更清晰
  • 安全策略变更已在 PR body 中说明
  • 矛盾示例已删除
  • message_id 保留的设计决策理解,由消费者根据 automation_send_disable_* 判断实际发送状态

qiooo added 10 commits April 21, 2026 19:41
- Add draftPreviewURL helpers for send-preview link generation
- Integrate preview_url output in +draft-create, +draft-edit, +reply,
  +forward, +reply-all shortcuts
- Add unit tests (7 test cases, all passing)

Change-Id: Ie3cbb8f96b308aae225bc69f4c3fc2226af0c230
Change-Id: Ibd10767bf4e4de7f453fff72487fe25090e14605
Change-Id: I75a969af29fa862bdf94947a3aa775d6eebee812
Change-Id: Ie5787cf255ec2347c49f0a271209c1a2e4008fe3
Change-Id: Ieaa5afef310edd5253f07eef06678b7a5db38fc0
Change-Id: Ied6031a05bdefecdcf60b09f66c5d3947d849f83
Change-Id: I400b8f9df97d614b33da3cbdde410ef615444741
Change-Id: I23293fe6c2febf248c58ea14c87c05dde49872a1
Change-Id: I747bf54bc3251387b05d94f87fe61da958d78104
Change-Id: I690df5612f36681c1690645d375c5c5e3ef9ca60
Change-Id: I7f73956696c5405d8eb81fcd2128f0e9898ea539
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 21, 2026

Codecov Report

❌ Patch coverage is 82.02247% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.45%. Comparing base (04e3a28) to head (2d8f661).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
shortcuts/mail/draft/service.go 80.76% 4 Missing and 1 partial ⚠️
shortcuts/mail/mail_forward.go 50.00% 3 Missing ⚠️
shortcuts/mail/mail_reply.go 50.00% 3 Missing ⚠️
shortcuts/mail/mail_reply_all.go 50.00% 3 Missing ⚠️
shortcuts/mail/mail_draft_edit.go 87.50% 0 Missing and 1 partial ⚠️
shortcuts/mail/mail_send.go 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #438      +/-   ##
==========================================
+ Coverage   61.05%   61.45%   +0.40%     
==========================================
  Files         400      400              
  Lines       34130    34158      +28     
==========================================
+ Hits        20838    20993     +155     
+ Misses      11368    11195     -173     
- Partials     1924     1970      +46     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 21, 2026

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@2d8f661c50f74f5204f79b46a604c046c5e98e3e

🧩 Skill update

npx skills add qiooo/lark-cli#feat/mail-send-preview -y -g

qiooo added 4 commits April 21, 2026 20:08
Change-Id: I5af612d70b05a3c0d8abbc9561fe76bb83b5b359
Change-Id: I2918226a0eb68a45f6cc4ea997e1c941d8c16d52
Change-Id: I8e0ec37aac48bcda6b5ad948f397d184a2a4d81d
Change-Id: Idd8abdb84613727a24e3fccb7b329e69566bc890
Copy link
Copy Markdown
Collaborator

@chanthuang chanthuang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving after new commits. Changes look good:

  • recall_status 展平处理与 automation_send_disable 策略一致
  • 测试覆盖更完善

@chanthuang chanthuang merged commit cb301a3 into larksuite:main Apr 21, 2026
18 checks passed
@liangshuo-1 liangshuo-1 mentioned this pull request Apr 21, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain/mail PR touches the mail domain size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants