feat(mail): support --send-separately and set_send_separately patch op#835
feat(mail): support --send-separately and set_send_separately patch op#835sysuljx wants to merge 2 commits into
Conversation
Add a draft-level "send separately" flag to the mail compose link: - +draft-create and +send accept --send-separately, which injects an X-Lms-Send-Separately: 1 EML header into the base64url-encoded raw body posted to drafts.create. The server reads the header at send time and delivers per-recipient copies so each To/Cc recipient only sees themselves; Bcc visibility is unchanged. - +draft-edit gains a set_send_separately patch op that upserts the header for "true"/"1" and removes it for "false"/"0". - Dry-run preview for both compose shortcuts surfaces a send_separately field so callers can confirm the flag took effect without sending. A single effective recipient triggers a stderr warning (the feature has no observable effect) but is not rejected so users can still add recipients later via +draft-edit. Backend errno 6002 paired with --send-separately is wrapped with a hint pointing at the most common cause (server not yet updated). - Reference docs (mail-draft-create / mail-send / mail-draft-edit), the mail skill-template, and the patch template emitted by --print-patch-template document the new flag and patch op plus a comparison with Bcc semantics. - Tests cover header injection, dry-run preview, single-recipient warning, patch apply (true/false/1/0/case variants), and Validate rejection for invalid values. sprint: S9
Replace internal backend service names in the helpers.go const comment and skill-template Send Separately concept entry with a neutral "mail backend" phrasing. Behavior is unchanged; the X-Lms-Send-Separately header semantics and per-recipient delivery contract are preserved. sprint: S9
|
lijiexiang seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
📝 WalkthroughWalkthroughThis PR adds a ChangesSend Separately Feature Implementation
🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
shortcuts/mail/draft/patch_send_separately_test.go (1)
23-51: ⚡ Quick winConsider adding an idempotent-upsert test when header already exists.
Current tests verify insertion/removal well, but not the “already present + set true again” path. A small case here would catch accidental duplicate-header regressions.
Optional extra test case
+func TestApplySetSendSeparatelyTrueKeepsSingleHeaderWhenAlreadyPresent(t *testing.T) { + snapshot := mustParseFixtureDraft(t, `Subject: Test +From: Alice <alice@example.com> +To: Bob <bob@example.com> +X-Lms-Send-Separately: 1 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 + +hello +`) + err := Apply(&DraftCtx{FIO: testFIO}, snapshot, Patch{ + Ops: []PatchOp{{Op: "set_send_separately", Value: "true"}}, + }) + if err != nil { + t.Fatalf("Apply() error = %v", err) + } + if got := headerValue(snapshot.Headers, "X-Lms-Send-Separately"); got != "1" { + t.Fatalf("X-Lms-Send-Separately = %q, want %q", got, "1") + } + if c := strings.Count(snapshot.Headers.String(), "X-Lms-Send-Separately:"); c != 1 { + t.Fatalf("expected exactly one X-Lms-Send-Separately header, got %d", c) + } +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shortcuts/mail/draft/patch_send_separately_test.go` around lines 23 - 51, Add an idempotency test that re-applies set_send_separately with a true value when the X-Lms-Send-Separately header already exists: create a snapshot (mustParseFixtureDraft/fixtureSendSeparatelyDraft), pre-insert or ensure snapshot.Headers contains X-Lms-Send-Separately="1", call Apply(&DraftCtx{FIO: testFIO}, snapshot, Patch{Ops: []PatchOp{{Op: "set_send_separately", Value: "true"}}}), then assert no duplicate headers were added and headerValue(snapshot.Headers, "X-Lms-Send-Separately") still equals "1"; place this alongside TestApplySetSendSeparatelyTrueAddsHeader/TestApplySetSendSeparatelyOneAddsHeader to catch duplicate-header regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@shortcuts/mail/helpers.go`:
- Around line 2101-2107: The current isMailErrno6002 uses strings.Contains which
can false-positive on numbers like "16002"; update isMailErrno6002 to match the
standalone error code using a regex word-boundary match (e.g., use
regexp.MatchString with pattern `\b6002\b`) so only the exact token "6002" (not
digits embedded in other numbers) triggers true; compile the regexp once
(package-level var) and replace the strings.Contains check in isMailErrno6002
with regexp.Match to avoid performance hits and ensure correct matching.
In `@shortcuts/mail/mail_draft_create_test.go`:
- Around line 489-495: The test currently checks for `"send_separately"` and
then a separate `"true"` which can yield false positives; update the second
assertion to check the full key/value pair (e.g. assert out contains
`"send_separately": true` or `"send_separately":true`) so the dry-run preview
actually shows send_separately set to true; alternatively parse stdout into JSON
and assert that the send_separately field is present and equals true (use the
existing out/stdout variables to locate the change).
In `@shortcuts/mail/mail_draft_create.go`:
- Around line 177-180: The warning logic currently counts BCC when computing
total recipients, which can suppress the single-recipient warning; change the
computation to only include To and CC addresses (use countAddresses(input.To) +
countAddresses(input.CC)) so the warning about --send-separately triggers
correctly when there is only one visible To/Cc recipient; update the block
around total := ... and the subsequent if total == 1 check in
mail_draft_create.go (referencing countAddresses and input.To/input.CC)
accordingly.
In `@shortcuts/mail/mail_send.go`:
- Around line 188-191: The warning for --send-separately is incorrectly counting
Bcc recipients; change the visible-recipient calculation to only include To and
Cc by replacing total := countAddresses(to) + countAddresses(ccFlag) +
countAddresses(bccFlag) with a total that sums only countAddresses(to) and
countAddresses(ccFlag), so the fmt.Fprintf warning (runtime.IO().ErrOut) is
emitted based on visible recipients (To/Cc) rather than including bccFlag.
In `@skills/lark-mail/references/lark-mail-draft-edit.md`:
- Around line 187-188: Doc currently lists accepted values for the draft "send
separately" option as "true"/"false"/"1"/"0" but the implementation and tests
accept mixed-case boolean strings; update the description for the `value` of
`X-Lms-Send-Separately` to state values are case-insensitive and that any case
variation of "true"/"1" will write `X-Lms-Send-Separately: 1` while any case
variation of "false"/"0" will remove the header, and that other values will be
rejected by the `Validate` logic; reference the header name
`X-Lms-Send-Separately` and the `Validate` behavior in the text so readers know
the mapping and rejection rules.
---
Nitpick comments:
In `@shortcuts/mail/draft/patch_send_separately_test.go`:
- Around line 23-51: Add an idempotency test that re-applies set_send_separately
with a true value when the X-Lms-Send-Separately header already exists: create a
snapshot (mustParseFixtureDraft/fixtureSendSeparatelyDraft), pre-insert or
ensure snapshot.Headers contains X-Lms-Send-Separately="1", call
Apply(&DraftCtx{FIO: testFIO}, snapshot, Patch{Ops: []PatchOp{{Op:
"set_send_separately", Value: "true"}}}), then assert no duplicate headers were
added and headerValue(snapshot.Headers, "X-Lms-Send-Separately") still equals
"1"; place this alongside
TestApplySetSendSeparatelyTrueAddsHeader/TestApplySetSendSeparatelyOneAddsHeader
to catch duplicate-header regressions.
🪄 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: 89a11182-da9c-4ab2-9095-b54703e3e473
📒 Files selected for processing (13)
shortcuts/mail/draft/model.goshortcuts/mail/draft/patch.goshortcuts/mail/draft/patch_send_separately_test.goshortcuts/mail/helpers.goshortcuts/mail/mail_draft_create.goshortcuts/mail/mail_draft_create_test.goshortcuts/mail/mail_draft_edit.goshortcuts/mail/mail_send.goshortcuts/mail/mail_send_test.goskill-template/domains/mail.mdskills/lark-mail/references/lark-mail-draft-create.mdskills/lark-mail/references/lark-mail-draft-edit.mdskills/lark-mail/references/lark-mail-send.md
| func isMailErrno6002(err error) bool { | ||
| if err == nil { | ||
| return false | ||
| } | ||
| msg := err.Error() | ||
| return strings.Contains(msg, "6002") | ||
| } |
There was a problem hiding this comment.
Tighten errno 6002 matching to avoid false-positive hints.
strings.Contains(msg, "6002") can match unrelated error text (e.g., 16002) and attach the wrong remediation hint.
Suggested fix
+var mailErrno6002Re = regexp.MustCompile(`(?:^|[^0-9])6002(?:[^0-9]|$)`)
+
func isMailErrno6002(err error) bool {
if err == nil {
return false
}
- msg := err.Error()
- return strings.Contains(msg, "6002")
+ return mailErrno6002Re.MatchString(err.Error())
}📝 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.
| func isMailErrno6002(err error) bool { | |
| if err == nil { | |
| return false | |
| } | |
| msg := err.Error() | |
| return strings.Contains(msg, "6002") | |
| } | |
| var mailErrno6002Re = regexp.MustCompile(`(?:^|[^0-9])6002(?:[^0-9]|$)`) | |
| func isMailErrno6002(err error) bool { | |
| if err == nil { | |
| return false | |
| } | |
| return mailErrno6002Re.MatchString(err.Error()) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@shortcuts/mail/helpers.go` around lines 2101 - 2107, The current
isMailErrno6002 uses strings.Contains which can false-positive on numbers like
"16002"; update isMailErrno6002 to match the standalone error code using a regex
word-boundary match (e.g., use regexp.MatchString with pattern `\b6002\b`) so
only the exact token "6002" (not digits embedded in other numbers) triggers
true; compile the regexp once (package-level var) and replace the
strings.Contains check in isMailErrno6002 with regexp.Match to avoid performance
hits and ensure correct matching.
| out := stdout.String() | ||
| if !strings.Contains(out, `"send_separately"`) { | ||
| t.Fatalf("expected dry-run preview to surface send_separately, got: %s", out) | ||
| } | ||
| if !strings.Contains(out, "true") { | ||
| t.Fatalf("expected dry-run preview send_separately=true, got: %s", out) | ||
| } |
There was a problem hiding this comment.
Tighten the dry-run assertion to avoid false positives.
The current "true" substring check can pass even if send_separately is false but another field is true. Assert the key/value pair together.
Suggested test assertion hardening
out := stdout.String()
if !strings.Contains(out, `"send_separately"`) {
t.Fatalf("expected dry-run preview to surface send_separately, got: %s", out)
}
- if !strings.Contains(out, "true") {
- t.Fatalf("expected dry-run preview send_separately=true, got: %s", out)
- }
+ if !strings.Contains(out, `"send_separately":true`) &&
+ !strings.Contains(out, `"send_separately": true`) {
+ t.Fatalf("expected dry-run preview send_separately=true, got: %s", out)
+ }📝 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.
| out := stdout.String() | |
| if !strings.Contains(out, `"send_separately"`) { | |
| t.Fatalf("expected dry-run preview to surface send_separately, got: %s", out) | |
| } | |
| if !strings.Contains(out, "true") { | |
| t.Fatalf("expected dry-run preview send_separately=true, got: %s", out) | |
| } | |
| out := stdout.String() | |
| if !strings.Contains(out, `"send_separately"`) { | |
| t.Fatalf("expected dry-run preview to surface send_separately, got: %s", out) | |
| } | |
| if !strings.Contains(out, `"send_separately":true`) && | |
| !strings.Contains(out, `"send_separately": true`) { | |
| t.Fatalf("expected dry-run preview send_separately=true, got: %s", out) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@shortcuts/mail/mail_draft_create_test.go` around lines 489 - 495, The test
currently checks for `"send_separately"` and then a separate `"true"` which can
yield false positives; update the second assertion to check the full key/value
pair (e.g. assert out contains `"send_separately": true` or
`"send_separately":true`) so the dry-run preview actually shows send_separately
set to true; alternatively parse stdout into JSON and assert that the
send_separately field is present and equals true (use the existing out/stdout
variables to locate the change).
| total := countAddresses(input.To) + countAddresses(input.CC) + countAddresses(input.BCC) | ||
| if total == 1 { | ||
| fmt.Fprintf(runtime.IO().ErrOut, "warning: --send-separately has no observable effect with only 1 recipient; add more via --cc/--bcc or +draft-edit\n") | ||
| } |
There was a problem hiding this comment.
Single-recipient warning logic should key off To/Cc recipients only.
Including Bcc in the count can hide this warning even when send-separately has no visible To/Cc impact.
Suggested fix
- total := countAddresses(input.To) + countAddresses(input.CC) + countAddresses(input.BCC)
- if total == 1 {
+ visibleRecipients := countAddresses(input.To) + countAddresses(input.CC)
+ if visibleRecipients <= 1 {
fmt.Fprintf(runtime.IO().ErrOut, "warning: --send-separately has no observable effect with only 1 recipient; add more via --cc/--bcc or +draft-edit\n")
}📝 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.
| total := countAddresses(input.To) + countAddresses(input.CC) + countAddresses(input.BCC) | |
| if total == 1 { | |
| fmt.Fprintf(runtime.IO().ErrOut, "warning: --send-separately has no observable effect with only 1 recipient; add more via --cc/--bcc or +draft-edit\n") | |
| } | |
| visibleRecipients := countAddresses(input.To) + countAddresses(input.CC) | |
| if visibleRecipients <= 1 { | |
| fmt.Fprintf(runtime.IO().ErrOut, "warning: --send-separately has no observable effect with only 1 recipient; add more via --cc/--bcc or +draft-edit\n") | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@shortcuts/mail/mail_draft_create.go` around lines 177 - 180, The warning
logic currently counts BCC when computing total recipients, which can suppress
the single-recipient warning; change the computation to only include To and CC
addresses (use countAddresses(input.To) + countAddresses(input.CC)) so the
warning about --send-separately triggers correctly when there is only one
visible To/Cc recipient; update the block around total := ... and the subsequent
if total == 1 check in mail_draft_create.go (referencing countAddresses and
input.To/input.CC) accordingly.
| total := countAddresses(to) + countAddresses(ccFlag) + countAddresses(bccFlag) | ||
| if total == 1 { | ||
| fmt.Fprintf(runtime.IO().ErrOut, "warning: --send-separately has no observable effect with only 1 recipient; add more via --cc/--bcc or +draft-edit\n") | ||
| } |
There was a problem hiding this comment.
Single-recipient warning should be based on To/Cc visibility, not total recipients.
--send-separately changes what To/Cc recipients see; counting Bcc can suppress this warning even when To/Cc has no practical split effect.
Suggested fix
- total := countAddresses(to) + countAddresses(ccFlag) + countAddresses(bccFlag)
- if total == 1 {
+ visibleRecipients := countAddresses(to) + countAddresses(ccFlag)
+ if visibleRecipients <= 1 {
fmt.Fprintf(runtime.IO().ErrOut, "warning: --send-separately has no observable effect with only 1 recipient; add more via --cc/--bcc or +draft-edit\n")
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@shortcuts/mail/mail_send.go` around lines 188 - 191, The warning for
--send-separately is incorrectly counting Bcc recipients; change the
visible-recipient calculation to only include To and Cc by replacing total :=
countAddresses(to) + countAddresses(ccFlag) + countAddresses(bccFlag) with a
total that sums only countAddresses(to) and countAddresses(ccFlag), so the
fmt.Fprintf warning (runtime.IO().ErrOut) is emitted based on visible recipients
(To/Cc) rather than including bccFlag.
| 将草稿标记为「分别发送」:发送时每个 To/Cc 收件人只看见自己(其他 To/Cc 在该副本里被剥离),看不到彼此存在。底层等价于 `set_header` / `remove_header` 操作 `X-Lms-Send-Separately`,但语义更明确,避免书写错头名 / 错值;`value` 只支持 `"true"` / `"false"` / `"1"` / `"0"`(其它值会被 `Validate` 拒绝)。`"true"`/`"1"` 写入 `X-Lms-Send-Separately: 1` 头;`"false"`/`"0"` 删除该头。 | ||
|
|
There was a problem hiding this comment.
Document accepted values as case-insensitive to match behavior.
The current wording says only "true"/"false"/"1"/"0" are accepted, but implementation/tests accept mixed-case boolean strings too. Please clarify to avoid user confusion.
Doc wording tweak
- ...;`value` 只支持 `"true"` / `"false"` / `"1"` / `"0"`(其它值会被 `Validate` 拒绝)。`"true"`/`"1"` 写入 `X-Lms-Send-Separately: 1` 头;`"false"`/`"0"` 删除该头。
+ ...;`value` 支持 `"true"` / `"false"` / `"1"` / `"0"`(`true/false` 大小写不敏感,其它值会被 `Validate` 拒绝)。`"true"`/`"1"` 写入 `X-Lms-Send-Separately: 1` 头;`"false"`/`"0"` 删除该头。📝 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.
| 将草稿标记为「分别发送」:发送时每个 To/Cc 收件人只看见自己(其他 To/Cc 在该副本里被剥离),看不到彼此存在。底层等价于 `set_header` / `remove_header` 操作 `X-Lms-Send-Separately`,但语义更明确,避免书写错头名 / 错值;`value` 只支持 `"true"` / `"false"` / `"1"` / `"0"`(其它值会被 `Validate` 拒绝)。`"true"`/`"1"` 写入 `X-Lms-Send-Separately: 1` 头;`"false"`/`"0"` 删除该头。 | |
| 将草稿标记为「分别发送」:发送时每个 To/Cc 收件人只看见自己(其他 To/Cc 在该副本里被剥离),看不到彼此存在。底层等价于 `set_header` / `remove_header` 操作 `X-Lms-Send-Separately`,但语义更明确,避免书写错头名 / 错值;`value` 支持 `"true"` / `"false"` / `"1"` / `"0"`(`true/false` 大小写不敏感,其它值会被 `Validate` 拒绝)。`"true"`/`"1"` 写入 `X-Lms-Send-Separately: 1` 头;`"false"`/`"0"` 删除该头。 |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@skills/lark-mail/references/lark-mail-draft-edit.md` around lines 187 - 188,
Doc currently lists accepted values for the draft "send separately" option as
"true"/"false"/"1"/"0" but the implementation and tests accept mixed-case
boolean strings; update the description for the `value` of
`X-Lms-Send-Separately` to state values are case-insensitive and that any case
variation of "true"/"1" will write `X-Lms-Send-Separately: 1` while any case
variation of "false"/"0" will remove the header, and that other values will be
rejected by the `Validate` logic; reference the header name
`X-Lms-Send-Separately` and the `Validate` behavior in the text so readers know
the mapping and rejection rules.
Generated by an automation pipeline.
Sprints
This MR was created autonomously. Quality gates were enforced by the repo's own pre-commit hooks.
Summary by CodeRabbit
Release Notes
New Features
--send-separatelyflag to+draft-createand+sendmail shortcuts to enable per-recipient message delivery.set_send_separatelypatch operation to+draft-editfor modifying send-separately behavior on existing drafts.Documentation
Tests