WebサイトからExchange Onlineのメールを送る【PHP】【Microsoft Graph】

WebサイトからExchange Onlineのメールを送る パソコンサポート
パソコンサポートホームページ作成
スポンサーリンク/Sponsored Link

当店HPでは、メールサーバにExchange Onlineを使用しています。

以前は、XServerのメールを普通に使っていたのですが

  • 送ったメールが迷惑メールに入ってしまう
  • iCloudメールに至っては受信すらされない

といった出来事が続き、Exchange Onlineに切り替えたのです。

その際、PHPで送信してみた記事を2018年に公開しました。

Microsoft Exchange + SwiftMailer で、確実に届くメール環境を構築する
【2023/8追記】現在はMicrosoftGraphを使う方法に切り替えました本記事は2018年7月時点で行った作業をまとめたものです。ところが、2023年になり、MicrosoftExchangeOnlineのSMTPAUTHが認証され...

SMTP-AUTHでは認証が通らない事態が多発

ところがその後、マイクロソフトのセキュリティポリシーの変化とともに、単純にID/パスワードで認証する方法(SMTP-AUTH)では、認証が通らないことが度々起こりました。

そのたびにメールが止まり、システム全体を止めて、マイクロソフトサポートに相談してきました。

その都度、マイクロソフトサポートの案内で、Microsoft 365 管理センターでどっかのスイッチをオンにしてみたり、Azure AD(現 Microsoft Entra)のどっかを無効にしてみたり、いろいろと延命してきたのですが

今回ついに、その延命も尽きたようなのです。

マイクロソフトサポートから回答がなかなか来なくなった

以前のトラブルでは、マイクロソフトサポートからも比較的すぐに回答がきていました。

初回トラブル時は「SMTP-AUTHの無効化は誤って適用されたもので、今後もサポートされる」と案内されたため、特にコードは変更しなかったのです。

ところが、その後も認証が通らないことがたびたび起こります。当方側の操作も必要になってきました。

(※現在は、SMTP-AUTHを使用していないため、どちらも上記指定とは逆の設定になっています)

そして今回は、トラブル発生からほぼ一日たっても、マイクロソフトサポートからこれといった解決策が来ないという事態にいたりました。

おそらく、サポートでも予期しない何かの変更があったのだと思うのですが…

そんなわけで、今回は観念して、本腰を入れて解決策を探したところ、「Microsoft Graph」が有望そうだ…ということが見えてきたのです。

こんなことなら、最初の時にサポートからこれを教えてくれていれば…とつい、思ってしまいますが、まあ仕方ありません。

とにかく、なんとかこれを実装して、トラブルにさよならしよう、というのが今回の作業です。

“Microsoft Graph”を使うと、メールが送信できそう

今回は、こちらの記事を見つけ、Microsoft Graphでの実装をやってみよう、と考えました。

Microsoft Graph を使って PHP アプリを構築する - Microsoft Graph
このチュートリアルでは、認証にAzureActiveDirectoryを使用し、データを取得するためにMicrosoftGraphを使用するPHPアプリを構築します。

実際にメールを送信するアクションも見つかりました。

user: sendMail - Microsoft Graph v1.0
要求本文に指定されたメッセージを、JSONまたはMIME形式で送信します。

これなら、引数さえ正しく渡せば、メールが送れそうです。

スポンサーリンク/Sponsored Link

Microsoft Graphを使った自動メール送信・全体像をつかむ

実際の作業は、こちらのチュートリアルに沿ってテストを行い、それを拡張する方法で進めていきました。

Microsoft Graph を使って PHP アプリを構築する - Microsoft Graph
このチュートリアルでは、認証にAzureActiveDirectoryを使用し、データを取得するためにMicrosoftGraphを使用するPHPアプリを構築します。

とはいえ、実際にやった作業を順番にここに書いても、なんだかよく分からなくなりそうです。

そこで、全部終わった時点で得られた知識をもとに、まずは、「Microsoft Graphでメールを送信する」という動作の全体イメージをご紹介していきたいと思います。

全体像をつかむ

今回実装した自動メール送信の全体像を、図解にしてみました。こんな構造です。

Microsoft Entraにユーザーアプリを作成
WebサーバにSDKをインストール
Entraから得た認証情報をサーバに設定
SDKからメール送信要求をGraphに送り、Graphがアプリを呼び出す。Entraが認証を行い、Exchange Onlineでメールを送信する

この図解の各要素をひとつずつ設定していく作業になります。

前提条件

作業の前提として、次の項目が満たされていることが必要です。

  • Microsoft 365 for Businessに契約しており、組織のアカウントがある。
  • Microsoft 365/ Microsoft Entraの管理者権限を持っている
  • Webサーバを契約しており、PHPが使える。SSH接続ができる。composerがインストールされている
  • 送信元アドレスのドメインは、Microsoft365に設定されている
  • 送信元アドレスは、Exchange Onlineのライセンスが割り当てられ、メールボックスの設定が完了している
    (Exchage Onlineを含むプラン、またはExchange Online単体ライセンス)
スポンサーリンク/Sponsored Link

【STEP1】Microsoft Entraにアプリを作成

どこから始めてもいいんですけれども、まずMicrosoft Entraにアプリを作成していきたいと思います。

Microsoft Graph を使って PHP アプリを構築する - Microsoft Graph
このチュートリアルでは、認証にAzureActiveDirectoryを使用し、データを取得するためにMicrosoftGraphを使用するPHPアプリを構築します。

チュートリアルのStep1を参考にしていきますが、現時点でスクショがAzure ADのままなので、うちの管理画面のスクショをモザイク入りで載せていこうかと思います。

管理センターにログイン、アプリ登録画面へ

まずはMicrosoft Entra管理センターにログイン。

Microsoft Entra admin center

「アプリの登録」をみつけます。

Microsoft Entra管理センター
アプリケーション アプリの登録

※スクリーンショットがスマホでは小さくて見づらい場合、指で広げる操作をすると大きくなります。

アプリを新規登録

「アプリの登録」に入ったら、新規登録

アプリの登録 新規登録

通常は、次の画面は既定値のまま、名前だけ決めて登録でOKです。

アプリケーションの登録
名前を入力
登録

次の画面の、赤く囲ったあたりに出ているクライアントIDやテナントIDが、Webアプリに実装するときに必要なものです。あとでコピーします。

作成したアプリの概要
クライアントID
オブジェクトID
テナントIDが表示されている

この画面の下にチュートリアルへのリンクがあります

いまの画面の下にリンクがあり、さまざまな言語に対応したリソースにつながっています。

例えば本記事で使ったチュートリアルは、次のようにたどって見つけたものです。

すべてのクイックスタートガイドの表示
Webアプリケーション
PHP(チュートリアル)

アプリにMail.Sendの権限を付与する

できたてのアプリで「APIのアクセス許可」を見ると、次のようになっています。

作成したアプリ
APIのアクセス許可
アクセス許可の追加

今回は、メールを送信しますので、Mail.Sendの権限を付与していきます。

アクセス許可の追加を選ぶと、2つ選択肢があり

APIアクセス許可の要求
Microsoft Graph
アプリケーションに必要なアクセス許可の種類
委任されたアクセス許可
アプリケーションの許可

【委任されたアクセス許可】 【アプリケーションの許可】の2種類があります。

これが結構重要なところです。

「委任されたアクセス許可」…アプリに認証要求すると、実際にMicrosoft365ユーザーのMFA(多要素認証)画面にリダイレクトされ、認証操作をすることによってはじめて、アプリがその権限を取得する(メールが送れるようになる)

「アプリケーションの許可」…アプリにシークレットキーを設定すれば、サインイン操作不要で、アプリは権限を行使できる(メールを送信できる)

今回は、ユーザーが関与することなく、自動的にメールを送信するのが目的ですので、「アプリケーションの許可」の方を選択します。この方法だと、SDKを噛ませてはいますが、実際に行う処理は、単純なSMTP-AUTHの場合と大差ない実装が可能になります。

※Wordpressのプラグインを使う方法の場合は、「委任されたアクセス許可」の方を選択します。詳しくは後ほど。

そこで、参考にするチュートリアルも、実は、最初にご紹介したチュートリアルの最後の方のリンクからたどった、「アプリ専用認証を使用」というチュートリアルを参考にしていきます。

Microsoft Graph とアプリ専用認証を使用して PHP アプリを構築する - Microsoft Graph
このチュートリアルでは、MicrosoftGraphAPIを使用してアプリ専用認証を使用してデータにアクセスするPHPアプリを構築します。
APIアクセス許可の要求 設定画面
APIの種類 MIcrosoft Graph
必要なアクセス許可 アプリケーションの許可
アクセス許可の検索窓にmailと入力
Mailの項目を展開 Mail.Sendをチェックオンに
アクセス許可の追加ボタンをクリック

「アクセス許可の追加」をクリックすると、次のような画面になります。矢印の箇所に、黄色い警告が出てしまいます。これは、「管理者の同意が必要」が「はい」な権限なのに、まだ同意を付与していないためです。

赤枠の箇所をクリックして、管理者の同意を与えます。

構成されたアクセス許可
Mail.Send 右端 状態の列 に黄色い三角の! で警告
表の上部 管理者の同意を与えますをクリック

すると次のように、同意付与済みの表示に変わります。

APIアクセス許可の右端、状態が緑のチェックマークに変わっている

最初から付与されている「User.Read」のアクセス許可は、いらないかなと思ったんですが、削除しようとすると「アプリケーションが正常に機能するために必要です」と警告が出るので、残しました。

User.Readのアクセス許可を削除しようとすると
アクセス許可の削除
このスコープは、アプリケーションが正常に機能するために必要です

クライアントシークレットの新規作成

次に、クライアントシークレットを作成します。SMTP-AUTHでいうと、パスワードに相当するものです。

作成したアプリ
証明書とシークレット
新しいクライアントシークレット

名前と有効期限をきめるよう指示されます。何に使ったシークレットなのか分かるような名前をつけます。有効期限は、推奨の6か月を使用しました。もっと長くも短くもできます。

クライアントシークレットの追加
説明 サイトメール送信用と入力
有効期限 推奨:180日(6か月)を選択
追加

有効期限がくる前に、クライアントシークレットを新たに作って差し替える必要があるので、カレンダーなどに入力してしっかりと管理をします。

シークレットが作成されたら、赤枠の箇所を確実にメモします。

クライアントシークレット(1)
説明 サイトメール送信用
有効期限 2024/1/30
値 クライアントシークレットが表示されている
シークレットID

「値」がクライアントシークレット本体です。ここを逃すと二度と表示されませんので、確実にメモしてください。

WordPressサイトの場合は、ここまでやったらあとはプラグインで。

もし、自動送信したいウェブサイトがWordPressで作成されている場合は、作業はここまででOKです。

Microsoft Graphでメールを送信できるプラグインがありました。当ブログの自動送信も、このプラグインで行っています。

WPO365 | MICROSOFT 365 GRAPH MAILER
WPO365|MSGRAPHMAILERprovidesyouwithamodern,reliableandefficientwaytosendWordPresstransactionalemailsfromoneofyourMicrosoft365…

プラグインの場合、APIのアクセス許可を「アプリケーションの許可」にしていると、動くんですが警告が表示されます。「委任されたアクセス許可」の方にします。

インストール後、設定画面で、ここまで作った各種IDの入力欄があります。正しく入力して、最下部送信テストボタンで成功すれば設定完了です。

スポンサーリンク/Sponsored Link

【STEP2】サーバにMicrosoft Graph SDKをインストール

次に、使用しているサーバにMicrosoft Graph PHP SDKをインストールします。

SSH接続して、Composerを使ってインストールしていきます。

この項目に必要だった知識まとめ

細かく書くと本記事の趣旨から逸脱するため、詳しい方法が書かれたリンクをまとめます。

SSH設定 | レンタルサーバーならエックスサーバー
レンタルサーバー「エックスサーバー」のご利用マニュアル|エックスサーバーではSSH接続機能を提供しています。当マニュアルではお客様のサーバーアカウントへSSHで接続するための手順を記載しています。
SSHソフトの設定(Tera Term) | レンタルサーバーならエックスサーバー
レンタルサーバー「エックスサーバー」のご利用マニュアル|「TeraTerm」を用いてお客様のサーバーアカウントへSSH接続するための手順をご案内しています。
エックスサーバー Composerをインストール(バージョン変更)する方法
エックスサーバーにはデフォルトでComposerがインストールされていますが、バージョンが古いため別のディレクトリに最新バージョンをインストールします。既存のComposerのバージョン確認バージョン確認用のコマンドで確認するとCompos

SDKのインストール

チュートリアルを参照します。

Microsoft Graph とアプリ専用認証を使用して PHP アプリを構築する - Microsoft Graph
このチュートリアルでは、MicrosoftGraphAPIを使用してアプリ専用認証を使用してデータにアクセスするPHPアプリを構築します。
composer require microsoft/microsoft-graph

うちの場合は、環境変数は他の方法で読み込ませるのでphpdotenvは入れませんでした。入れる場合はチュートリアル通り「vlucas/phpdotenv」を追加。

また、すでにいろいろとcomposer使用中だったので、チュートリアルにある「composer init」はなしで。

Composer 1系で実行すると、非対応ですと言われインストールできません。Composer 2にバージョンアップしてから実行する必要があります。

各種ID・キーは、今回は.htaccessで設定

各種キーは.htaccess に入れ込みました。(.htaccessは外部からアクセス不可を確認のうえです。)

SetEnv GRAPH_CLIENT_ID '[クライアントID]'
SetEnv GRAPH_CLIENT_SECRET '[クライアントシークレット]'
SetEnv GRAPH_TENANT_ID '[テナントID]'

チュートリアルのように.envを使ってもよいが、くれぐれも.envを外部から読みだされないよう細心の注意を払う必要あり。

それぞれの’GRAPH_~’という名前は任意の名前で。同じ名前をコード側で参照できればOK。

クライアントID、テナントIDはここです。(さきほどと同じスクリーンショット)

作成したアプリの概要
クライアントID
オブジェクトID
テナントIDが表示されている

クライアントシークレットは、さきほど新規作成して保存しておいたもの。「値」に書いてあるものを入れます。

クライアントシークレット(1)
説明 サイトメール送信用
有効期限 2024/1/30
値 クライアントシークレットが表示されている
シークレットID

新規作成の一度きりしか表示されませんので、今から見に行っても見れません。控え忘れていたら、いちど削除して作り直しです。

スポンサーリンク/Sponsored Link

【STEP3】PHPコードを書く

チュートリアルに掲載のコードを、今回の目的と手段にあわせて書き換えたものを用意します。

Microsoft Graph とアプリ専用認証を使用して PHP アプリを構築する - Microsoft Graph
このチュートリアルでは、MicrosoftGraphAPIを使用してアプリ専用認証を使用してデータにアクセスするPHPアプリを構築します。

いらないものが残っていたり、もっと良い書き方があったりするかもしれませんが、ひとまずこういうコードで送信は成功しました。

チュートリアル掲載コードのコメントがそのまま残ってたりしますが、ああそこを使ったのかという参考程度に、比較してごらんください。

GraphHelper.inc

<?php
use Microsoft\Graph\Graph;
use Microsoft\Graph\Http;
use Microsoft\Graph\Model;
use GuzzleHttp\Client;

class GraphHelper {
private static Client $tokenClient;
private static string $clientId = '';
private static string $clientSecret = '';
private static string $tenantId = '';
private static Graph $appClient;
private static string $appToken;

public static function initializeGraphForAppOnlyAuth(): void {
    GraphHelper::$tokenClient = new Client();
    GraphHelper::$clientId = getenv('GRAPH_CLIENT_ID');
    GraphHelper::$clientSecret = getenv('GRAPH_CLIENT_SECRET');
    GraphHelper::$tenantId = getenv('GRAPH_TENANT_ID');
    GraphHelper::$appClient = new Graph();
}
public static function getAppOnlyToken(): string {
    if (isset(GraphHelper::$appToken)) {
        return GraphHelper::$appToken;
    }

    $tokenRequestUrl = 'https://login.microsoftonline.com/'.GraphHelper::$tenantId.'/oauth2/v2.0/token';

    // POST to the /token endpoint
    $tokenResponse = GraphHelper::$tokenClient->post($tokenRequestUrl, [
        'form_params' => [
            'client_id' => GraphHelper::$clientId,
            'client_secret' => GraphHelper::$clientSecret,
            'grant_type' => 'client_credentials',
            'scope' => 'https://graph.microsoft.com/.default'
        ],
        // These options are needed to enable getting
        // the response body from a 4xx response
        'http_errors' => false,
        'curl' => [
            CURLOPT_FAILONERROR => false
        ]
    ]);

    $responseBody = json_decode($tokenResponse->getBody()->getContents());
    if ($tokenResponse->getStatusCode() == 200) {
        // Return the access token
        GraphHelper::$appToken = $responseBody->access_token;
        return $responseBody->access_token;
    } else {
        $error = isset($responseBody->error) ? $responseBody->error : $tokenResponse->getStatusCode();
        throw new Exception('Token endpoint returned '.$error, 100);
    }
}
public static function sendMail(string $subject, string $body, string $recipient,string $mime): void {
    $token = GraphHelper::getAppOnlyToken();
    GraphHelper::$appClient->setAccessToken($token);

    $sendMailBody = array(
        'message' => array (
            'subject' => $subject,
            'body' => array (
                'content' => $body,
                'contentType' => $mime
            ),
            'toRecipients' => array (
                array (
                    'emailAddress' => array (
                        'address' => $recipient
                    )
                )
            )
        )
    );

    GraphHelper::$appClient->createRequest('POST', '/users/[送信元となるMS365ユーザーアドレス]/sendMail')
                            ->attachBody($sendMailBody)
                            ->execute();
}
}
?>

うちの場合は、メール送信元アドレスは一つだけで固定なので、ハードコーディングしてしまいました。複数アドレスから送信する場合は、ここを引数で渡す必要がありそうです。

「アプリケーションの許可」で認証していますので、ユーザーのメールアドレスさえ指定すれば、テナント内の任意のユーザーとしてメールが送信できてしまいます。

また、チュートリアル通りでは、テキスト形式のメールしか送信できません。引数$mimeを追加し、HTMLメールも送信できるようにしています。

メール送信用の関数モジュール

いまのGraphHelper.incを使って、こんなふうに。

<?php

require_once 'vendor/autoload.php';
require_once __DIR__.'/GraphHelper.inc';

function sendGraphmail_html($to_email, $subject, $body): void {

	initializeGraph();

    try {
        GraphHelper::sendMail($subject, $body, $to_email,'HTML');
    } catch (Exception $e) {
        die('メール送信エラーです。繰り返し発生する場合は、お手数ですが当店までご連絡ください。'.PHP_EOL.PHP_EOL);
    }
}
function sendGraphmail($to_email,$subject, $body): void {

	initializeGraph();

    try {
        GraphHelper::sendMail($subject, $body, $to_email,'text');
    } catch (Exception $e) {
        die('メール送信エラーです。繰り返し発生する場合は、お手数ですが当店までご連絡ください。'.PHP_EOL.PHP_EOL);
    }
}

function initializeGraph(): void {
    GraphHelper::initializeGraphForAppOnlyAuth();
}
?>

既存のモジュールが、テキストメールとHTMLメールで呼び出す関数を分けていたため、便宜上このように2つにしました。最後の引数を「text」にすればテキストメール、「HTML」とすればHTMLメールが送信できました。

スポンサーリンク/Sponsored Link

これで完成

あとは、できた送信モジュールを既存のコードに組み込めば完成。

最初に載せた全体図の全要素がそろいました。

Microsoft Entraにユーザーアプリを作成
WebサーバにSDKをインストール
Entraから得た認証情報をサーバに設定
SDKからメール送信要求をGraphに送り、Graphがアプリを呼び出す。Entraが認証を行い、Exchange Onlineでメールを送信する

以上で作業完了。メールの送信テストをおこない、SMTP-AUTHを残すために設定していた項目も、はれて推奨設定に戻して終了です。

スポンサーリンク/Sponsored Link

おわりに

記事を書くことで、細かいところの再点検になりました。

トラブルが発生してから対処すると、大急ぎになるので、細かいところが抜けがちです。

そして、マイクロソフトサポートに相談しても、SMTP-AUTHを延命する話しか出てこなく、対応が遅れてしまいました。こういう話をMicrosoft 365のサポートから聞こうというのが、そもそも無理な相談なのかもしれません。

そんなわけで、本職の方が見たら笑われるかな、と思いつつ、次の方が同じ苦労をしなくてすめば、と思い、記事にしてみた次第です。

パソコン教室・キュリオステーション志木店からのお知らせ
レッスンはオンラインで受講できます

パソコン教室・キュリオステーション志木店では、本年よりオンラインでの在宅レッスンを実施しております。
教室の全コースがオンラインで受講可能。実際にインストラクターがご対応いたします。
1時間の無料体験レッスンはいつでも予約できます。詳しくは公式ページをご覧ください。

スポンサーリンク/Sponsored Link
キュリオステーション志木店運営をフォローする
志木駅前のパソコン教室・キュリオステーション志木店のブログ

コメント

タイトルとURLをコピーしました