Skip to content

feat: add scheduled send support for all send shortcuts and cancel-scheduled-send#468

Closed
Yi-Jiao-Chan wants to merge 1 commit intolarksuite:mainfrom
Yi-Jiao-Chan:harness/01KP547MGRRAWR0F7WVTKS7G2H-test
Closed

feat: add scheduled send support for all send shortcuts and cancel-scheduled-send#468
Yi-Jiao-Chan wants to merge 1 commit intolarksuite:mainfrom
Yi-Jiao-Chan:harness/01KP547MGRRAWR0F7WVTKS7G2H-test

Conversation

@Yi-Jiao-Chan
Copy link
Copy Markdown

@Yi-Jiao-Chan Yi-Jiao-Chan commented Apr 14, 2026

Generated by the harness-coding skill.

  • Task ID: 01KP342QM8XZZT86SCJJGGK50Y-1
  • Branch: harness/01KP547MGRRAWR0F7WVTKS7G2H-test
  • Target: main

Sprints

ID Title Status Commit
S1 Registry: register cancel-scheduled-send endpoint + send_time field + skill-meta update passed aff3a80
S2 CLI: scheduled send support for all send shortcuts + cancel-scheduled-send shortcut passed bedc4fa

Source specs

  • /tmp/harness-agent-dev/repos/01KP547MGRRAWR0F7WVTKS7G2H-test/01KP342QM8XZZT86SCJJGGK50Y-1/input/tech-design.md

This MR was created autonomously. Quality gates were enforced by the repo's own pre-commit hooks.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added --send-time flag to schedule email delivery for future dates/times across send, reply, reply-all, and forward commands.
    • Added cancel-scheduled-send command to cancel previously scheduled emails.
  • Tests

    • Added comprehensive test coverage for scheduled email functionality.

…heduled-send

Add --send-time flag to +send, +reply, +reply-all, and +forward shortcuts
to support scheduling emails for future delivery via RFC 3339 timestamps.
Add +cancel-scheduled-send shortcut to cancel scheduled sends.
Include time parsing/validation utilities and comprehensive tests.

sprint: S2
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@github-actions github-actions Bot added domain/mail PR touches the mail domain size/L Large or sensitive change across domains or core paths labels Apr 14, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

📝 Walkthrough

Walkthrough

This PR adds scheduled email sending capability to mail shortcuts. It introduces a SendWithBody function to support custom request payloads, adds a new MailCancelScheduledSend shortcut for canceling scheduled messages, and extends four existing mail shortcuts (MailSend, MailReply, MailReplyAll, MailForward) with RFC3339-based --send-time flags. Helper functions validate send times (enforcing a 5-minute minimum lead) and format timestamps for display.

Changes

Cohort / File(s) Summary
Mail Draft Service Enhancement
shortcuts/mail/draft/service.go
Added SendWithBody() function enabling POST requests to draft send endpoints with custom request payloads, complementing the existing Send() function that uses nil bodies.
Cancel Scheduled Send Shortcut
shortcuts/mail/mail_cancel_scheduled_send.go, shortcuts/mail/mail_cancel_scheduled_send_test.go
Implemented new MailCancelScheduledSend shortcut supporting cancellation of scheduled messages via --message-id (required) and --user-mailbox-id (optional, defaults to me). Tests validate success cases for default and custom mailbox IDs, and failure cases for missing flags and API errors.
Scheduled Send Utilities
shortcuts/mail/scheduled_send.go, shortcuts/mail/scheduled_send_test.go
Added helper functions parseAndValidateSendTime() (validates RFC3339 format, enforces 5-minute minimum lead time) and formatScheduledTimeHuman() (converts timestamps to human-readable format with relative times). Tests cover valid/invalid formats, timezone handling, past timestamps, and future-time formatting.
Existing Shortcuts with Scheduled Send Support
shortcuts/mail/mail_send.go, shortcuts/mail/mail_reply.go, shortcuts/mail/mail_reply_all.go, shortcuts/mail/mail_forward.go
Added --send-time flag to all four shortcuts. Updated DryRun descriptions to reflect scheduling vs. immediate send. Enhanced Validate to parse send-time via helper. Modified Execute to conditionally use SendWithBody() with send-time payload when scheduling, and updated output to include status: "scheduled", send_time, and cancel tips when applicable.
Scheduled Send Integration Tests
shortcuts/mail/mail_scheduled_send_test.go
Comprehensive e2e tests for scheduled sending across all modified shortcuts. Tests cover draft-only mode (verifies draft_id), sending with --confirm-send (verifies status=scheduled and message_id), input validation for invalid/near-term send-times, and reply/reply-all/forward flows with message lookup stubs.
Shortcuts Registry
shortcuts/mail/shortcuts.go
Registered new MailCancelScheduledSend shortcut in the Shortcuts() slice.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI
    participant Validate as Validate<br/>(Send-Time)
    participant DryRun as DryRun
    participant Execute as Execute
    participant Draft as Draft<br/>Service
    participant API as Mail API
    
    CLI->>Validate: --send-time flag provided
    Validate->>Validate: parseAndValidateSendTime()<br/>(RFC3339, 5min min lead)
    alt Invalid or Too Soon
        Validate-->>CLI: ErrValidation
    else Valid
        Validate-->>CLI: Validated time ✓
    end
    
    CLI->>DryRun: Preview operation
    alt --confirm-send + --send-time
        DryRun-->>CLI: "schedule send"<br/>(includes send_time in request)
    else No --send-time
        DryRun-->>CLI: "send" or "save draft"
    end
    
    CLI->>Execute: Confirm send operation
    alt --confirm-send + --send-time
        Execute->>Draft: SendWithBody()<br/>(send_time in payload)
        Draft->>API: POST /drafts/{id}/send<br/>with {send_time}
        API-->>Draft: {message_id, status}
        Draft-->>Execute: message_id
        Execute-->>CLI: status: "scheduled"<br/>send_time, message_id<br/>+cancel-scheduled-send tip
    else --confirm-send only
        Execute->>Draft: Send()
        Draft->>API: POST /drafts/{id}/send
        API-->>Draft: {message_id, status}
        Draft-->>Execute: message_id
        Execute-->>CLI: status: "sent"
    else No --confirm-send
        Execute-->>CLI: Draft saved<br/>(+ scheduling tip if --send-time set)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

domain/mail, size/L

Suggested reviewers

  • chanthuang
  • fangshuyu-768
  • liangshuo-1

Poem

🐰 A rabbit hops through scheduled mail,
With --send-time flags that never fail,
Five minutes hence, a message sent,
Or cancel quick if time's misspent! ✉️⏰

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The provided description does not follow the repository's template structure (missing Summary, Changes, Test Plan, and Related Issues sections) and instead uses task/sprint tracking format. Rewrite the description to follow the required template: include a brief Summary, itemized Changes list, Test Plan with checkboxes, and Related Issues section.
Docstring Coverage ⚠️ Warning Docstring coverage is 35.14% 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
Title check ✅ Passed The title accurately and concisely summarizes the main changes: adding scheduled send support to all send shortcuts and a new cancel-scheduled-send shortcut.

✏️ 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

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

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: 7

🧹 Nitpick comments (1)
shortcuts/mail/mail_scheduled_send_test.go (1)

46-83: Assert the /send request body in the shared scheduled-send stubs.

The confirm-send tests below currently pass as long as /drafts/.../send is called. They do not prove that the shortcut actually sent {"send_time": ...}, so a regression back to draftpkg.Send(...) would still go green. Please make these stubs validate the request payload, and ideally assert the echoed send_time/cancel tip in stdout too.

Also applies to: 108-165

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/mail/mail_scheduled_send_test.go` around lines 46 - 83, The current
stubScheduledSendEndpoints stub for the POST
"/user_mailboxes/me/drafts/draft_sched001/send" only checks that the endpoint is
hit; update stubScheduledSendEndpoints to assert the incoming request body
contains the expected send payload (e.g., presence and value/format of
"send_time" and any cancel tip field) by adding request body
validation/assertion to the httpmock.Stub for that URL (and mirror the same
validation for the additional stubs around lines 108-165), and update the test
harness to capture and assert the echoed send_time/cancel tip printed to stdout
so the tests fail if the shortcut doesn't send the JSON payload (reference the
function stubScheduledSendEndpoints and the POST stub for
"/user_mailboxes/me/drafts/draft_sched001/send").
🤖 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_cancel_scheduled_send.go`:
- Around line 66-77: The result payload currently only returns "message_id" and
then prints tips that use the wrong command names/flags and the wrong
identifier; update the restore/cancel logic to capture the draft identifier
returned by the restore operation (e.g., draftID variable) and include
"draft_id": draftID in the result map, and change the tip strings to use the
correct commands and flags: "lark-cli mail +draft-edit --draft-id %s" and
"lark-cli mail user_mailbox.drafts send --draft-id %s --confirm-send" (replace
messageID with draftID); reference the result map construction and the
fmt.Fprintf call in mail_cancel_scheduled_send.go and ensure the flag name
matches mail_draft_edit.go's expected --draft-id and the command name matches
helpers.go's user_mailbox.drafts convention.

In `@shortcuts/mail/mail_forward.go`:
- Around line 228-265: The scheduling hint/result currently loses actionable
state and reports the raw --send-time; update the success and draft tips to
include the original forward parameters and use the API-normalized send time
from the response: when creating the draft-only tip (where hintSendDraft is
called) include the original recipients/body/subject (the values used to create
draft) in the suggested re-run CLI command and when scheduling succeeds (after
draftpkg.SendWithBody / draftpkg.Send) read the normalized timestamp from
resData (e.g. resData["send_time"] or equivalent returned field) and emit that
in outData["send_time"] instead of sendTimeStr, and also build the cancel/re-run
tip to include mailboxID, draftID and preserved recipients/body so the command
is actionable; adjust code around parseAndValidateSendTime,
draftpkg.SendWithBody, hintSendDraft and runtime.Out to pass and log these
fields.

In `@shortcuts/mail/mail_reply_all.go`:
- Around line 205-242: The tip/output for scheduled sends must include all
required rerun inputs and reflect the normalized send time; parse sendTimeStr
with parseAndValidateSendTime whenever you build tips (including the save-only
branch that calls hintSendDraft) so you can echo validatedTime (UTC-normalized)
not the raw flag, and when constructing the final outData/tip after
draftpkg.Send or draftpkg.SendWithBody include the actual message id
(resData["message_id"]) and either the draft id or body flag needed to rerun
(e.g. --message-id <message_id> --body '<body>' or --draft-id <draftID>), and
ensure the scheduled tip uses validatedTime; update code paths around
draftpkg.SendWithBody, draftpkg.Send, runtime.Out and hintSendDraft to use
validatedTime and resData values when building the tip.

In `@shortcuts/mail/mail_reply.go`:
- Around line 190-200: The tip shown when confirmSend is false and sendTimeStr
!= "" is incorrect because it tells the user to rerun "+reply" (which requires
--message-id/--body) instead of scheduling the draft that was just created;
update the branch that sets outData["tip"] (the block referencing sendTimeStr,
draftID, mailboxID and runtime.Out) to instead show a runnable command that
schedules the returned draft, e.g. use the existing drafts send command with the
draft_id and user_mailbox_id and send_time and include --confirm-send (format
the tip with mailboxID, draftID and sendTimeStr), and keep calling
hintSendDraft(runtime, mailboxID, draftID) unchanged.
- Around line 48-60: The dry-run currently attaches send_time to the draft
creation body (variable body) which makes the preview inaccurate; instead, do
not include send_time in the draft creation body and, when sendTimeStr != "" and
confirmSend is true, attach send_time to the dry-run POST for the send step (the
POST to mailboxPath(mailboxID, "drafts", "<draft_id>", "send")). Locate the
body/sendTimeStr logic and the api builder (variables body, sendTimeStr, api and
confirmSend in mail_reply.go) and change it so send_time is omitted from the
draft POST and added to the send POST's body (or Body call) for the send step
only. Ensure mailboxPath(..., "drafts") uses the draft-only body and
mailboxPath(..., "drafts", "<draft_id>", "send") carries the send_time when
confirmSend is set.

In `@shortcuts/mail/mail_send.go`:
- Around line 154-164: The tip for scheduling a saved draft is incorrect: update
the message and any hint to tell users to rerun the send command including the
draft_id (and mailbox id) so the saved draft is scheduled rather than creating a
new draft. Specifically, change the formatted tip in the !confirmSend branch
(where sendTimeStr, mailboxID, draftID are used) to show a command including
--draft-id "<draftID>" (and --user_mailbox_id "<mailboxID>") together with
--send-time and --confirm-send, and ensure hintSendDraft(runtime, mailboxID,
draftID) (or its scheduling-aware overload) is invoked/updated so the helper
also references the draft_id when sendTimeStr is present.
- Around line 49-66: The dry-run currently adds send_time to bodyPreview which
is used for POST /drafts, but the real flow sends the schedule in the send
request (draftpkg.SendWithBody -> POST /drafts/{draft_id}/send); modify the
construction so bodyPreview (used with POST mailboxPath(..., "drafts")) does not
include send_time, and when confirmSend is true create a separate sendBody that
includes send_time (or attach send_time to the Body for the POST
mailboxPath(mailboxID, "drafts", "<draft_id>", "send")) and chain that POST with
Body(sendBody) to mirror SendWithBody; update the api variable accordingly
(referencing bodyPreview, sendBody, api, mailboxPath, confirmSend).

---

Nitpick comments:
In `@shortcuts/mail/mail_scheduled_send_test.go`:
- Around line 46-83: The current stubScheduledSendEndpoints stub for the POST
"/user_mailboxes/me/drafts/draft_sched001/send" only checks that the endpoint is
hit; update stubScheduledSendEndpoints to assert the incoming request body
contains the expected send payload (e.g., presence and value/format of
"send_time" and any cancel tip field) by adding request body
validation/assertion to the httpmock.Stub for that URL (and mirror the same
validation for the additional stubs around lines 108-165), and update the test
harness to capture and assert the echoed send_time/cancel tip printed to stdout
so the tests fail if the shortcut doesn't send the JSON payload (reference the
function stubScheduledSendEndpoints and the POST stub for
"/user_mailboxes/me/drafts/draft_sched001/send").
🪄 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: f365aceb-0714-4d48-81f7-d5964eedfe4a

📥 Commits

Reviewing files that changed from the base of the PR and between 20761fa and bedc4fa.

📒 Files selected for processing (11)
  • shortcuts/mail/draft/service.go
  • shortcuts/mail/mail_cancel_scheduled_send.go
  • shortcuts/mail/mail_cancel_scheduled_send_test.go
  • shortcuts/mail/mail_forward.go
  • shortcuts/mail/mail_reply.go
  • shortcuts/mail/mail_reply_all.go
  • shortcuts/mail/mail_scheduled_send_test.go
  • shortcuts/mail/mail_send.go
  • shortcuts/mail/scheduled_send.go
  • shortcuts/mail/scheduled_send_test.go
  • shortcuts/mail/shortcuts.go

Comment on lines +66 to +77
result := map[string]interface{}{
"message_id": messageID,
"status": "cancelled",
"restored_as_draft": true,
}

runtime.Out(result, nil)
fmt.Fprintf(runtime.IO().ErrOut,
"tip: The scheduled send has been cancelled and the message restored as a draft.\n"+
" Use lark-cli mail +draft-edit --id %s to edit\n"+
" Use lark-cli mail user-mailbox-drafts send --draft-id %s --confirm-send to send immediately\n",
messageID, messageID)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C3 '\+draft-edit|user_mailbox\.drafts send|user-mailbox-drafts send|--draft-id|--id' shortcuts/mail

Repository: larksuite/cli

Length of output: 8584


Fix the follow-up commands: use correct command names, flags, and draft identifiers.

After cancel, the tip instructions have three issues:

  1. Command name mismatch: Uses user-mailbox-drafts send instead of user_mailbox.drafts send (which all other draft shortcuts use, see helpers.go:44)
  2. Flag name error: Suggests +draft-edit --id instead of +draft-edit --draft-id (required by mail_draft_edit.go:46,77)
  3. Wrong identifier: Both commands receive messageID but should receive a draft_id returned by the cancel operation

The result payload (lines 66-72) captures only message_id and does not extract the draft_id that the restore operation should return. Either the API response includes a draft ID that must be captured in the result, or the tips should not suggest commands that cannot be executed with the available data.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/mail/mail_cancel_scheduled_send.go` around lines 66 - 77, The
result payload currently only returns "message_id" and then prints tips that use
the wrong command names/flags and the wrong identifier; update the
restore/cancel logic to capture the draft identifier returned by the restore
operation (e.g., draftID variable) and include "draft_id": draftID in the result
map, and change the tip strings to use the correct commands and flags: "lark-cli
mail +draft-edit --draft-id %s" and "lark-cli mail user_mailbox.drafts send
--draft-id %s --confirm-send" (replace messageID with draftID); reference the
result map construction and the fmt.Fprintf call in
mail_cancel_scheduled_send.go and ensure the flag name matches
mail_draft_edit.go's expected --draft-id and the command name matches
helpers.go's user_mailbox.drafts convention.

Comment on lines +228 to +265
outData := map[string]interface{}{
"draft_id": draftID,
"tip": fmt.Sprintf(`draft saved. To send: lark-cli mail user_mailbox.drafts send --params '{"user_mailbox_id":"%s","draft_id":"%s"}'`, mailboxID, draftID),
}, nil)
}
if sendTimeStr != "" {
outData["tip"] = fmt.Sprintf(
`draft saved. To schedule send, add --confirm-send: lark-cli mail +forward --send-time %q --confirm-send`, sendTimeStr)
}
runtime.Out(outData, nil)
hintSendDraft(runtime, mailboxID, draftID)
return nil
}
resData, err := draftpkg.Send(runtime, mailboxID, draftID)
var resData map[string]interface{}
if sendTimeStr != "" {
validatedTime, parseErr := parseAndValidateSendTime(sendTimeStr)
if parseErr != nil {
return parseErr
}
sendBody := map[string]interface{}{"send_time": validatedTime}
resData, err = draftpkg.SendWithBody(runtime, mailboxID, draftID, sendBody)
} else {
resData, err = draftpkg.Send(runtime, mailboxID, draftID)
}
if err != nil {
return fmt.Errorf("failed to send forward (draft %s created but not sent): %w", draftID, err)
}
runtime.Out(map[string]interface{}{
outData := map[string]interface{}{
"message_id": resData["message_id"],
"thread_id": resData["thread_id"],
}, nil)
}
if sendTimeStr != "" {
outData["status"] = "scheduled"
outData["send_time"] = sendTimeStr
if msgID, ok := resData["message_id"].(string); ok && msgID != "" {
outData["tip"] = fmt.Sprintf("To cancel: lark-cli mail +cancel-scheduled-send --message-id %s", msgID)
}
}
runtime.Out(outData, nil)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

The scheduling hint/result should preserve actionable state.

The draft-only tip here cannot be used to rerun the forward because it drops the original recipients/body context. Also, once scheduling succeeds, stdout should report the normalized timestamp actually sent to the API instead of the raw --send-time input.

🤖 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 228 - 265, The scheduling
hint/result currently loses actionable state and reports the raw --send-time;
update the success and draft tips to include the original forward parameters and
use the API-normalized send time from the response: when creating the draft-only
tip (where hintSendDraft is called) include the original recipients/body/subject
(the values used to create draft) in the suggested re-run CLI command and when
scheduling succeeds (after draftpkg.SendWithBody / draftpkg.Send) read the
normalized timestamp from resData (e.g. resData["send_time"] or equivalent
returned field) and emit that in outData["send_time"] instead of sendTimeStr,
and also build the cancel/re-run tip to include mailboxID, draftID and preserved
recipients/body so the command is actionable; adjust code around
parseAndValidateSendTime, draftpkg.SendWithBody, hintSendDraft and runtime.Out
to pass and log these fields.

Comment on lines +205 to +242
outData := map[string]interface{}{
"draft_id": draftID,
"tip": fmt.Sprintf(`draft saved. To send: lark-cli mail user_mailbox.drafts send --params '{"user_mailbox_id":"%s","draft_id":"%s"}'`, mailboxID, draftID),
}, nil)
}
if sendTimeStr != "" {
outData["tip"] = fmt.Sprintf(
`draft saved. To schedule send, add --confirm-send: lark-cli mail +reply-all --send-time %q --confirm-send`, sendTimeStr)
}
runtime.Out(outData, nil)
hintSendDraft(runtime, mailboxID, draftID)
return nil
}
resData, err := draftpkg.Send(runtime, mailboxID, draftID)
var resData map[string]interface{}
if sendTimeStr != "" {
validatedTime, parseErr := parseAndValidateSendTime(sendTimeStr)
if parseErr != nil {
return parseErr
}
sendBody := map[string]interface{}{"send_time": validatedTime}
resData, err = draftpkg.SendWithBody(runtime, mailboxID, draftID, sendBody)
} else {
resData, err = draftpkg.Send(runtime, mailboxID, draftID)
}
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{}{
outData := map[string]interface{}{
"message_id": resData["message_id"],
"thread_id": resData["thread_id"],
}, nil)
}
if sendTimeStr != "" {
outData["status"] = "scheduled"
outData["send_time"] = sendTimeStr
if msgID, ok := resData["message_id"].(string); ok && msgID != "" {
outData["tip"] = fmt.Sprintf("To cancel: lark-cli mail +cancel-scheduled-send --message-id %s", msgID)
}
}
runtime.Out(outData, nil)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

The new scheduling tip/output drops information the user needs.

When --send-time is set without --confirm-send, the emitted tip cannot be rerun as-is because it omits required inputs like --message-id and --body. And on the scheduled path, you already compute validatedTime for the API call but still echo the raw flag value back to stdout, which is misleading for timezone-less input because that gets normalized to UTC before send.

🤖 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 205 - 242, The tip/output for
scheduled sends must include all required rerun inputs and reflect the
normalized send time; parse sendTimeStr with parseAndValidateSendTime whenever
you build tips (including the save-only branch that calls hintSendDraft) so you
can echo validatedTime (UTC-normalized) not the raw flag, and when constructing
the final outData/tip after draftpkg.Send or draftpkg.SendWithBody include the
actual message id (resData["message_id"]) and either the draft id or body flag
needed to rerun (e.g. --message-id <message_id> --body '<body>' or --draft-id
<draftID>), and ensure the scheduled tip uses validatedTime; update code paths
around draftpkg.SendWithBody, draftpkg.Send, runtime.Out and hintSendDraft to
use validatedTime and resData values when building the tip.

Comment on lines +48 to 60
body := map[string]interface{}{"raw": "<base64url-EML>"}
if sendTimeStr != "" {
body["send_time"] = sendTimeStr
}
api := common.NewDryRunAPI().
Desc(desc).
GET(mailboxPath(mailboxID, "messages", messageId)).
GET(mailboxPath(mailboxID, "profile")).
POST(mailboxPath(mailboxID, "drafts")).
Body(map[string]interface{}{"raw": "<base64url-EML>"})
Body(body)
if confirmSend {
api = api.POST(mailboxPath(mailboxID, "drafts", "<draft_id>", "send"))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Attach send_time to the dry-run /send step, not POST /drafts.

Execute only submits send_time via draftpkg.SendWithBody(...) on /drafts/{draft_id}/send, but this dry-run always shows it on the draft-creation body and never on the send call. That makes the preview inaccurate for both draft-only and --confirm-send flows.

Suggested adjustment
-		body := map[string]interface{}{"raw": "<base64url-EML>"}
-		if sendTimeStr != "" {
+		body := map[string]interface{}{"raw": "<base64url-EML>"}
+		if confirmSend && sendTimeStr != "" {
 			body["send_time"] = sendTimeStr
 		}
 		api := common.NewDryRunAPI().
 			Desc(desc).
 			GET(mailboxPath(mailboxID, "messages", messageId)).
 			GET(mailboxPath(mailboxID, "profile")).
 			POST(mailboxPath(mailboxID, "drafts")).
 			Body(body)
 		if confirmSend {
 			api = api.POST(mailboxPath(mailboxID, "drafts", "<draft_id>", "send"))
+			if sendTimeStr != "" {
+				api = api.Body(map[string]interface{}{"send_time": sendTimeStr})
+			}
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
body := map[string]interface{}{"raw": "<base64url-EML>"}
if sendTimeStr != "" {
body["send_time"] = sendTimeStr
}
api := common.NewDryRunAPI().
Desc(desc).
GET(mailboxPath(mailboxID, "messages", messageId)).
GET(mailboxPath(mailboxID, "profile")).
POST(mailboxPath(mailboxID, "drafts")).
Body(map[string]interface{}{"raw": "<base64url-EML>"})
Body(body)
if confirmSend {
api = api.POST(mailboxPath(mailboxID, "drafts", "<draft_id>", "send"))
}
body := map[string]interface{}{"raw": "<base64url-EML>"}
if confirmSend && sendTimeStr != "" {
body["send_time"] = sendTimeStr
}
api := common.NewDryRunAPI().
Desc(desc).
GET(mailboxPath(mailboxID, "messages", messageId)).
GET(mailboxPath(mailboxID, "profile")).
POST(mailboxPath(mailboxID, "drafts")).
Body(body)
if confirmSend {
api = api.POST(mailboxPath(mailboxID, "drafts", "<draft_id>", "send"))
if sendTimeStr != "" {
api = api.Body(map[string]interface{}{"send_time": sendTimeStr})
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/mail/mail_reply.go` around lines 48 - 60, The dry-run currently
attaches send_time to the draft creation body (variable body) which makes the
preview inaccurate; instead, do not include send_time in the draft creation body
and, when sendTimeStr != "" and confirmSend is true, attach send_time to the
dry-run POST for the send step (the POST to mailboxPath(mailboxID, "drafts",
"<draft_id>", "send")). Locate the body/sendTimeStr logic and the api builder
(variables body, sendTimeStr, api and confirmSend in mail_reply.go) and change
it so send_time is omitted from the draft POST and added to the send POST's body
(or Body call) for the send step only. Ensure mailboxPath(..., "drafts") uses
the draft-only body and mailboxPath(..., "drafts", "<draft_id>", "send") carries
the send_time when confirmSend is set.

Comment on lines 190 to 200
if !confirmSend {
runtime.Out(map[string]interface{}{
outData := map[string]interface{}{
"draft_id": draftID,
"tip": fmt.Sprintf(`draft saved. To send: lark-cli mail user_mailbox.drafts send --params '{"user_mailbox_id":"%s","draft_id":"%s"}'`, mailboxID, draftID),
}, nil)
}
if sendTimeStr != "" {
outData["tip"] = fmt.Sprintf(
`draft saved. To schedule send, add --confirm-send: lark-cli mail +reply --send-time %q --confirm-send`, sendTimeStr)
}
runtime.Out(outData, nil)
hintSendDraft(runtime, mailboxID, draftID)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The draft-only scheduling tip is not runnable.

Line 197 suggests rerunning +reply with only --send-time and --confirm-send, but +reply still requires --message-id and --body, and rerunning it would create a new draft instead of scheduling the draft_id you just returned.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/mail/mail_reply.go` around lines 190 - 200, The tip shown when
confirmSend is false and sendTimeStr != "" is incorrect because it tells the
user to rerun "+reply" (which requires --message-id/--body) instead of
scheduling the draft that was just created; update the branch that sets
outData["tip"] (the block referencing sendTimeStr, draftID, mailboxID and
runtime.Out) to instead show a runnable command that schedules the returned
draft, e.g. use the existing drafts send command with the draft_id and
user_mailbox_id and send_time and include --confirm-send (format the tip with
mailboxID, draftID and sendTimeStr), and keep calling hintSendDraft(runtime,
mailboxID, draftID) unchanged.

Comment on lines +49 to 66
bodyPreview := map[string]interface{}{
"raw": "<base64url-EML>",
"_preview": map[string]interface{}{
"to": to,
"subject": subject,
},
}
if sendTimeStr != "" {
bodyPreview["send_time"] = sendTimeStr
}
api := common.NewDryRunAPI().
Desc(desc).
GET(mailboxPath(mailboxID, "profile")).
POST(mailboxPath(mailboxID, "drafts")).
Body(map[string]interface{}{
"raw": "<base64url-EML>",
"_preview": map[string]interface{}{
"to": to,
"subject": subject,
},
})
Body(bodyPreview)
if confirmSend {
api = api.POST(mailboxPath(mailboxID, "drafts", "<draft_id>", "send"))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Dry-run is previewing send_time on the wrong request.

At runtime the schedule is sent through draftpkg.SendWithBody(...) to /drafts/{draft_id}/send, but this preview adds send_time to POST /drafts whenever the flag is present. The dry-run should mirror the actual request sequence.

Suggested adjustment
-		bodyPreview := map[string]interface{}{
+		bodyPreview := map[string]interface{}{
 			"raw": "<base64url-EML>",
 			"_preview": map[string]interface{}{
 				"to":      to,
 				"subject": subject,
 			},
 		}
-		if sendTimeStr != "" {
+		if confirmSend && sendTimeStr != "" {
 			bodyPreview["send_time"] = sendTimeStr
 		}
 		api := common.NewDryRunAPI().
 			Desc(desc).
 			GET(mailboxPath(mailboxID, "profile")).
 			POST(mailboxPath(mailboxID, "drafts")).
 			Body(bodyPreview)
 		if confirmSend {
 			api = api.POST(mailboxPath(mailboxID, "drafts", "<draft_id>", "send"))
+			if sendTimeStr != "" {
+				api = api.Body(map[string]interface{}{"send_time": sendTimeStr})
+			}
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/mail/mail_send.go` around lines 49 - 66, The dry-run currently adds
send_time to bodyPreview which is used for POST /drafts, but the real flow sends
the schedule in the send request (draftpkg.SendWithBody -> POST
/drafts/{draft_id}/send); modify the construction so bodyPreview (used with POST
mailboxPath(..., "drafts")) does not include send_time, and when confirmSend is
true create a separate sendBody that includes send_time (or attach send_time to
the Body for the POST mailboxPath(mailboxID, "drafts", "<draft_id>", "send"))
and chain that POST with Body(sendBody) to mirror SendWithBody; update the api
variable accordingly (referencing bodyPreview, sendBody, api, mailboxPath,
confirmSend).

Comment on lines 154 to 164
if !confirmSend {
runtime.Out(map[string]interface{}{
outData := map[string]interface{}{
"draft_id": draftID,
"tip": fmt.Sprintf(`draft saved. To send: lark-cli mail user_mailbox.drafts send --params '{"user_mailbox_id":"%s","draft_id":"%s"}'`, mailboxID, draftID),
}, nil)
}
if sendTimeStr != "" {
outData["tip"] = fmt.Sprintf(
`draft saved. To schedule send, add --confirm-send: lark-cli mail +send --send-time %q --confirm-send`, sendTimeStr)
}
runtime.Out(outData, nil)
hintSendDraft(runtime, mailboxID, draftID)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This “schedule later” tip will not work for the saved draft.

Line 161 tells users to rerun +send with only --send-time and --confirm-send, but that command is missing the required compose flags and would create a new draft instead of scheduling the draft_id returned on Line 156.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/mail/mail_send.go` around lines 154 - 164, The tip for scheduling a
saved draft is incorrect: update the message and any hint to tell users to rerun
the send command including the draft_id (and mailbox id) so the saved draft is
scheduled rather than creating a new draft. Specifically, change the formatted
tip in the !confirmSend branch (where sendTimeStr, mailboxID, draftID are used)
to show a command including --draft-id "<draftID>" (and --user_mailbox_id
"<mailboxID>") together with --send-time and --confirm-send, and ensure
hintSendDraft(runtime, mailboxID, draftID) (or its scheduling-aware overload) is
invoked/updated so the helper also references the draft_id when sendTimeStr is
present.

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.

3 participants