Merge branch 'ag/send-email-outlook' into jch

Update send-email to work better with Outlook's smtp server.

* ag/send-email-outlook:
  send-email: add --[no-]outlook-id-fix option
  send-email: retrieve Message-ID from outlook SMTP server
Junio C Hamano 2025-05-01 14:07:15 -07:00
commit 348b6983f1
2 changed files with 45 additions and 1 deletions

View File

@ -115,6 +115,19 @@ illustration below where `[PATCH v2 0/3]` is in reply to `[PATCH 0/2]`:
Only necessary if --compose is also set. If --compose Only necessary if --compose is also set. If --compose
is not set, this will be prompted for. is not set, this will be prompted for.


--[no-]outlook-id-fix::
Microsoft Outlook SMTP servers discard the Message-ID sent via email and
assign a new random Message-ID, thus breaking threads.
+
With `--outlook-id-fix`, 'git send-email' uses a mechanism specific to
Outlook servers to learn the Message-ID the server assigned to fix the
threading. Use it only when you know that the server reports the
rewritten Message-ID the same way as Outlook servers do.
+
Without this option specified, the fix is done by default when talking
to 'smtp.office365.com' or 'smtp-mail.outlook.com'. Use
`--no-outlook-id-fix` to disable even when talking to these two servers.

--subject=<string>:: --subject=<string>::
Specify the initial subject of the email thread. Specify the initial subject of the email thread.
Only necessary if --compose is also set. If --compose Only necessary if --compose is also set. If --compose

View File

@ -41,6 +41,8 @@ git send-email --translate-aliases
--subject <str> * Email "Subject:" --subject <str> * Email "Subject:"
--reply-to <str> * Email "Reply-To:" --reply-to <str> * Email "Reply-To:"
--in-reply-to <str> * Email "In-Reply-To:" --in-reply-to <str> * Email "In-Reply-To:"
--[no-]outlook-id-fix * The SMTP host is an Outlook server that munges the
Message-ID. Retrieve it from the server.
--[no-]xmailer * Add "X-Mailer:" header (default). --[no-]xmailer * Add "X-Mailer:" header (default).
--[no-]annotate * Review each patch that will be sent in an editor. --[no-]annotate * Review each patch that will be sent in an editor.
--compose * Open an editor for introduction. --compose * Open an editor for introduction.
@ -68,7 +70,7 @@ git send-email --translate-aliases
--smtp-auth <str> * Space-separated list of allowed AUTH mechanisms, or --smtp-auth <str> * Space-separated list of allowed AUTH mechanisms, or
"none" to disable authentication. "none" to disable authentication.
This setting forces to use one of the listed mechanisms. This setting forces to use one of the listed mechanisms.
--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.


@ -290,6 +292,7 @@ my $validate = 1;
my $mailmap = 0; 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 %config_bool_settings = ( my %config_bool_settings = (
"thread" => \$thread, "thread" => \$thread,
@ -305,6 +308,7 @@ my %config_bool_settings = (
"xmailer" => \$use_xmailer, "xmailer" => \$use_xmailer,
"forbidsendmailvariables" => \$forbid_sendmail_variables, "forbidsendmailvariables" => \$forbid_sendmail_variables,
"mailmap" => \$mailmap, "mailmap" => \$mailmap,
"outlookidfix" => \$outlook_id_fix,
); );


my %config_settings = ( my %config_settings = (
@ -551,6 +555,7 @@ my %options = (
"relogin-delay=i" => \$relogin_delay, "relogin-delay=i" => \$relogin_delay,
"git-completion-helper" => \$git_completion_helper, "git-completion-helper" => \$git_completion_helper,
"v=s" => \$reroll_count, "v=s" => \$reroll_count,
"outlook-id-fix!" => \$outlook_id_fix,
); );
$rc = GetOptions(%options); $rc = GetOptions(%options);


@ -1574,6 +1579,16 @@ Message-ID: $message_id
return ($recipients_ref, $to, $date, $gitversion, $cc, $ccline, $header); return ($recipients_ref, $to, $date, $gitversion, $cc, $ccline, $header);
} }


sub is_outlook {
my ($host) = @_;
if ($outlook_id_fix eq 'auto') {
$outlook_id_fix =
($host eq 'smtp.office365.com' ||
$host eq 'smtp-mail.outlook.com') ? 1 : 0;
}
return $outlook_id_fix;
}

# Prepares the email, then asks the user what to do. # Prepares the email, then asks the user what to do.
# #
# If the user chooses to send the email, it's sent and 1 is returned. # If the user chooses to send the email, it's sent and 1 is returned.
@ -1737,6 +1752,22 @@ EOF
$smtp->datasend("$line") or die $smtp->message; $smtp->datasend("$line") or die $smtp->message;
} }
$smtp->dataend() or die $smtp->message; $smtp->dataend() or die $smtp->message;

# Outlook discards the Message-ID header we set while sending the email
# and generates a new random Message-ID. So in order to avoid breaking
# threads, we simply retrieve the Message-ID from the server response
# and assign it to the $message_id variable, which will then be
# assigned to $in_reply_to by the caller when the next message is sent
# as a response to this message.
if (is_outlook($smtp_server)) {
if ($smtp->message =~ /<([^>]+)>/) {
$message_id = "<$1>";
printf __("Outlook reassigned Message-ID to: %s\n"), $message_id;
} else {
warn __("Warning: Could not retrieve Message-ID from server response.\n");
}
}

$smtp->code =~ /250|200/ or die sprintf(__("Failed to send %s\n"), $subject).$smtp->message; $smtp->code =~ /250|200/ or die sprintf(__("Failed to send %s\n"), $subject).$smtp->message;
} }
if ($quiet) { if ($quiet) {