Merge branch 'ag/send-email-imap-sent'

"git send-email" learned to drive "git imap-send" to store already
sent e-mails in an IMAP folder.

* ag/send-email-imap-sent:
  send-email: enable copying emails to an IMAP folder without actually sending them
  send-email: add ability to send a copy of sent emails to an IMAP folder
main
Junio C Hamano 2025-09-18 10:07:00 -07:00
commit 1c385d1bf8
4 changed files with 84 additions and 10 deletions

View File

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

View File

@ -300,6 +300,32 @@ 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.

--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=<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

@ -62,7 +62,7 @@ git send-email --translate-aliases
--smtp-user <str> * Username for SMTP-AUTH. --smtp-user <str> * Username for SMTP-AUTH.
--smtp-pass <str> * Password for SMTP-AUTH; not necessary. --smtp-pass <str> * Password for SMTP-AUTH; not necessary.
--smtp-encryption <str> * tls or ssl; anything else disables. --smtp-encryption <str> * tls or ssl; anything else disables.
--smtp-ssl * Deprecated. Use '--smtp-encryption ssl'. --smtp-ssl * Deprecated. Use `--smtp-encryption ssl`.
--smtp-ssl-cert-path <str> * Path to ca-certificates (either directory or file). --smtp-ssl-cert-path <str> * Path to ca-certificates (either directory or file).
Pass an empty string to disable certificate Pass an empty string to disable certificate
verification. verification.
@ -73,6 +73,10 @@ 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.
--[no-]use-imap-only * Only copy emails to the IMAP folder specified by
`--imap-sent-folder` instead of actually sending them.


--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 +204,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 +281,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);
@ -293,6 +298,7 @@ my $mailmap = 0;
my $target_xfer_encoding = 'auto'; my $target_xfer_encoding = 'auto';
my $forbid_sendmail_variables = 1; my $forbid_sendmail_variables = 1;
my $outlook_id_fix = 'auto'; my $outlook_id_fix = 'auto';
my $use_imap_only = 0;


my %config_bool_settings = ( my %config_bool_settings = (
"thread" => \$thread, "thread" => \$thread,
@ -309,6 +315,7 @@ my %config_bool_settings = (
"forbidsendmailvariables" => \$forbid_sendmail_variables, "forbidsendmailvariables" => \$forbid_sendmail_variables,
"mailmap" => \$mailmap, "mailmap" => \$mailmap,
"outlookidfix" => \$outlook_id_fix, "outlookidfix" => \$outlook_id_fix,
"useimaponly" => \$use_imap_only,
); );


my %config_settings = ( my %config_settings = (
@ -322,6 +329,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 +535,8 @@ 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,
"use-imap-only!" => \$use_imap_only,
"annotate!" => \$annotate, "annotate!" => \$annotate,
"compose" => \$compose, "compose" => \$compose,
"quiet" => \$quiet, "quiet" => \$quiet,
@ -1678,6 +1688,8 @@ EOF


if ($dry_run) { if ($dry_run) {
# We don't want to send the email. # 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)) { } elsif (defined $sendmail_cmd || file_name_is_absolute($smtp_server)) {
my $pid = open my $sm, '|-'; my $pid = open my $sm, '|-';
defined $pid or die $!; defined $pid or die $!;
@ -1829,6 +1841,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 +2246,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

@ -1442,14 +1442,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)