feat(gmail): add --draft flag to +send, +reply, +reply-all, +forward#571
Draft
malob wants to merge 2 commits intogoogleworkspace:mainfrom
Draft
feat(gmail): add --draft flag to +send, +reply, +reply-all, +forward#571malob wants to merge 2 commits intogoogleworkspace:mainfrom
malob wants to merge 2 commits intogoogleworkspace:mainfrom
Conversation
Include original message attachments on +forward by default, matching Gmail web behavior. Add --no-original-attachments flag to opt out (skips file attachments but preserves inline images in HTML mode). Preserve cid: inline images in HTML mode for both +forward and +reply/+reply-all by building the correct multipart/related MIME structure via mail-builder's MimePart API. Gmail's API rewrites Content-Disposition: inline to attachment in multipart/mixed, so explicit multipart/related is required. In plain-text mode, inline images are not included for both forward and reply, matching Gmail web behavior. Key implementation details: - Single-pass MIME payload walker replaces separate text/html extractors - OriginalPart metadata type with lazy attachment data fetching - Part classification uses Content-Disposition to distinguish regular attachments from inline images (some clients set Content-ID on both) - Content-ID and content_type sanitized against CRLF header injection - Size preflight before downloading original attachments - Remote filename sanitization (not rejection) for sender-controlled names - Walker does not recurse into hydratable parts (e.g., message/rfc822)
When --draft is set, calls users.drafts.create instead of users.messages.send. Message construction is identical; only the API method and metadata wrapper change. Threaded drafts (replies and forwards) preserve threadId in the draft metadata.
🦋 Changeset detectedLatest commit: 93997d9 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds a
--draftflag to+send,+reply,+reply-all, and+forward. When set, callsusers.drafts.createinstead ofusers.messages.send. Message construction is identical — only the API method and metadata wrapper change.How it works
--draftadded tocommon_mail_args(one place, all four helpers get it)resolve_draft_methodnavigatesusers.drafts.createin the discovery docresolve_mail_methoddispatcher selects between send and draft based on the flagdispatch_raw_email(renamed fromsend_raw_emailto reflect its dual role) switches the method and wraps draft metadata in the{"message": {...}}envelope required by the Drafts APIthreadIdin the metadatausers.drafts.sendcommand (suppressed during--dry-run)Related issues and PRs
Feature request: Gmail draft-only mode for CLI+agent workflows #424 / PR feat(gmail): implement robust draft-only mode with centralized policy enforcement (#424) #499: Requests a draft-only policy that blocks all send paths for agent safety. The
--draftflag handles the draft creation side, so the policy work can focus purely on enforcement — restricting which operations are allowed — without needing to implement how drafts are created.Is there a way to allow the CLI to draft emails but not sending them? #286: The original "can I draft without sending?" request. The
--draftflag answers the drafting half; the "without sending" enforcement is Feature request: Gmail draft-only mode for CLI+agent workflows #424's territory.Feature request: +draft helper and CC/threading support for +send #555 (closed): Requested a
+drafthelper and CC/threading support for+send. CC/BCC and threading already exist via+reply/+reply-all/+forward; this PR adds the draft capability. Reporter closed the issue after learning about the existing features.PR fix(gmail): add HTML draft creation guidance to generated skill #568: Adds skill guidance for manually constructing RFC 2822 messages for
users.drafts.create. The--draftflag makes that workflow significantly simpler —+send --drafthandles all the RFC 5322 formatting, MIME encoding, and base64 automatically.Feature: Possibly more gfranular approach for email labeling/drafting? #508: Requests finer-grained Gmail OAuth scopes for label-only and draft-without-send access. Label management is already possible via the
gmail.labelsscope, but draft-without-send is not —gmail.composebundles draft creation and sending together. Client-side enforcement like the draft-only policy from Feature request: Gmail draft-only mode for CLI+agent workflows #424 is the available workaround.Live testing
Tested against a real Gmail account:
+send --draftDRAFTlabel+reply --draft+send --draft --dry-run+send(without--draft)Test coverage
782 total tests (6 new). New tests cover:
build_send_metadatafor all four(thread_id, draft)combinationsresolve_draft_methoddiscovery doc traversalthreadIdin draft metadata when no threadDry Run Output:
{ "body": "{\"message\":{}}", "dry_run": true, "is_multipart_upload": true, "method": "POST", "query_params": [], "url": "https://gmail.googleapis.com/upload/gmail/v1/users/me/drafts" }Checklist:
AGENTS.mdguidelines (no generatedgoogle-*crates).cargo fmt --allto format the code perfectly.cargo clippy -- -D warningsand resolved all warnings.pnpx changeset) to document my changes.