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 <gargaditya08@live.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Aditya Garg committed Aug 12, 2025 at 06:44 UTC 04133f5bc4f3dc7c847f4ba50e02486bcc117d94
4 files changed +61 -9
Documentation/config/sendemail.adoc
+1
@@ -88,6 +88,7 @@ sendemail.smtpServer::
88 sendemail.smtpServerPort::
89 sendemail.smtpServerOption::
90 sendemail.smtpUser::
91 +sendemail.imapSentFolder::
92 sendemail.thread::
93 sendemail.transferEncoding::
94 sendemail.validate::
Documentation/git-send-email.adoc
+12
@@ -299,6 +299,18 @@ must be used for each option.
299 commands and replies will be printed. Useful to debug TLS
300 connection and authentication problems.
301
302 +--imap-sent-folder=<folder>::
303 + Some email providers (e.g. iCloud) do not send a copy of the emails sent
304 + using SMTP to the `Sent` folder or similar in your mailbox. Use this option
305 + to use `git imap-send` to send a copy of the emails to the folder specified
306 + using this option. You can run `git imap-send --list` to get a list of
307 + valid folder names, including the correct name of the `Sent` folder in
308 + your mailbox. You can also use this option to send emails to a dedicated
309 + IMAP folder of your choice.
310 ++
311 +This feature requires setting up `git imap-send`. See linkgit:git-imap-send[1]
312 +for instructions.
313 +
314 --batch-size=<num>::
315 Some email servers (e.g. 'smtp.163.com') limit the number of emails to be
316 sent per session (connection) and this will lead to a failure when
git-send-email.perl
+30 -1
@@ -73,6 +73,8 @@ git send-email --translate-aliases
73 --no-smtp-auth * Disable SMTP authentication. Shorthand for
74 `--smtp-auth=none`
75 --smtp-debug <0|1> * Disable, enable Net::SMTP debug.
76 + --imap-sent-folder <str> * IMAP folder where a copy of the emails should be sent.
77 + Make sure `git imap-send` is set up to use this feature.
78
79 --batch-size <int> * send max <int> message per connection.
80 --relogin-delay <int> * delay <int> seconds between two successive login.
@@ -200,7 +202,7 @@ my $re_encoded_word = qr/=\?($re_token)\?($re_token)\?($re_encoded_text)\?=/;
202
203 # Variables we fill in automatically, or via prompting:
204 my (@to,@cc,@xh,$envelope_sender,
203 - $initial_in_reply_to,$reply_to,$initial_subject,@files,
205 + $initial_in_reply_to,$reply_to,$initial_subject,@files,@imap_copy,
206 $author,$sender,$smtp_authpass,$annotate,$compose,$time);
207 # Things we either get from config, *or* are overridden on the
208 # command-line.
@@ -277,6 +279,7 @@ my ($smtp_server, $smtp_server_port, @smtp_server_options);
279 my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path);
280 my ($batch_size, $relogin_delay);
281 my ($identity, $aliasfiletype, @alias_files, $smtp_domain, $smtp_auth);
282 +my ($imap_sent_folder);
283 my ($confirm);
284 my (@suppress_cc);
285 my ($auto_8bit_encoding);
@@ -322,6 +325,7 @@ my %config_settings = (
325 "smtpauth" => \$smtp_auth,
326 "smtpbatchsize" => \$batch_size,
327 "smtprelogindelay" => \$relogin_delay,
328 + "imapsentfolder" => \$imap_sent_folder,
329 "to" => \@config_to,
330 "tocmd" => \$to_cmd,
331 "cc" => \@config_cc,
@@ -527,6 +531,7 @@ my %options = (
531 "smtp-domain:s" => \$smtp_domain,
532 "smtp-auth=s" => \$smtp_auth,
533 "no-smtp-auth" => sub {$smtp_auth = 'none'},
534 + "imap-sent-folder=s" => \$imap_sent_folder,
535 "annotate!" => \$annotate,
536 "compose" => \$compose,
537 "quiet" => \$quiet,
@@ -1829,6 +1834,17 @@ EOF
1834 print "\n";
1835 }
1836
1837 + if ($imap_sent_folder && !$dry_run) {
1838 + my $imap_header = $header;
1839 + if (@initial_bcc) {
1840 + # Bcc is not a part of $header, so we add it here.
1841 + # This is only for the IMAP copy, not for the actual email
1842 + # sent to the recipients.
1843 + $imap_header .= "Bcc: " . join(", ", @initial_bcc) . "\n";
1844 + }
1845 + push @imap_copy, "From git-send-email\n$imap_header\n$message";
1846 + }
1847 +
1848 return 1;
1849 }
1850
@@ -2223,6 +2239,19 @@ sub cleanup_compose_files {
2239
2240 $smtp->quit if $smtp;
2241
2242 +if ($imap_sent_folder && @imap_copy && !$dry_run) {
2243 + my $imap_input = join("\n", @imap_copy);
2244 + eval {
2245 + print "\nStarting git imap-send...\n";
2246 + my ($fh, $ctx) = Git::command_input_pipe(['imap-send', '-f', $imap_sent_folder]);
2247 + print $fh $imap_input;
2248 + Git::command_close_pipe($fh, $ctx);
2249 + 1;
2250 + } or do {
2251 + warn "Warning: failed to send messages to IMAP folder $imap_sent_folder: $@";
2252 + };
2253 +}
2254 +
2255 sub apply_transfer_encoding {
2256 my $message = shift;
2257 my $from = shift;
imap-send.c
+18 -8
@@ -1441,14 +1441,24 @@ static int count_messages(struct strbuf *all_msgs)
1441
1442 while (1) {
1443 if (starts_with(p, "From ")) {
1444 - p = strstr(p+5, "\nFrom: ");
1445 - if (!p) break;
1446 - p = strstr(p+7, "\nDate: ");
1447 - if (!p) break;
1448 - p = strstr(p+7, "\nSubject: ");
1449 - if (!p) break;
1450 - p += 10;
1451 - count++;
1444 + if (starts_with(p, "From git-send-email")) {
1445 + p = strstr(p+5, "\nFrom: ");
1446 + if (!p) break;
1447 + p += 7;
1448 + p = strstr(p, "\nTo: ");
1449 + if (!p) break;
1450 + p += 5;
1451 + count++;
1452 + } else {
1453 + p = strstr(p+5, "\nFrom: ");
1454 + if (!p) break;
1455 + p = strstr(p+7, "\nDate: ");
1456 + if (!p) break;
1457 + p = strstr(p+7, "\nSubject: ");
1458 + if (!p) break;
1459 + p += 10;
1460 + count++;
1461 + }
1462 }
1463 p = strstr(p+5, "\nFrom ");
1464 if (!p)