From: Aditya Garg <gargaditya08@live.com>
To: Junio C Hamano <gitster@pobox.com>,
"git@vger.kernel.org" <git@vger.kernel.org>
Cc: Eric Sunshine <sunshine@sunshineco.com>,
"sandals@crustytoothpaste.net" <sandals@crustytoothpaste.net>,
Julian Swagemakers <julian@swagemakers.org>,
Jeff King <peff@peff.net>, Zi Yao <ziyao@disroot.org>,
Rens Oliemans <hallo@rensoliemans.nl>,
Drew DeVault <drew@ddevault.org>
Subject: [PATCH RFC] send-mail: add support for Microsoft Graph API
Date: Sun, 25 May 2025 11:06:32 +0000 [thread overview]
Message-ID: <20250525110621.64308-1-gargaditya08@live.com> (raw)
Apart from SMTP, Microsoft also provides a REST API, branded as
Microsoft Graph for sending mails. Upon testing a bit, I have
found a few benefits over SMTP. Firstly, SMTP servers of Microsoft
are kinda slow. On an average, initialising the SMTP server even
on a fast internet connection takes around 8-10 seconds with
send-email. Once initialised, subsequent messages sometimes also
face delays, taking around 3-5 seconds per message, and other
times they are sent almost instantaneously. Secondly, their SMTP
server does not respect the Message-ID specified by the user and
replaces it with their own generated string.
Microsoft Graph API solves both these problems. It is extremely
fast, taking around 1 second to send a series of 5 patches, and
also respects the Message-ID specified by the user.
After this patch, users can use the graph API by having their
config as:
[sendemail]
useMSGraph = true
MSGraphUser = yourname@outlook.com
The API requires an OAuth2.0 access token for authentication, and
users can either generate them manually, or use a credential helper
TODO:
I still need to write the documentation for this feature, but I wanted to
get the code reviewed first.
PS: This patch is sent using MS Graph. Note the fixed Message-ID ;)
Signed-off-by: Aditya Garg <gargaditya08@live.com>
---
git-send-email.perl | 71 ++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 67 insertions(+), 4 deletions(-)
diff --git a/git-send-email.perl b/git-send-email.perl
index b09251c4fc..edb96c7e1a 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -73,7 +73,10 @@ sub usage {
--no-smtp-auth * Disable SMTP authentication. Shorthand for
`--smtp-auth=none`
--smtp-debug <0|1> * Disable, enable Net::SMTP debug.
-
+ --[no-]use-msgraph <str> * Use Microsoft Graph API instead of SMTP.
+ --msgraph-user <str> * Username in case an email API is used.
+ If not specified, the from address is used.
+ --msgraph-token <str> * OAuth2.0 access token for the email API.
--batch-size <int> * send max <int> message per connection.
--relogin-delay <int> * delay <int> seconds between two successive login.
This option can only be used with --batch-size
@@ -201,7 +204,7 @@ sub format_2822_time {
# Variables we fill in automatically, or via prompting:
my (@to,@cc,@xh,$envelope_sender,
$initial_in_reply_to,$reply_to,$initial_subject,@files,
- $author,$sender,$smtp_authpass,$annotate,$compose,$time);
+ $author,$sender,$smtp_authpass,$annotate,$compose,$time,$msgraph_token);
# Things we either get from config, *or* are overridden on the
# command-line.
my ($no_cc, $no_to, $no_bcc, $no_identity, $no_header_cmd);
@@ -283,6 +286,7 @@ sub do_edit {
my ($compose_encoding);
my ($sendmail_cmd);
my ($mailmap_file, $mailmap_blob);
+my ($msgraph_user);
# Variables with corresponding config settings & hardcoded defaults
my ($debug_net_smtp) = 0; # Net::SMTP, see send_message()
my $thread = 1;
@@ -293,6 +297,7 @@ sub do_edit {
my $target_xfer_encoding = 'auto';
my $forbid_sendmail_variables = 1;
my $outlook_id_fix = 'auto';
+my $use_msgraph = 0;
my %config_bool_settings = (
"thread" => \$thread,
@@ -309,6 +314,7 @@ sub do_edit {
"forbidsendmailvariables" => \$forbid_sendmail_variables,
"mailmap" => \$mailmap,
"outlookidfix" => \$outlook_id_fix,
+ "usemsgraph" => \$use_msgraph,
);
my %config_settings = (
@@ -337,6 +343,8 @@ sub do_edit {
"composeencoding" => \$compose_encoding,
"transferencoding" => \$target_xfer_encoding,
"sendmailcmd" => \$sendmail_cmd,
+ "msgraphuser" => \$msgraph_user,
+ "msgraphtoken" => \$msgraph_token,
);
my %config_path_settings = (
@@ -556,6 +564,9 @@ sub config_regexp {
"git-completion-helper" => \$git_completion_helper,
"v=s" => \$reroll_count,
"outlook-id-fix!" => \$outlook_id_fix,
+ "use-msgraph!" => \$use_msgraph,
+ "msgraph-user=s" => \$msgraph_user,
+ "msgraph-token:s" => \$msgraph_token,
);
$rc = GetOptions(%options);
@@ -1095,7 +1106,7 @@ sub expand_one_alias {
$reply_to = sanitize_address($reply_to);
}
-if (!defined $sendmail_cmd && !defined $smtp_server) {
+if (!defined $sendmail_cmd && !defined $smtp_server && !$use_msgraph) {
my @sendmail_paths = qw( /usr/sbin/sendmail /usr/lib/sendmail );
push @sendmail_paths, map {"$_/sendmail"} split /:/, $ENV{PATH};
foreach (@sendmail_paths) {
@@ -1603,6 +1614,7 @@ sub is_outlook {
sub send_message {
my ($recipients_ref, $to, $date, $gitversion, $cc, $ccline, $header) = gen_header();
my @recipients = @$recipients_ref;
+ my $msgraph_response_code;
my @sendmail_parameters = ('-i', @recipients);
my $raw_from = $sender;
@@ -1680,6 +1692,52 @@ sub send_message {
}
print $sm "$header\n$message";
close $sm or die $!;
+ } elsif ($use_msgraph) {
+ # Use Microsoft Graph API
+ # https://learn.microsoft.com/en-us/graph/api/user-sendmail
+ my $auth_api;
+ if (!defined $msgraph_user) {
+ $msgraph_user = (defined $smtp_authuser
+ ? $smtp_authuser
+ : $raw_from
+ );
+ }
+ require MIME::Base64;
+ require HTTP::Tiny;
+ $auth_api = Git::credential({
+ 'protocol' => 'https',
+ 'host' => 'graph.microsoft.com',
+ 'username' => $msgraph_user,
+ 'password' => $msgraph_token,
+ }, sub {
+ my $cred = shift;
+ $msgraph_token = $cred->{'password'};
+ });
+ my $email_url = "https://graph.microsoft.com/v1.0/users/$msgraph_user/sendMail";
+ my $real_message = "$header\n$message\n";
+ # Convert LF to CRLF
+ unless ($real_message =~ /\r\n/) {
+ $real_message =~ s/\r?\n/\r\n/g;
+ }
+ # The API requires the message to be base64 encoded if sending in MIME format.
+ my $encoded_message = MIME::Base64::encode_base64($real_message, "");
+ my $http = HTTP::Tiny->new(
+ default_headers => {
+ 'Authorization' => "Bearer $msgraph_token",
+ 'Content-Type' => 'text/plain',
+ },
+ );
+ my $response = $http->post($email_url, {
+ content => $encoded_message,
+ });
+
+
+ if (!$response->{success}) {
+ die sprintf(__("Failed to send email: %s\nResponse: %s"),
+ $response->{status}, $response->{content});
+ } else {
+ $msgraph_response_code = $response->{status};
+ }
} else {
if (!defined $smtp_server) {
@@ -1790,12 +1848,15 @@ sub send_message {
} else {
print($dry_run ? __("Dry-OK. Log says:") : __("OK. Log says:"));
print "\n";
- if (!defined $sendmail_cmd && !file_name_is_absolute($smtp_server)) {
+ if (!defined $sendmail_cmd && !file_name_is_absolute($smtp_server)
+ && !$use_msgraph) {
print "Server: $smtp_server\n";
print "MAIL FROM:<$raw_from>\n";
foreach my $entry (@recipients) {
print "RCPT TO:<$entry>\n";
}
+ } elsif ($use_msgraph) {
+ print "Email API: Microsoft Graph API\n";
} else {
my $sm;
if (defined $sendmail_cmd) {
@@ -1810,6 +1871,8 @@ sub send_message {
if ($smtp) {
print __("Result: "), $smtp->code, ' ',
($smtp->message =~ /\n([^\n]+\n)$/s);
+ } elsif (defined $msgraph_response_code) {
+ print __("Result: "), $msgraph_response_code;
} else {
print __("Result: OK");
}
--
2.49.0
next reply other threads:[~2025-05-25 11:06 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-05-25 11:06 Aditya Garg [this message]
2025-05-25 11:22 ` [PATCH RFC] send-mail: add support for Microsoft Graph API Aditya Garg
2025-05-25 20:24 ` brian m. carlson
2025-05-26 1:36 ` Drew DeVault
2025-05-26 3:38 ` Aditya Garg
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250525110621.64308-1-gargaditya08@live.com \
--to=gargaditya08@live.com \
--cc=drew@ddevault.org \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=hallo@rensoliemans.nl \
--cc=julian@swagemakers.org \
--cc=peff@peff.net \
--cc=sandals@crustytoothpaste.net \
--cc=sunshine@sunshineco.com \
--cc=ziyao@disroot.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox