Skip to content

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

Open
qiooo wants to merge 2 commits intolarksuite:mainfrom
qiooo:feat/mail-send-preview
Open

feat(mail): add draft preview URL to draft operations#438
qiooo wants to merge 2 commits intolarksuite:mainfrom
qiooo:feat/mail-send-preview

Conversation

@qiooo
Copy link
Copy Markdown

@qiooo qiooo commented Apr 13, 2026

  • 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)

Summary

Changes

  • Change 1
  • Change 2

Test Plan

  • Unit tests pass
  • Manual local verification confirms the lark xxx command works as expected

Related Issues

  • None

Summary by CodeRabbit

  • New Features

    • Mail draft commands (create, edit, forward, reply, reply-all) now include a shareable draft preview URL in human-readable output when available; draft IDs shown in tips are terminal-sanitized and preview_url is printed only when non-empty.
  • Tests

    • Added unit tests covering preview URL generation, brand-specific origins, HTTPS/host/path/query structure, special-character draft IDs, nil/empty inputs, and map-preservation/nil-safety.

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

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@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

📝 Walkthrough

Walkthrough

Adds unexported helpers to generate brand-aware HTTPS draft preview URLs (Feishu default) and integrates them into mail shortcuts so create/edit/reply/forward flows populate and optionally display a preview_url. New unit tests validate brand logic, encoding, nil/empty handling, and map mutation behavior.

Changes

Cohort / File(s) Summary
Testing
shortcuts/mail/draft_preview_url_test.go
New unit tests covering draft preview URL helpers: brand-to-origin mapping (Lark vs Feishu), HTTPS/host/path checks, query params (draftId, scene=send-preview), special-character round-trips, runtime brand selection, nil/empty draftID behavior, and addDraftPreviewURL map semantics.
Core Helpers
shortcuts/mail/helpers.go
Added unexported helpers: draftPreviewURL, draftPreviewURLForBrand, draftPreviewOriginForBrand, and addDraftPreviewURL to build brand-aware, URL-escaped preview URLs (defaulting to Feishu) and conditionally set out["preview_url"].
Draft Create/Edit
shortcuts/mail/mail_draft_create.go, shortcuts/mail/mail_draft_edit.go
Call addDraftPreviewURL(runtime, out, draftID) after create/update; human-readable output now conditionally prints preview_url when present.
Reply/Forward/Send
shortcuts/mail/mail_forward.go, shortcuts/mail/mail_reply.go, shortcuts/mail/mail_reply_all.go, shortcuts/mail/mail_send.go
Draft-save branches refactored to build out maps with sanitized IDs, call addDraftPreviewURL, and emit formatted multi-line output via runtime.OutFormat, including an optional preview_url line when available. Minor I/O/import adjustments where needed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • chanthuang

Poem

🐰 I hopped through brands, both Feishu and Lark,
Escaped each draft id, left no paw-mark,
A little preview, shiny and bright,
Popped in the output, a comforting sight,
Hop, click, preview — then off into night ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description includes a summary of changes and mentions unit tests, but the template sections contain unfilled placeholders like 'Change 1' and 'Change 2' without substantive detail. Replace placeholder 'Change 1' and 'Change 2' entries with specific details about the new helpers and integration changes, providing clearer context for reviewers.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding draft preview URLs to mail draft operations, which aligns with all file modifications.

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

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

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

@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

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

- draftPreviewOriginForBrand now calls ResolveOpenBaseURL and replaces
  "open." with "www." instead of hardcoding feishu/lark URLs
- sanitize tip strings with sanitizeForTerminal before printing (fixes
  terminal injection risk in mailboxID/draftID values)
- add missing hintSendDraft stderr output to reply/reply-all/forward

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@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

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/M Single-domain feat or fix with limited business impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants