@@ -262,6 +262,7 @@ type GmailDraftsCreateCmd struct {
262262 BodyHTMLFile string `name:"body-html-file" help:"HTML body file path ('-' for stdin)"`
263263 ReplyToMessageID string `name:"reply-to-message-id" help:"Reply to Gmail message ID (sets In-Reply-To/References and thread)"`
264264 ThreadID string `name:"thread-id" help:"Reply within a Gmail thread (uses latest message for headers)"`
265+ ReplyAll bool `name:"reply-all" help:"Auto-populate recipients from original message (requires --reply-to-message-id or --thread-id)"`
265266 ReplyTo string `name:"reply-to" help:"Reply-To header address"`
266267 Quote bool `name:"quote" help:"Include quoted original message in reply (requires --reply-to-message-id or --thread-id)"`
267268 Attach []string `name:"attach" help:"Attachment file path (repeatable)"`
@@ -277,6 +278,7 @@ type draftComposeInput struct {
277278 BodyHTML string
278279 ReplyToMessageID string
279280 ReplyToThreadID string
281+ ReplyAll bool
280282 ReplyTo string
281283 Quote bool
282284 Attach []string
@@ -287,6 +289,11 @@ type draftComposeInput struct {
287289}
288290
289291func (c draftComposeInput ) validate () error {
292+ if c .ReplyAll &&
293+ strings .TrimSpace (c .ReplyToMessageID ) == "" &&
294+ strings .TrimSpace (c .ReplyToThreadID ) == "" {
295+ return usage ("--reply-all requires --reply-to-message-id or --thread-id" )
296+ }
290297 if strings .TrimSpace (c .Subject ) == "" &&
291298 strings .TrimSpace (c .ReplyToMessageID ) == "" &&
292299 strings .TrimSpace (c .ReplyToThreadID ) == "" {
@@ -299,7 +306,8 @@ func (c draftComposeInput) validate() error {
299306}
300307
301308func buildDraftMessage (ctx context.Context , svc * gmail.Service , account string , input draftComposeInput ) (* gmail.Message , string , []mailmime.AttachmentMetadata , error ) {
302- from , err := resolveComposeSender (ctx , svc , account , input .From )
309+ sendAs , sendAsErr := listSendAs (ctx , svc )
310+ from , err := resolveComposeFrom (ctx , svc , account , input .From , sendAs , sendAsErr )
303311 if err != nil {
304312 return nil , "" , nil , err
305313 }
@@ -321,6 +329,36 @@ func buildDraftMessage(ctx context.Context, svc *gmail.Service, account string,
321329 subject = autoReplySubject ("" , info .Subject )
322330 }
323331
332+ toRecipients := splitCSV (input .To )
333+ ccRecipients := splitCSV (input .Cc )
334+ bccRecipients := splitCSV (input .Bcc )
335+ if input .ReplyAll {
336+ recipients , recipientErr := buildReplyRecipients (
337+ info ,
338+ selfEmailsForReply (account , from .sendingEmail , sendAs ),
339+ true ,
340+ nil ,
341+ nil ,
342+ nil ,
343+ nil ,
344+ )
345+ if recipientErr != nil {
346+ return nil , "" , nil , recipientErr
347+ }
348+ toRecipients = formatMailboxes (recipients .To )
349+ ccRecipients = formatMailboxes (recipients .Cc )
350+ bccRecipients = formatMailboxes (recipients .Bcc )
351+ if strings .TrimSpace (input .To ) != "" {
352+ toRecipients = splitCSV (input .To )
353+ }
354+ if strings .TrimSpace (input .Cc ) != "" {
355+ ccRecipients = splitCSV (input .Cc )
356+ }
357+ if strings .TrimSpace (input .Bcc ) != "" {
358+ bccRecipients = splitCSV (input .Bcc )
359+ }
360+ }
361+
324362 msg , err := buildGmailMessage (ctx , sendMessageOptions {
325363 FromAddr : from .header ,
326364 ReplyTo : input .ReplyTo ,
@@ -330,9 +368,9 @@ func buildDraftMessage(ctx context.Context, svc *gmail.Service, account string,
330368 ReplyInfo : info ,
331369 Attachments : atts ,
332370 }, sendBatch {
333- To : splitCSV ( input . To ) ,
334- Cc : splitCSV ( input . Cc ) ,
335- Bcc : splitCSV ( input . Bcc ) ,
371+ To : toRecipients ,
372+ Cc : ccRecipients ,
373+ Bcc : bccRecipients ,
336374 }, true )
337375 if err != nil {
338376 return nil , "" , nil , err
@@ -557,6 +595,7 @@ func (c *GmailDraftsCreateCmd) Run(ctx context.Context, flags *RootFlags) error
557595 BodyHTML : htmlBody ,
558596 ReplyToMessageID : replyToMessageID ,
559597 ReplyToThreadID : threadID ,
598+ ReplyAll : c .ReplyAll ,
560599 ReplyTo : c .ReplyTo ,
561600 Quote : c .Quote ,
562601 Attach : attachPaths ,
@@ -578,6 +617,7 @@ func (c *GmailDraftsCreateCmd) Run(ctx context.Context, flags *RootFlags) error
578617 "body_html_len" : len (strings .TrimSpace (input .BodyHTML )),
579618 "reply_to_message_id" : strings .TrimSpace (input .ReplyToMessageID ),
580619 "thread_id" : strings .TrimSpace (input .ReplyToThreadID ),
620+ "reply_all" : input .ReplyAll ,
581621 "reply_to" : strings .TrimSpace (input .ReplyTo ),
582622 "quote" : input .Quote ,
583623 "from" : strings .TrimSpace (input .From ),
@@ -615,6 +655,7 @@ type GmailDraftsUpdateCmd struct {
615655 BodyHTMLFile string `name:"body-html-file" help:"HTML body file path ('-' for stdin)"`
616656 ReplyToMessageID string `name:"reply-to-message-id" help:"Reply to Gmail message ID (sets In-Reply-To/References and thread)"`
617657 ThreadID string `name:"thread-id" help:"Reply within a Gmail thread (uses latest message for headers); overrides the draft's existing thread"`
658+ ReplyAll bool `name:"reply-all" help:"Auto-populate recipients from original message (requires --reply-to-message-id or --thread-id)"`
618659 ReplyTo string `name:"reply-to" help:"Reply-To header address"`
619660 Quote bool `name:"quote" help:"Include quoted original message in reply"`
620661 Attach []string `name:"attach" help:"Attachment file path (repeatable). Replaces existing attachments; omit to preserve them, or use --clear-attachments to remove all."`
@@ -667,6 +708,7 @@ func (c *GmailDraftsUpdateCmd) Run(ctx context.Context, flags *RootFlags) error
667708 BodyHTML : htmlBody ,
668709 ReplyToMessageID : replyToMessageID ,
669710 ReplyToThreadID : threadID ,
711+ ReplyAll : c .ReplyAll ,
670712 ReplyTo : c .ReplyTo ,
671713 Quote : c .Quote ,
672714 Attach : attachPaths ,
@@ -681,14 +723,15 @@ func (c *GmailDraftsUpdateCmd) Run(ctx context.Context, flags *RootFlags) error
681723
682724 if dryRunErr := dryRunExit (ctx , flags , "gmail.drafts.update" , map [string ]any {
683725 "draft_id" : draftID ,
684- "to_keep_existing" : ! toWasSet ,
726+ "to_keep_existing" : ! toWasSet && ! input . ReplyAll ,
685727 "to" : splitCSV (input .To ),
686728 "cc" : splitCSV (input .Cc ),
687729 "bcc" : splitCSV (input .Bcc ),
688730 "subject" : strings .TrimSpace (input .Subject ),
689731 "body_len" : len (strings .TrimSpace (input .Body )),
690732 "body_html_len" : len (strings .TrimSpace (input .BodyHTML )),
691733 "reply_to_message_id" : strings .TrimSpace (input .ReplyToMessageID ),
734+ "reply_all" : input .ReplyAll ,
692735 "reply_to" : strings .TrimSpace (input .ReplyTo ),
693736 "quote" : input .Quote ,
694737 "from" : strings .TrimSpace (input .From ),
@@ -709,7 +752,7 @@ func (c *GmailDraftsUpdateCmd) Run(ctx context.Context, flags *RootFlags) error
709752 existingMessageID := ""
710753 existingTo := ""
711754 var existingPayload * gmail.MessagePart
712- if ! toWasSet || strings .TrimSpace (replyToMessageID ) == "" || preserveAttachments {
755+ if ( ! toWasSet && ! c . ReplyAll ) || strings .TrimSpace (replyToMessageID ) == "" || preserveAttachments {
713756 existing , fetchErr := svc .Users .Drafts .Get ("me" , draftID ).Format ("full" ).Do ()
714757 if fetchErr != nil {
715758 return fetchErr
@@ -718,12 +761,12 @@ func (c *GmailDraftsUpdateCmd) Run(ctx context.Context, flags *RootFlags) error
718761 existingThreadID = strings .TrimSpace (existing .Message .ThreadId )
719762 existingMessageID = strings .TrimSpace (existing .Message .Id )
720763 existingPayload = existing .Message .Payload
721- if ! toWasSet {
764+ if ! toWasSet && ! c . ReplyAll {
722765 existingTo = strings .TrimSpace (headerValue (existing .Message .Payload , "To" ))
723766 }
724767 }
725768 }
726- if ! toWasSet {
769+ if ! toWasSet && ! c . ReplyAll {
727770 to = existingTo
728771 }
729772
0 commit comments