feat(gmail): add drafts reply, reply-all, and forward - #980
Conversation
Split the message-build half of gmail reply/reply-all/forward into pure builders (buildReplyComposeMessage/buildForwardComposeMessage) that take an already-acquired *gmail.Service, plus service-free resolveReplyInputs/resolveForwardInputs that resolve body/note exactly once and validate. The Run methods now orchestrate resolve -> dry-run -> acquire -> build -> finalize, so the acquire gate and finalize step are no longer baked into the builders. Extract composeSignatureOptions (the shared signature flags + signatureRequested/validateSignatureOptions/resolveComposeSignature methods) and embed it into GmailSendCmd and GmailReplyOptions, removing the throwaway GmailSendCmd the reply path constructed just to borrow those methods. Extract a GmailForwardOptions embed from GmailForwardCmd. No behavior change: CLI flags are byte-identical, and validation order, dry-run output, error wrapping, and finalize are unchanged; the existing reply/reply-all/forward/send/signature tests pass with assertions unchanged. This sets up reuse by upcoming drafts reply/reply-all/forward commands, which will share resolve+build and finalize with Drafts.Create under the non-send service gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `gmail drafts reply`, `drafts reply-all`, and `drafts forward`, giving the drafts surface full flag/ergonomic parity with the send-side reply/reply-all/forward commands. They embed the same options structs and reuse the shared resolve/build helpers, differing only at finalize: they save a draft (Drafts.Create) instead of sending. Drafts use the non-send service gate, so they work under --gmail-no-send, matching gmail drafts create. `drafts forward` allows an addressless draft (no --to), matching drafts create and Gmail's UI. The recipient requirement is resolved explicitly per call, which also aligns gmail forward's required---to handling with send/reply (runtime check + explanatory help) while keeping the MIME missing-To backstop on the send path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gmail send, forward, and drafts create/update parsed --to/--cc/--bcc with naive comma-splitting, while reply already parsed them as addresses. The splitting mangled quoted display names ("Smith, John" <john@example.com>) wherever the fragment list was consumed directly: --track counted such a recipient as two and refused to send, --track-split built one message per fragment, and dry-runs reported the broken fragments. Unparseable input was not rejected: send transmitted messages with garbage or empty To headers, and RFC 5322 group syntax ("undisclosed-recipients:;", which parses to zero addresses without an error) was silently dropped or silently saved.
Route every compose command through shared address-aware parsing (parseComposeRecipients -> mail.ParseAddressList), with reply's within-flag case-insensitive dedup applying everywhere. Recipients are parsed service-free before the dry-run, so the dry-run reports the same lists the built message carries; malformed input and non-empty flags that parse to zero recipients now fail fast with a flag-named error and no API call, on every compose command. A draft update without --to keeps the existing draft's To header leniently (verbatim if it does not parse), so legacy drafts stay editable. Nameless addresses whose bare form is not valid on the wire (quoted local parts) are re-quoted instead of silently emitted broken. The tracking pixel identity is now always the bare email address, independent of how the recipient was typed. Includes final-review test and comment polish across the compose paths.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: needs maintainer review before merge. Reviewed August 10, 2026, 11:16 PM ET / August 11, 2026, 03:16 UTC. ClawSweeper reviewWhat this changesAdds Gmail draft-only reply, reply-all, and forward commands, refactors shared compose helpers, and parses compose recipients as RFC-aware mailbox lists. Merge readinessKeep open: this active collaborator-owned PR adds a capability absent from current main, with provider-backed draft creation proof and no discrete correctness finding in the reviewed patch. Priority: P2 Review scores
Verification
How this fits togetherGmail CLI compose commands turn user inputs into MIME messages, then either send them or save them as Gmail drafts. Safety profiles and no-send guards decide which operations may use the Gmail service. flowchart LR
A[CLI compose command] --> B[Validate body and recipients]
B --> C[Shared message builder]
C --> D{Operation type}
D --> E[Send message]
D --> F[Save Gmail draft]
G[Safety profile and no-send guard] --> D
Decision needed
Why: Collaborator-authored PRs are not eligible for automated cleanup, and the final landing choice remains maintainer authority despite strong implementation and proof evidence. Before merge
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Merge the draft-only compose paths while retaining the existing no-send and safety-profile boundaries and the new regression coverage for shared recipient handling. Do we have a high-confidence way to reproduce the issue? Not applicable: this PR adds a new capability, and its body includes a provider-backed after-fix validation run for the new Gmail draft flows. Is this the best way to solve the issue? Yes: reusing the existing compose builders and changing only finalization to Gmail Drafts.Create is the narrowest maintainable implementation path. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 6af89c8dd7cb. LabelsLabel changes:
Label justifications:
EvidenceWhat I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
|
Lands #977 with @malob's three commits preserved as authored, plus a
maintainer changelog commit.
Review verdict from the dedicated lane: the design is sound — shared compose
builders, address-aware recipient parsing across all commands, and draft-only
semantics that reuse the send-side flags. Verified in review: dry-runs emit the
correct
gmail.drafts.*operations,--gmail-no-sendpermits drafts whilestill blocking
gmail reply, the agent-safe binary permits draft compositionwhile readonly rejects it, and full CI/autoreview pass.
Provider-backed proof was completed against the approved test mailbox with the
branch binary (draft created, verified, deleted; seed message trashed):
One rail proved itself unprompted: replying to a self-addressed message
correctly fails closed with "reply has no recipients after applying recipient
changes" — self-exclusion works and the validator refuses an empty recipient
list rather than guessing.
Closes #977. Thanks @malob.