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>
main
Aditya Garg 2025-08-12 06:44:35 +00:00 committed by Junio C Hamano
parent 3f2a94875d
commit 04133f5bc4
4 changed files with 61 additions and 9 deletions

View File

@ -88,6 +88,7 @@ sendemail.smtpServer::
sendemail.smtpServerPort:: sendemail.smtpServerPort::
sendemail.smtpServerOption:: sendemail.smtpServerOption::
sendemail.smtpUser:: sendemail.smtpUser::
sendemail.imapSentFolder::
sendemail.thread:: sendemail.thread::
sendemail.transferEncoding:: sendemail.transferEncoding::
sendemail.validate:: sendemail.validate::

View File

@ -299,6 +299,18 @@ must be used for each option.
commands and replies will be printed. Useful to debug TLS commands and replies will be printed. Useful to debug TLS
connection and authentication problems. connection and authentication problems.


--imap-sent-folder=<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=<num>:: --batch-size=<num>::
Some email servers (e.g. 'smtp.163.com') limit the number of emails to be 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 sent per session (connection) and this will lead to a failure when

View File

@ -73,6 +73,8 @@ git send-email --translate-aliases
--no-smtp-auth * Disable SMTP authentication. Shorthand for --no-smtp-auth * Disable SMTP authentication. Shorthand for
`--smtp-auth=none` `--smtp-auth=none`
--smtp-debug <0|1> * Disable, enable Net::SMTP debug. --smtp-debug <0|1> * Disable, enable Net::SMTP debug.
--imap-sent-folder <str> * 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 <int> * send max <int> message per connection. --batch-size <int> * send max <int> message per connection.
--relogin-delay <int> * delay <int> seconds between two successive login. --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)\?=/;


# Variables we fill in automatically, or via prompting: # Variables we fill in automatically, or via prompting:
my (@to,@cc,@xh,$envelope_sender, 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); $author,$sender,$smtp_authpass,$annotate,$compose,$time);
# Things we either get from config, *or* are overridden on the # Things we either get from config, *or* are overridden on the
# command-line. # 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 ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path);
my ($batch_size, $relogin_delay); my ($batch_size, $relogin_delay);
my ($identity, $aliasfiletype, @alias_files, $smtp_domain, $smtp_auth); my ($identity, $aliasfiletype, @alias_files, $smtp_domain, $smtp_auth);
my ($imap_sent_folder);
my ($confirm); my ($confirm);
my (@suppress_cc); my (@suppress_cc);
my ($auto_8bit_encoding); my ($auto_8bit_encoding);
@ -322,6 +325,7 @@ my %config_settings = (
"smtpauth" => \$smtp_auth, "smtpauth" => \$smtp_auth,
"smtpbatchsize" => \$batch_size, "smtpbatchsize" => \$batch_size,
"smtprelogindelay" => \$relogin_delay, "smtprelogindelay" => \$relogin_delay,
"imapsentfolder" => \$imap_sent_folder,
"to" => \@config_to, "to" => \@config_to,
"tocmd" => \$to_cmd, "tocmd" => \$to_cmd,
"cc" => \@config_cc, "cc" => \@config_cc,
@ -527,6 +531,7 @@ my %options = (
"smtp-domain:s" => \$smtp_domain, "smtp-domain:s" => \$smtp_domain,
"smtp-auth=s" => \$smtp_auth, "smtp-auth=s" => \$smtp_auth,
"no-smtp-auth" => sub {$smtp_auth = 'none'}, "no-smtp-auth" => sub {$smtp_auth = 'none'},
"imap-sent-folder=s" => \$imap_sent_folder,
"annotate!" => \$annotate, "annotate!" => \$annotate,
"compose" => \$compose, "compose" => \$compose,
"quiet" => \$quiet, "quiet" => \$quiet,
@ -1829,6 +1834,17 @@ EOF
print "\n"; 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; return 1;
} }


@ -2223,6 +2239,19 @@ sub cleanup_compose_files {


$smtp->quit if $smtp; $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 { sub apply_transfer_encoding {
my $message = shift; my $message = shift;
my $from = shift; my $from = shift;

View File

@ -1441,14 +1441,24 @@ static int count_messages(struct strbuf *all_msgs)


while (1) { while (1) {
if (starts_with(p, "From ")) { if (starts_with(p, "From ")) {
p = strstr(p+5, "\nFrom: "); if (starts_with(p, "From git-send-email")) {
if (!p) break; p = strstr(p+5, "\nFrom: ");
p = strstr(p+7, "\nDate: "); if (!p) break;
if (!p) break; p += 7;
p = strstr(p+7, "\nSubject: "); p = strstr(p, "\nTo: ");
if (!p) break; if (!p) break;
p += 10; p += 5;
count++; 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 "); p = strstr(p+5, "\nFrom ");
if (!p) if (!p)