git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH RFC] send-mail: add support for Microsoft Graph API

From
Aditya Garg <gargaditya08@live.com>
Date
May 25, 2025, 11:06 UTC
Message-ID
<20250525110621.64308-1-gargaditya08@live.com>

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: Aditya Garg
Message 1 of 5 in “send-mail: add support for Microsoft Graph API”
  1. send-mail: add support for Microsoft Graph APIAditya Garg, May 25, 2025
  2. Aditya GargMay 25, 2025
  3. brian m. carlsonMay 25, 2025
  4. Drew DeVaultMay 26, 2025
  5. Aditya GargMay 26, 2025

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.