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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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@xxxxxxxxxxx

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@xxxxxxxx>
---
 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







[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux