From 04133f5bc4f3dc7c847f4ba50e02486bcc117d94 Mon Sep 17 00:00:00 2001 From: Aditya Garg Date: Tue, 12 Aug 2025 06:44:35 +0000 Subject: [PATCH 1/2] send-email: add ability to send a copy of sent emails to an IMAP folder Some email providers like Apple iCloud Mail do not support sending a copy of sent emails to the "Sent" folder if SMTP server is used. As a workaround, various email clients like Thunderbird which rely on SMTP, use IMAP to send a copy of sent emails to the "Sent" folder. Something similar can be done if sending emails via `git send-email`, by using the `git imap-send` command to send a copy of the sent email to an IMAP folder specified by the user. Add this functionality to `git send-email` by introducing a new configuration variable `sendemail.imapfolder` and command line option `--imap-folder` which specifies the IMAP folder to send a copy of the sent emails to. If specified, a copy of the sent emails will be sent by piping the emails to `git imap-send` command, after all emails are sent via SMTP and the SMTP server has been closed. Signed-off-by: Aditya Garg Signed-off-by: Junio C Hamano --- Documentation/config/sendemail.adoc | 1 + Documentation/git-send-email.adoc | 12 +++++++++++ git-send-email.perl | 31 ++++++++++++++++++++++++++++- imap-send.c | 26 ++++++++++++++++-------- 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/Documentation/config/sendemail.adoc b/Documentation/config/sendemail.adoc index 4722334657..dd2dbc87a0 100644 --- a/Documentation/config/sendemail.adoc +++ b/Documentation/config/sendemail.adoc @@ -88,6 +88,7 @@ sendemail.smtpServer:: sendemail.smtpServerPort:: sendemail.smtpServerOption:: sendemail.smtpUser:: +sendemail.imapSentFolder:: sendemail.thread:: sendemail.transferEncoding:: sendemail.validate:: diff --git a/Documentation/git-send-email.adoc b/Documentation/git-send-email.adoc index 5335502d68..d1c41a0dbd 100644 --- a/Documentation/git-send-email.adoc +++ b/Documentation/git-send-email.adoc @@ -299,6 +299,18 @@ must be used for each option. commands and replies will be printed. Useful to debug TLS connection and authentication problems. +--imap-sent-folder=:: + Some email providers (e.g. iCloud) do not send a copy of the emails sent + using SMTP to the `Sent` folder or similar in your mailbox. Use this option + to use `git imap-send` to send a copy of the emails to the folder specified + using this option. You can run `git imap-send --list` to get a list of + valid folder names, including the correct name of the `Sent` folder in + your mailbox. You can also use this option to send emails to a dedicated + IMAP folder of your choice. ++ +This feature requires setting up `git imap-send`. See linkgit:git-imap-send[1] +for instructions. + --batch-size=:: Some email servers (e.g. 'smtp.163.com') limit the number of emails to be sent per session (connection) and this will lead to a failure when diff --git a/git-send-email.perl b/git-send-email.perl index 437f8ac46a..b3cc237baa 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -73,6 +73,8 @@ git send-email --translate-aliases --no-smtp-auth * Disable SMTP authentication. Shorthand for `--smtp-auth=none` --smtp-debug <0|1> * Disable, enable Net::SMTP debug. + --imap-sent-folder * IMAP folder where a copy of the emails should be sent. + Make sure `git imap-send` is set up to use this feature. --batch-size * send max message per connection. --relogin-delay * delay seconds between two successive login. @@ -200,7 +202,7 @@ my $re_encoded_word = qr/=\?($re_token)\?($re_token)\?($re_encoded_text)\?=/; # Variables we fill in automatically, or via prompting: my (@to,@cc,@xh,$envelope_sender, - $initial_in_reply_to,$reply_to,$initial_subject,@files, + $initial_in_reply_to,$reply_to,$initial_subject,@files,@imap_copy, $author,$sender,$smtp_authpass,$annotate,$compose,$time); # Things we either get from config, *or* are overridden on the # command-line. @@ -277,6 +279,7 @@ my ($smtp_server, $smtp_server_port, @smtp_server_options); my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path); my ($batch_size, $relogin_delay); my ($identity, $aliasfiletype, @alias_files, $smtp_domain, $smtp_auth); +my ($imap_sent_folder); my ($confirm); my (@suppress_cc); my ($auto_8bit_encoding); @@ -322,6 +325,7 @@ my %config_settings = ( "smtpauth" => \$smtp_auth, "smtpbatchsize" => \$batch_size, "smtprelogindelay" => \$relogin_delay, + "imapsentfolder" => \$imap_sent_folder, "to" => \@config_to, "tocmd" => \$to_cmd, "cc" => \@config_cc, @@ -527,6 +531,7 @@ my %options = ( "smtp-domain:s" => \$smtp_domain, "smtp-auth=s" => \$smtp_auth, "no-smtp-auth" => sub {$smtp_auth = 'none'}, + "imap-sent-folder=s" => \$imap_sent_folder, "annotate!" => \$annotate, "compose" => \$compose, "quiet" => \$quiet, @@ -1829,6 +1834,17 @@ EOF print "\n"; } + if ($imap_sent_folder && !$dry_run) { + my $imap_header = $header; + if (@initial_bcc) { + # Bcc is not a part of $header, so we add it here. + # This is only for the IMAP copy, not for the actual email + # sent to the recipients. + $imap_header .= "Bcc: " . join(", ", @initial_bcc) . "\n"; + } + push @imap_copy, "From git-send-email\n$imap_header\n$message"; + } + return 1; } @@ -2223,6 +2239,19 @@ sub cleanup_compose_files { $smtp->quit if $smtp; +if ($imap_sent_folder && @imap_copy && !$dry_run) { + my $imap_input = join("\n", @imap_copy); + eval { + print "\nStarting git imap-send...\n"; + my ($fh, $ctx) = Git::command_input_pipe(['imap-send', '-f', $imap_sent_folder]); + print $fh $imap_input; + Git::command_close_pipe($fh, $ctx); + 1; + } or do { + warn "Warning: failed to send messages to IMAP folder $imap_sent_folder: $@"; + }; +} + sub apply_transfer_encoding { my $message = shift; my $from = shift; diff --git a/imap-send.c b/imap-send.c index f5a656ac71..44de0c5a77 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1441,14 +1441,24 @@ static int count_messages(struct strbuf *all_msgs) while (1) { if (starts_with(p, "From ")) { - p = strstr(p+5, "\nFrom: "); - if (!p) break; - p = strstr(p+7, "\nDate: "); - if (!p) break; - p = strstr(p+7, "\nSubject: "); - if (!p) break; - p += 10; - count++; + if (starts_with(p, "From git-send-email")) { + p = strstr(p+5, "\nFrom: "); + if (!p) break; + p += 7; + p = strstr(p, "\nTo: "); + if (!p) break; + p += 5; + count++; + } else { + p = strstr(p+5, "\nFrom: "); + if (!p) break; + p = strstr(p+7, "\nDate: "); + if (!p) break; + p = strstr(p+7, "\nSubject: "); + if (!p) break; + p += 10; + count++; + } } p = strstr(p+5, "\nFrom "); if (!p) From f33b2207da792b45354e9af8948745a169f75651 Mon Sep 17 00:00:00 2001 From: Aditya Garg Date: Tue, 12 Aug 2025 06:44:36 +0000 Subject: [PATCH 2/2] send-email: enable copying emails to an IMAP folder without actually sending them `git imap-send` was built on the idea of copying emails to an IMAP folder like drafts, and sending them later using an email client. Currently the only way to do it is by piping output of `git format-patch` to IMAP send. Add another way to do it by using `git send-email` with the `--use-imap-only` or `sendmail.useImapOnly` option. This allows users to use the advanced features of `git send-email` like tweaking Cc: list programmatically, compose the cover letter, etc. and then send the well formatted emails to an IMAP folder using `git imap-send`. While at it, use `` instead of '' for --smtp-encryption ssl in help section of `git send-email`. Signed-off-by: Aditya Garg Signed-off-by: Junio C Hamano --- Documentation/config/sendemail.adoc | 1 + Documentation/git-send-email.adoc | 14 ++++++++++++++ git-send-email.perl | 9 ++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Documentation/config/sendemail.adoc b/Documentation/config/sendemail.adoc index dd2dbc87a0..90164c734d 100644 --- a/Documentation/config/sendemail.adoc +++ b/Documentation/config/sendemail.adoc @@ -89,6 +89,7 @@ sendemail.smtpServerPort:: sendemail.smtpServerOption:: sendemail.smtpUser:: sendemail.imapSentFolder:: +sendemail.useImapOnly:: sendemail.thread:: sendemail.transferEncoding:: sendemail.validate:: diff --git a/Documentation/git-send-email.adoc b/Documentation/git-send-email.adoc index d1c41a0dbd..a385f865fb 100644 --- a/Documentation/git-send-email.adoc +++ b/Documentation/git-send-email.adoc @@ -311,6 +311,20 @@ must be used for each option. This feature requires setting up `git imap-send`. See linkgit:git-imap-send[1] for instructions. +--use-imap-only:: +--no-use-imap-only:: + If this is set, all emails will only be copied to the IMAP folder specified + with `--imap-sent-folder` or `sendemail.imapSentFolder` and will not be sent + to the recipients. Useful if you just want to create a draft of the emails + and use another email client to send them. + If disabled with `--no-use-imap-only`, the emails will be sent like usual. + Disabled by default, but the `sendemail.useImapOnly` configuration + variable can be used to enable it. + ++ +This feature requires setting up `git imap-send`. See linkgit:git-imap-send[1] +for instructions. + --batch-size=:: Some email servers (e.g. 'smtp.163.com') limit the number of emails to be sent per session (connection) and this will lead to a failure when diff --git a/git-send-email.perl b/git-send-email.perl index b3cc237baa..96504e7be1 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -62,7 +62,7 @@ git send-email --translate-aliases --smtp-user * Username for SMTP-AUTH. --smtp-pass * Password for SMTP-AUTH; not necessary. --smtp-encryption * tls or ssl; anything else disables. - --smtp-ssl * Deprecated. Use '--smtp-encryption ssl'. + --smtp-ssl * Deprecated. Use `--smtp-encryption ssl`. --smtp-ssl-cert-path * Path to ca-certificates (either directory or file). Pass an empty string to disable certificate verification. @@ -75,6 +75,8 @@ git send-email --translate-aliases --smtp-debug <0|1> * Disable, enable Net::SMTP debug. --imap-sent-folder * IMAP folder where a copy of the emails should be sent. Make sure `git imap-send` is set up to use this feature. + --[no-]use-imap-only * Only copy emails to the IMAP folder specified by + `--imap-sent-folder` instead of actually sending them. --batch-size * send max message per connection. --relogin-delay * delay seconds between two successive login. @@ -296,6 +298,7 @@ my $mailmap = 0; my $target_xfer_encoding = 'auto'; my $forbid_sendmail_variables = 1; my $outlook_id_fix = 'auto'; +my $use_imap_only = 0; my %config_bool_settings = ( "thread" => \$thread, @@ -312,6 +315,7 @@ my %config_bool_settings = ( "forbidsendmailvariables" => \$forbid_sendmail_variables, "mailmap" => \$mailmap, "outlookidfix" => \$outlook_id_fix, + "useimaponly" => \$use_imap_only, ); my %config_settings = ( @@ -532,6 +536,7 @@ my %options = ( "smtp-auth=s" => \$smtp_auth, "no-smtp-auth" => sub {$smtp_auth = 'none'}, "imap-sent-folder=s" => \$imap_sent_folder, + "use-imap-only!" => \$use_imap_only, "annotate!" => \$annotate, "compose" => \$compose, "quiet" => \$quiet, @@ -1683,6 +1688,8 @@ EOF if ($dry_run) { # We don't want to send the email. + } elsif ($use_imap_only) { + die __("The destination IMAP folder is not properly defined.") if !defined $imap_sent_folder; } elsif (defined $sendmail_cmd || file_name_is_absolute($smtp_server)) { my $pid = open my $sm, '|-'; defined $pid or die $!;