Microsoft Graphに統合されているOutlookメールREST APIを使用して、電子メールを送信する方法を解説する。
Microsoftアカウントがあれば、outlook.comでメールの送受信ができる。それにはOutlookアプリやOutlookのWebサイトを利用する他に、RESTful Web API(REST API)である「Microsoft Graph」を使う方法がある。REST APIは、独自のアプリやWebサイトから利用できる。エンドユーザーがMicrosoftアカウントを持っていることが前提のデスクトップアプリや、組織のMicrosoftアカウントを使ってメールを送受信するシステムなどで、このRESTサービスは役に立つだろう。本稿では、Microsoft Graph APIを使ってメールを送信する方法を解説する。
なお、OutlookのWebサイトはoutlook.live.comであり、Microsoft Graph APIが提供されているサイトはgraph.microsoft.comである。実際にメール送信に使われるサーバのドメインがoutlook.comであるので、本稿では「outlook.comでメールの送受信」と表現している。
特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。Graph APIの基礎知識をお持ちの方は、前半を飛ばして「テキストメールを組み立てるには?」に進んでいただきたい。
なお、本稿に掲載したサンプルコードをそのまま試すにはVisual Studio 2017以降が必要である。サンプルコードはコンソールアプリの一部であり、コードの冒頭に以下の宣言が必要となる。また、C#のサンプルコードには、C# 7.1以降が必要である。
using Microsoft.Graph;
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using static System.Console;
Imports System.Console
Imports System.Net.Http
Imports System.Net.Http.Headers
Imports Microsoft.Graph
Imports Microsoft.Identity.Client
Microsoft Graph APIとは、Microsoftのクラウドサービスを利用するための共通APIだと考えると分かりやすい。幾つものサービスを利用するためのAPIが用意されている。その概略は、「Microsoft Graph の概要」をご覧いただきたい(次の画像)。ちなみに、Build 2015で発表されたときには、「Office Graph API」/「Office 365 Unified API」などと呼ばれていた(「特集:Build 2015:全ての開発者が押さえておくべきマイクロソフトの最新技術動向 (2/5)」のページの下方で紹介している)。
Microsoft Graphで提供されているさまざまなサービスの中から、Outlookの機能の、さらにその一部であるメール送信を本稿では利用してみようというわけだ。
Microsoft GraphはREST APIであるから、アプリで直接HTTPリクエストを組み立ててAPIを利用することもできる。例えばメール送信なら「メールを送信する - ドキュメント - Microsoft Graph」をご覧いただきたい(次の画像)。
このようなHTTPリクエストを組み立て、送信し、受け取った結果を解析するコードを書くのは面倒なものだ。クライアントアプリから使うためのライブラリ「Microsoft Graph .NET Client Library」(以降、「Graph Client Library」)がMicrosoftからリリースされているので、それを使おう。.NET Standard 1.1に対応しているので、.NET Frameworkだけでなく、UWPアプリやXamarinなどからも利用できる。
NuGetからVisual Studioのプロジェクトに導入する。Graph Client Libraryの他に、ユーザー認証のためのライブラリも必要だ。
まずGraph Client Libraryであるが、NuGetで「Microsoft.Graph」を検索すれば見つかる(次の画像)。
Graph APIを利用するためのユーザー認証には幾つかの方法がある(後述)。ここでは、Microsoft Authentication Library(MSAL)を使おう。まだプレビューのままになっているが、すでに十分に安定している。Visual StudioのNuGetパッケージ管理画面で[プレリリースを含める]にチェックを入れてから「Microsoft.Identity.Client」を検索する(次の画像)。
従来のデスクトップアプリなどの場合は、あらかじめGraph APIを利用するアプリをアプリケーション登録ポータル(Application Registration Portal)に登録し、アプリケーションID(クライアントID)を取得する(UWPアプリの場合は、ストアに登録することで自動的に登録される)。登録には、Microsoftアカウントが必要だ。
アプリケーション登録ポータルにサインインしたら、「集中型アプリケーション」(converged applications=「統合アプリ」)を追加して、アプリの名前などを登録する。登録手順の概要を次の画像に示すので、参考にしてもらいたい(このUIは頻繁に変更されるので、異なっている可能性がある)。今回の場合、登録内容で重要なのは、アプリの名前とアクセス許可だ。アプリの名前は、そのままユーザー認証のUIに表示される。アクセス許可が正しくないと、アプリからのAPI呼び出しが拒絶される。また、アプリケーションIDは、後ほどコーディングするときのクライアントIDとして使うので、書き留めておく。
アプリを登録する手順の概要
アクセス許可に2種類あるが、エンドユーザーにMicrosoftアカウントでサインインしてもらって(=エンドユーザーに権限を委任してもらって)アプリがアクセスする場合は「委任されたアクセス許可」を選ぶ。
ほとんどのGraph APIは、呼び出すときにアクセストークンが必要である。Azure ADで認証を受けると、アクセストークンを得られる。以下に示すように、この認証には2通りの方法がある。
エンドユーザーが認証を行う場合、そのアカウントが一般的なMicrosoftアカウントか、それとも、職場または学校アカウント(Azure ADのアカウント)なのかによって、以下のように利用できる認証方法が異なる。
今回はMicrosoftアカウントを使いたいので、Azure AD v2.0を選ぶ。そのREST APIをラップしたライブラリが、「ライブラリを導入するには?」でインストールしたMicrosoft Authentication Library(MSAL)である。
話が長くなったが、ここまでがGraph APIの基礎知識である。ようやくコーディングに取り掛かる準備ができた。
さて、MSALを使ってユーザー認証を受けるコンソールアプリは次のコードのようになる。一部、「*」文字で伏せ字にしているが、実際には適切な文字列を設定してほしい(以下同じ)。
class Program
{
// ClientIDには、アプリを登録したときのアプリケーションIDを設定する
const string ClientID = "********-****-****-****-************";
private static readonly string RedirectUri = $"msal{ClientID}://auth";
// ユーザー認証関係のプロパティ(後ほど、別のクラスからもアクセスする)
public static PublicClientApplication PCA { get; }
= new PublicClientApplication(ClientID) { RedirectUri = RedirectUri, };
public static IReadOnlyList<string> Scopes { get; }
= new List<string> { "User.Read", "Mail.Send", }.AsReadOnly();
// 「async Task Main」と書けるのはC# 7.1以降
static async Task Main(string[] args)
{
AuthenticationResult ar = null; // 認証結果(アクセストークンなどが入る)
try
{
// まずサイレントサインインを試す
try
{
IAccount signedInUser
= (await Program.PCA.GetAccountsAsync()).FirstOrDefault();
if(signedInUser != null)
ar = await PCA.AcquireTokenSilentAsync(Scopes, signedInUser);
// サイレントサインインに失敗するとMsalUiRequiredExceptionが出る
}
catch { }
// サイレントサインインができなかったら、サインインのUIを出す
if (ar == null)
ar = await PCA.AcquireTokenAsync(Scopes);
WriteLine("サインインしました。");
WriteLine($"AccessToken={ar.AccessToken}");
WriteLine($"Username={ar.Account.Username}");
WriteLine($"ExpiresOn={ar.ExpiresOn.LocalDateTime.ToString("HH:mm:ss")}");
WriteLine($"Scopes={string.Join(", ", ar.Scopes)}");
// 出力例:
// サインインしました。
// AccessToken=EwBwA8l6BAAURSN……中略……Rd2Ag==
// Username=biac@***.com
// ExpiresOn=23:02:13
// Scopes=mail.send, openid, profile, user.read
}
catch(MsalException ex)
{
// 認証せずにダイアログを閉じると例外が出る
WriteLine($"{ex.GetType().Name}:{ex.ErrorCode}");
}
if (ar == null)
{
// サインインできなかったのでアプリ終了
#if DEBUG
ReadKey();
#endif
return;
}
// ここでGraph APIを使った処理を行う
// サインアウトする
await PCA.RemoveAsync(ar.Account);
WriteLine("サインアウトしました");
#if DEBUG
ReadKey();
#endif
}
}
Module Module1
' ClientIDには、アプリを登録したときのアプリケーションIDを設定する
Const ClientID As String = "********-****-****-****-************"
Private ReadOnly RedirectUri As String = $"msal{ClientID}://auth"
' ユーザー認証関係のプロパティ
Public ReadOnly Property PCA As PublicClientApplication _
= New PublicClientApplication(ClientID) With {.RedirectUri = RedirectUri}
Public ReadOnly Property Scopes As IReadOnlyList(Of String) _
= New List(Of String) From {"User.Read", "Mail.Send"}.AsReadOnly()
Sub Main()
Task.Run(Async Function() As Task
Await SendMailAsync()
End Function).Wait()
#If DEBUG Then
ReadKey()
#End If
End Sub
Async Function SendMailAsync() As Task
Dim ar As AuthenticationResult = Nothing ' 認証結果(アクセストークンなどが入る)
Try
' まずサイレントサインインを試す
Try
Dim signedInUser As IAccount _
= (Await PCA.GetAccountsAsync()).FirstOrDefault()
If (signedInUser IsNot Nothing) Then
ar = Await PCA.AcquireTokenSilentAsync(Scopes, signedInUser)
' サイレントサインインに失敗するとMsalUiRequiredExceptionが出る
End If
Catch
End Try
' サイレントサインインができなかったら、サインインのUIを出す
If (ar Is Nothing) Then
ar = Await PCA.AcquireTokenAsync(Scopes)
End If
WriteLine("サインインしました。")
WriteLine($"AccessToken={ar.AccessToken}")
WriteLine($"Username={ar.Account.Username}")
WriteLine($"ExpiresOn={ar.ExpiresOn.LocalDateTime.ToString("HH:mm:ss")}")
WriteLine($"Scopes={String.Join(", ", ar.Scopes)}")
' 出力例:
' サインインしました。
' AccessToken=EwBwA8l6BAAURSN……中略……Rd2Ag==
' Username=biac@***.com
' ExpiresOn=23:02:13
' Scopes=mail.send, openid, profile, user.read
Catch ex As MsalException
' 認証せずにダイアログを閉じると例外が出る
WriteLine($"{ex.GetType().Name}:{ex.ErrorCode}")
Catch ex As Exception
WriteLine(ex.ToString())
End Try
If (ar Is Nothing) Then
' サインインできなかったのでアプリ終了
Return
End If
' ここでGraph APIを使った処理を行う
' サインアウトする
Await PCA.RemoveAsync(ar.Account)
WriteLine("サインアウトしました")
End Function
End Module
上のコードを実行すると、次の画像のような認証ダイアログが表示される。
MSALの認証ダイアログ
このダイアログはMSALのライブラリが出している。ここで入力したパスワードは、アプリには渡されない。ライブラリを使わずにAzure ADの認証を受ける場合でも、同様なWebページが表示され、やはりパスワードがアプリに渡されることはない。
メールのメッセージは、Messageクラス(Microsoft.Graph名前空間)で表される。ちなみに、MessageクラスはOutlookItemクラス(Microsoft.Graph名前空間)を継承している。
テキストメールのメッセージを組み立てるには、Messageクラスのインスタンスを作り、そのプロパティを設定すればよい(次のコード)。文字コードを指定する方法はないが、JISコードでエンコーディングされて送信される(恐らく、Windowsの言語設定を見てGraph APIを呼び出すときにAccept-Languageを送信し、それによって判定しているものと思われる)。
// メール作成
var email = new Message
{
ToRecipients = new Recipient[] {
new Recipient {
EmailAddress = new EmailAddress {
Name = "outlook.comからメール受信",
Address = "***@***.jp"
}
},
// Recipientオブジェクトは複数指定可能(CC/BCCも)
},
// CcRecipients = ……省略……
// BccRecipients = ……省略……
Subject = "Graph APIでメールを送信するテスト",
Body = new ItemBody
{
ContentType = BodyType.Text,
Content = "Microsoft GRAPHを使ったメール送信のテスト。",
},
};
' メール作成
Dim email = New Message With
{
.ToRecipients = {
New Recipient With {
.EmailAddress = New EmailAddress With {
.Name = "outlook.comからメール受信",
.Address = "***@***.jp"
}
}
},
.Subject = "Graph APIでメールを送信するテスト",
.Body = New ItemBody With
{
.ContentType = BodyType.Text,
.Content = "Microsoft GRAPH を使ったメール送信のテスト。"
}
}
' CC(CcRecipientsプロパティ)/BCC(BccRecipientsプロパティ)も指定可
' Recipientオブジェクトは複数指定可能(TO/CC/BCC)
なお、MessageクラスにはFromプロパティもあるが、通常は設定しない。サインインしたアカウントのメールアドレスが使われる。サインインしたアカウントに複数のメールアドレスが登録されている場合には、サインインに使ったものとは異なるメールアドレスをFromプロパティに設定することは可能だ。ただし、その場合はNameプロパティは無視されて、登録されている名前が使われる。
もしも、サインインしたアカウントに結び付けられていないメールアドレスをFromプロパティに設定してしまった場合は、メール送信がエラーになるばかりか、そのアカウントが凍結されてしまうこともあるので注意が必要だ(次の画像)。要するに、FROMの詐称は許さないというわけである。
MSALライブラリでGraph APIを使うには、GraphServiceClientクラス(Microsoft.Graph名前空間)を利用する。
Graph APIにリクエストを送信するときにアクセストークンを添付する必要があるのだが、MSALではその処理をIAuthenticationProviderインタフェース(Microsoft.Graph名前空間)で行うように設計されている。その実装を先に作っておこう(次のコード)。
public class MsalAuthenticationProvider : IAuthenticationProvider
{
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
// 確実に有効なアクセストークンを得るために、サイレントサインインを行う
IAccount signedInUser
= (await Program.PCA.GetAccountsAsync()).FirstOrDefault();
AuthenticationResult ar
= await Program.PCA.AcquireTokenSilentAsync(Program.Scopes, signedInUser);
// アクセストークンをrequestにセットする
request.Headers.Authorization
= new AuthenticationHeaderValue("Bearer", ar.AccessToken);
}
}
Public Class MsalAuthenticationProvider
Implements IAuthenticationProvider
Public Async Function AuthenticateRequestAsync(request As HttpRequestMessage) As Task _
Implements IAuthenticationProvider.AuthenticateRequestAsync
' 確実に有効なアクセストークンを得るために、サイレントサインインを行う
Dim signedInUser As IAccount = (Await PCA.GetAccountsAsync()).FirstOrDefault()
Dim ar As AuthenticationResult _
= Await PCA.AcquireTokenSilentAsync(Scopes, signedInUser)
' アクセストークンをrequestにセットする
request.Headers.Authorization _
= New AuthenticationHeaderValue("Bearer", ar.AccessToken)
End Function
End Class
実際にメールを送信するコードは、次のようにシンプルなものだ。
// メール送信
try
{
GraphServiceClient gsc
= new GraphServiceClient(new MsalAuthenticationProvider());
await gsc.Me.SendMail(email).Request().PostAsync();
WriteLine("メール送信完了");
}
catch (Exception ex)
{
WriteLine("メール送信失敗");
WriteLine(ex.ToString());
}
' メール送信
Try
Dim gsc As GraphServiceClient _
= New GraphServiceClient(New MsalAuthenticationProvider())
Await gsc.Me.SendMail(email).Request().PostAsync()
WriteLine("メール送信完了")
Catch ex As Exception
WriteLine("メール送信失敗")
WriteLine(ex.ToString())
End Try
送信するメールにファイルを添付するには、MessageオブジェクトのAttachmentsプロパティを設定すればよい(次のコード)。マルチパートメールの仕組みを意識することなく、簡単にコーディングできる。
email.Attachments = new MessageAttachmentsCollectionPage();
string path = @"C:\Windows\Web\Wallpaper\Theme2\img10.jpg"; // 添付したいファイル
email.Attachments.Add(new FileAttachment
{
ODataType = "#microsoft.graph.fileAttachment",
ContentBytes = System.IO.File.ReadAllBytes(path),
ContentType = "image/jpeg",
Name = System.IO.Path.GetFileName(path),
});
email.Attachments = New MessageAttachmentsCollectionPage()
Dim path As String = "C:\Windows\Web\Wallpaper\Theme2\img10.jpg" ' 添付したいファイル
email.Attachments.Add(New FileAttachment With
{
.ODataType = "#microsoft.graph.fileAttachment",
.ContentBytes = System.IO.File.ReadAllBytes(path),
.ContentType = "image/jpeg",
.Name = System.IO.Path.GetFileName(path)
})
エンドユーザーのMicrosoftアカウントを使って、outlook.comからメールを送信できる。Microsoft Graph APIを使えるようにするまでの準備がちょっと厄介であるが、メールを作って送信するコードは簡単である。
利用可能バージョン:.NET Framework 4.5以降
カテゴリ:オープンソースライブラリ 処理対象:Windowsフォーム
カテゴリ:オープンソースライブラリ 処理対象:WPF
カテゴリ:オープンソースライブラリ 処理対象:Xamarin.Forms
カテゴリ:クラスライブラリ 処理対象:電子メール
使用ライブラリ:Messageクラス(Microsoft.Graph名前空間)
使用ライブラリ:GraphServiceClientクラス(Microsoft.Graph名前空間)
関連TIPS:電子メールを送信するには?(MailKit編)[.NET 4.5、C#/VB]
関連TIPS:構文:インスタンス化と同時にプロパティを設定するには?[C#/VB]
関連TIPS:構文:コレクションのインスタンス化と同時に要素を追加するには?[C#/VB]
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]
関連TIPS:Visual Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?
Copyright© Digital Advantage Corp. All Rights Reserved.