お問い合わせフォームの内容を PowerAutomate で Planner にタスク化して管理する

# お問い合わせフォームの内容をタスク化して抜け漏れを防ぐ
WordPress でコーポレートサイトを運用していると、大抵の場合フォームからのお問い合わせをメールで受け付けるような設定が多いと思われる。
中小企業でもそれなりの人数になってくると、「誰かがやっているだろう」「メールが多くて未読ばかり」といった感じになってくることがある。

DX の基本として、Teams を導入し社内メールを原則禁止とはしたものの、お客様とのやり取りに配布メールが含まれていたりして、毎日何十通ものメールが届くことを回避することはできない。
そこにフォームからのお問い合わせメールが届いたとしても、そもそも開いてもいなかったり、誰が返答したかもわからなかったりと、気が付くと抜け漏れが発生していることがあるため、フォームからのお問い合わせを Planner でタスク化し管理することとした。

なお、追加で 2 営業日を土日祝日判定して期限日を決めるフローと、 Teams のタグへメンションする方法も実装している。
## 前提条件
* お問い合わせフォームは複数のメールアドレスに同時に送信できるプラグイン等を使用している
* Teams が導入済み
* Teams に専用のメールアドレスで投稿が可能（管理者が禁止している場合もあり）
## 事前準備
まずは Teams にお問い合わせフォームからのメール投稿を受け付ける専用のチャネルを用意した。チャネル名の右側三点リーダー（ … ）から「メール アドレスを取得」で Teams に投稿可能なメールアドレスを取得しておく。
次に、同じ専用チャネルのタブで「 Planner および To Do タスク Tasks」を開始し、お問い合わせを管理する Planner を作っておく。

WordPress のお問い合わせフォームには Contactform 7 を利用していたため、送信先のメールアドレスは複数設定することが可能であった。既存のメールアドレスに追加して先ほど取得したメールアドレスを設定、実際にフォームに投稿し Teams に自動的に投稿されることを確認しておく。
## 実装
ちょっとフローが多いが、完成形は以下のようになっている。
<details><summary>クリックして開く</summary>

![screencapture-make-powerautomate-environments-Default-8c13009c-bc66-408e-8430-225f19456283-flows-16e1cb84-3f24-45de-aa17-832a12235f11-2022-09-02-14_38_58 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/571defd7-f3e5-808c-ae19-2cae82b1ee11.png)
</details>
タスクを作成する際に「宛先」と「対応期限」（2営業日）を処理しているため、少し複雑な処理が加わっている。フォームからの宛先がひとつで、対応期限にも特にこだわりがなければ省いてもよい。

まずはトリガーであるが、今回はフォームから Teams へのメール投稿が実施されたことをキーとして実行することにした。フォームからのメールは、お問い合わせの内容により各事業部のメールアドレスへ振り分けて送信されるため、メールを受け取ったことをキーにはできなかったためだ。
「チャネルに新しいメッセージが追加されたとき」をトリガーとして開始。最初に作っておいたチャネルを指定しておく。
![スクリーンショット 2022-09-01 135509 (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/c35d4def-c777-704b-0ca3-cc26fab055a4.jpeg)
次に投稿されたメッセージ内容から情報を取得する。「メッセージ詳細を取得する」フローを配置して、メッセージにはトリガーから取得できる「メッセージ ID 」を設定する。
![スクリーンショット 2022-09-01 135533 (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/5b9bc3aa-8e57-68d7-fb6b-acd2427a9e28.jpeg)
メッセージの種類は「チャネル」とし、チームと先ほど作成したチャネルを指定しておく。
次に取得したメッセージから本文を抜き出す。今回は「Html からテキスト」フローを利用した。
![スクリーンショット 2022-09-01 135558.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/bb2f3da1-5739-564b-40ec-34007092b942.jpeg)
Teams に投稿された本文は html で記述されているため、処理しやすくするためにテキスト化して取得している。html の方が処理しやすいのであれば変換は必要ない。

次に、投稿された Teams のタイトルはメールフォームから送られてくるメールタイトルそのままになっているため、場合によってはタスク名として使えない。そのため、本文から適切にタイトルとして使えそうな文言を抜き出すこととする。
ついでにタスク本文にメール本文の必要な箇所のみを転記するために、本文からも一部を抜き出す処理を追加する。

まずは宛先として文字列を抜き出すが、そのための本文中にある文言の文字位置を検索して抜き出す。「テキストの位置の検索」フローを追加して、テキストに先ほど html からテキスト化した本文を指定する。
![スクリーンショット 2022-09-01 135638.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/506d6614-4d63-39c3-7699-d356f430d25a.jpeg)
検索テキストは、今回メール本文開始部分に「事業部にお問い合わせがありました。」との文言から始まっているため、「お問い合わせがありました。」を検索テキストに指定している。
これで「事業部に」の「に」の部分の位置が取り出せた。

ちなみに、「に」から選択していない理由は、現在のフォームの仕様により、「事業部にお問い合わせがありました。」のパターンと、「その他のお問い合わせがありました。」と業務内容に関する問い合わせのパターンが存在しているため、両方に対応するためである。このような特殊な事情でない限り検索テキストは適切な文言を設定すればよい。

次に「部分文字列」フローを追加して、テキストは変換後の本文、開始位置は最初なので「 0 」を指定、長さは上記特殊な事情がなければテキストの位置の検索で取得した位置をそのまま当てはめればよい。
![スクリーンショット 2022-09-01 135702.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/909fe92b-e645-df05-3114-b420de5b89b2.jpeg)
今回は特殊な事情を考慮し、長さの部分に`sub()`関数を用いて「 1 」を引いている。
```c#
sub(outputs('テキストの位置の検索（宛先）')?['body'],1)
```

続けて本文を抜き出す。「テキストの位置の検索」フローを追加して変換後の本文を指定、フォームから送られてくるメールのお客様情報部分に「【お客様情報】」との記載があるため、そのまま検索テキストとして利用している。
![スクリーンショット 2022-09-01 140055.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/709f73b7-c20a-5118-330f-f9b693132b54.jpeg)
文末まで取得しても良いが、Contactform 7 からのメールには、送信先が分かるようにサイトの情報が文末に含まれている。これを除去するためにもうひとつ「テキストの位置の検索」フローを追加して変換後の本文を指定、文末についてくるコメントの目前にある「 -- 」を検索テキストとして指定する。
![スクリーンショット 2022-09-01 140118.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/214df61f-1720-b34b-8591-c8fff4eef3e4.jpeg)
さて実際に本文を抜き出す作業になるが、まずは「honbun」「matsubi」「mojisuu」の変数を用意した。
![スクリーンショット 2022-09-01 163938.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/89ee3182-974c-7faf-4d31-62d9be812d86.jpeg)
それら変数を設定していくが、まずは本文の変数について、値には`add()`関数を使用し 7 を足している。
![スクリーンショット 2022-09-01 140345.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/9f86c6b1-bc6c-9c5c-c4dc-6fffb40e59cb.jpeg)
```c#
add(outputs('テキストの位置の検索（本文）')?['body'],7)
```
これは「【お客様情報】」の文字数が「 7 」文字のためこの文字列を排除するためだ。もちろん必要がなければ普通に指定して頂いて問題ない。

次に末尾の変数だが、ここは`sub()`関数で「 1 」を引いている。余計な文字が入らないようにするためだ。
![スクリーンショット 2022-09-01 140404.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/67118d36-d02b-1eca-975b-64cbd11854d0.jpeg)
```c#
sub(outputs('テキストの位置の検索（末尾）')?['body'],1)
```

最後に文字数の変数だが、`sub()`関数で末尾から本文を引くことで作り出している。
![スクリーンショット 2022-09-01 140421.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/ae8c38dd-628b-005e-9d6e-a5be9274ee15.jpeg)
```c#
sub(variables('matsubi'),variables('honbun'))
```

これらの数値を使用して「部分文字列」フローにて本文を抜き出す。
![スクリーンショット 2022-09-01 140504.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/1f5c7e10-803b-3b3c-9eeb-0b950e122962.jpeg)
テキストには変換後の本文を指定、開始位置は「honbun」の変数、長さは「mojisuu」の変数を指定している。

さて、情報が揃ったことから、 Planner のタスクを登録していく。「タスクを作成する」フローを追加して、事前に作成していた Planner のグループ ID 、プラン ID 、バケット ID を記載していく。
![スクリーンショット 2022-09-01 142457 (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/7400dbe1-ba84-32df-59d2-26d2303fe6f6.jpeg)
タイトルには「部分文字列（宛先）」で抜き出した本文を使用し、分かりやすく文章にしてみた。
開始日時は Teams に投稿された際の作成時間をそのまま指定している。
期限日時が入っているが、これは別途オプションとして説明するためここでは触れない。

最後に「タスクの詳細の更新」フローを利用して、抜き出した本文をタスク内にも記載していく。
![スクリーンショット 2022-09-01 142536.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/1e9bb060-c729-5fba-2491-2af50bf9e220.jpeg)
タスク ID には「タスクを作成する」フローで作られた ID をそのまま利用、説明には「部分文字列（本文）」で抜き出した本文を指定した。

これでフォームから送られてくるメールを Teams に投稿し、さらに Planner でタスクを作成するところまで完成した。
## Teams にタスクが登録されたことを @メンションで通知する（オプション）
ここからはタスク作成には必須ではないが、同様に抜け漏れを防ぐための方法として、 Planner のタスクが登録されたことを Teams の投稿されたメッセージに返信する形で、さらに事業部毎に作られたタグ宛てに @メンションして通知するフローを追加してみる。

まずは抜き出した「部分文字列（宛先）」を利用してメンション先を取得することから始める。「スイッチ」フローを追加してオンに「部分文字列（宛先）」の本文を指定する。
![スクリーンショット 2022-09-01 135845.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/99d50b05-d0f3-7c1f-43b1-221244d424d5.jpeg)
次にスイッチ先を事業部の数だけ用意してメンションタグを取得する。 Teams にタグを用意していることが前提条件となる。
![スクリーンショット 2022-09-01 135935.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/01a6f95b-b6f4-8151-e744-dc70a938f768.jpeg)
ケースには次の値と等しいに「部分文字列（宛先）」で取得できる事業部名を入れている。
タグのメンションを取得するには「タグの @mention トークンを取得する」フローを追加してタグを指定して取得している。
![スクリーンショット 2022-09-01 135956 (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/f4e53929-dae6-2084-6c98-4ba5d4e1ef82.jpeg)
チームとタグ ID を指定するだけでメンショントークンが取得できる。
このメンショントークンを元に「チャネル内のメッセージで応答します」フローを追加して返信を投稿する。
![スクリーンショット 2022-09-01 140022 (2).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/45ada322-2b16-0a17-802a-f88493a6f1ed.jpeg)
投稿者は「User」でないとメンションはできないと思われる。今後のアップデートで可能になるかもしれないが、現時点ではフローを動作させているユーザーでの自動投稿となるだろう。
投稿先は「Channel」で、メッセージ ID には取得した Teams の投稿から Messageid を指定する。

あとはチームとチャネルを指定して、適切な本文を記載すればよい。本文には「タグの @mention トークンを取得する」で取得した「@mention タグ」を指定すればタグにメンションを送ることができる。
## 土日祝日判定を含めた 2 営業日を期限日に設定する（オプション）
せっかくタスクが自動作成されても、対応を行わなければ意味がない。タスクに管理者が担当者を割り振れば最初に通知が届く。しかしその後担当者がうっかり忘れてしまうとやはり抜け漏れにつながってしまう。

Planner では期限日が設定されていれば、期限切れに対して担当者に通知が届く。初期お問い合わせに対する返答は、少なくとも 2 営業日以内に返すのがよいと個人的に思っているので、作られたタスクにも登録日から 2 日後を設定していた。
ところが、金曜日にタスクが作られると日曜日が期限日になってしまうため、いろいろと都合が悪い。そこで、土日判定および祝日判定を実施して 2 営業日を割り出すこととした。

まずは土日祝日ではない営業日を判定する変数「holiday」と、土日祝日を判定する変数「weekday」を用意した。
![スクリーンショット 2022-09-01 171815.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/20ff3dcb-e285-bf8b-1264-fdba2517ba69.jpeg)
営業日を判定する「holiday」にはとりあえず「 0 」を設定している。
土日祝日を判定する「weekday」は種類が「ブール値」だ。値としてただ「false」を指定している。

次に、まず最初の 1 日目が土日でないかを判定するところから開始する。その後のループを簡易にするために最初の土日判定は個別に設定した。土日を判定するために「スイッチ」フローを利用している。オンには Teams に投稿された日付情報を元に`addDays()`関数を使用し +1 日で計算をしている。さらにその日付から`dayOfWeek()`関数で曜日の数値を取得した値をスイッチのデータとして利用した。
![スクリーンショット 2022-09-01 140540.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/465be8cb-ce88-93e9-0ad0-08bb4b3a7aa0.jpeg)
```c#
dayOfWeek(addDays(triggerOutputs()?['body/createdDateTime'],1))
```
この曜日の数値データを元に土日の判定を行っている。土曜日の場合「 6 」が返ってくるため次の値と等しいに「 6 」を指定する。
![スクリーンショット 2022-09-01 140611.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/be4702e8-4526-b837-1f62-0f55757430e9.jpeg)
翌日が土曜日の場合（つまりお問い合わせは金曜日）最初の 1 営業日目は 3 日後になるため、変数「holiday」の値を「 3 」に設定する。

次に日曜日の場合であるが「 0 」が返ってくるため次の値と等しいに「 0 」を指定する。
![スクリーンショット 2022-09-01 140633.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/b4c3c756-a17c-f27f-5636-dd0ffcb37f5b.jpeg)
翌日が日曜日である場合（つまりお問い合わせは土曜日）最初の 1 営業日目は 2 日後になるため、変数「holiday」の値を「 2 」に設定する。

ケースの「既定」には翌日が土日でなかった場合の設定を行っておく。
![スクリーンショット 2022-09-01 140655.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/0215f2d8-d8b6-652b-48f0-a19d0c44ec57.jpeg)
翌日が「土日ではない」と仮定して、最初の 1 営業日目は 1 日後として変数「holiday」の値を「 1 」に設定する。

ここから土日祝日判定をループで実施していく。「Do until」フローを追加して変数「weekday」が「true」になるまで繰り返す設定とする。最初に変数「weekday」を無条件で「false」に設定しているため、ここは必ず 1 回以上ループする。
![スクリーンショット 2022-09-01 140734.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/67664f72-1c94-8140-b75a-15601b312da1.jpeg)
祝日判定には Google カレンダーを利用する。「カレンダーのイベントの一覧を表示する」フローを追加し、Google のアカウントと連携したら、カレンダー ID を「日本の祝日」とする。
![スクリーンショット 2022-09-01 140757.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/ff312118-95bd-63bb-2793-6c313b1a724c.jpeg)
最小時間と最大時間は共に変数「holiday」の値を参照でいいのだが、まったく同じ時間を設定してしまうとデータを取得できなかった。そのため最小時間には`yyyy-MM-ddT00:00:00`とフォーマットの指定で対象日の 0 時、最大時間には`yyyy-MM-ddT23:59:59`と対象日の 24 時ギリギリ前を指定して設定している。
* 最小時間（ UTC 変換の必要があったためコードを修正）
~~addDays(triggerOutputs()?['body/createdDateTime'],variables('holiday'),'yyyy-MM-ddT00:00:00.0000000Z')~~
```c#
convertTimeZone(addDays(triggerOutputs()?['body/createdDateTime'],variables('holiday'),'yyyy-MM-ddT00:00:00'), 'Tokyo Standard Time', 'UTC')
```
* 最大時間（ UTC 変換の必要があったためコードを修正）
~~addDays(triggerOutputs()?['body/createdDateTime'],variables('holiday'),'yyyy-MM-ddT23:59:59.0000000Z')~~
```c#
convertTimeZone(addDays(triggerOutputs()?['body/createdDateTime'],variables('holiday'),'yyyy-MM-ddT23:59:59'), 'Tokyo Standard Time', 'UTC')
```

なお、対象日の取得には`addDays()`関数を用いて Teams の投稿時間をベースに、変数「holiday」の値を足している。変数にはその前の土日判定で 1 ～ 3 の数値が入っている。また、カレンダーからデータを取得するには UTC で日時を指定する必要があるため`convertTimeZone`関数を用いて UTC 時間に変更している。

次に取得した祝日の情報を元に「条件」フローを追加して判定を実施している。`empty()`関数を用いて「カレンダーのイベントの一覧を表示する」フローから「イベント リスト アイテム」を取得してブール値を得る。
![スクリーンショット 2022-09-01 140820.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/50a068ae-b543-6a0b-285a-75714a06149b.jpeg)
```c#
empty(outputs('カレンダーのイベントの一覧を表示する（1営業日目）')?['body/items'])
```
次の値に等しいとして`false`を比較対象とし、はいといいえの分岐処理につなげている。
![スクリーンショット 2022-09-01 140849.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/bcb2ad2f-bc84-75ec-2392-569b0c6ca06c.jpeg)
判定が祝日だった場合、「変数の値を増やす」フローで変数「holiday」の値を 1 増やす。さらに「変数の設定」フローで変数「weekday」のブール値を`false`と設定している。

判定が祝日でなかった場合にも同様に「変数の設定」フローを追加している
![スクリーンショット 2022-09-01 140912.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/13365beb-9827-759e-3767-a6d3e67cee4b.jpeg)
設定で変数「weekday」のブール値を`true`と設定し、ループ終了の値を設定しておく。

次に「条件」フローを追加して土曜日であるかの判定を実施している。
![スクリーンショット 2022-09-01 140952.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/8d31bccb-c238-1fd6-9cb1-3856f5ac87ec.jpeg)
お問い合わせが木曜日で金曜日が祝日だった場合、翌日が土曜日になってしまう。祝日は 1 日分しか判定していないため、祝日判定で日曜日になってしまうことはない、土曜だけ判断すれば問題はない。
判定には`dayOfWeek()`関数で「 6 」の場合を取得すればよいので、`addDays()`関数により Teams の投稿時間から変数「holiday」の値を足して、`equals()`関数でブール値に変換している。
```c#
equals(dayOfWeek(addDays(triggerOutputs()?['body/createdDateTime'],variables('holiday'))), 6)
```
次の値に等しいで`true`だった場合は土曜日と判定する。
![スクリーンショット 2022-09-01 141013.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/c36e6a3d-1aef-9b17-8f42-c4e3054f49ed.jpeg)
祝日判定で 1 追加されている場合で、さらに次の日が土曜日だった場合は、次の営業日は月曜日となる想定のため、「変数の値を増やす」フローにて変数「holiday」を「 2 」増やしておく。

なお、この時点ではまだ月曜日が祝日ではない判定ができていないため、「変数の設定」フローを追加して、変数「weekday」を`false`にしておくことを忘れてはいけない。

条件フローで土曜日でなかった場合は空にしておく。ループが繰り返されて、次の営業日が祝日でないかつ土曜日でもないとなった場合に`true`となってループを抜ける。

2 営業日目の判定はループから開始している。まずは変数を調整するため「変数の値を増やす」フローを追加して変数「holiday」に「 1 」を足している。これは目前のループで 1 営業日目までの日数を作っているため、 2 営業日目となる +1 日を作っている。さらに変数「weekday」のブール値を`false`に戻して次のループに対応している。
![スクリーンショット 2022-09-02 134956.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/c4f3a2d1-42a9-dca1-1c1c-fcd3166c2f1a.jpeg)
変数が用意できたら「条件」フローを追加して先に土曜日判定を行う。これを行うことで 2 営業日目のループも 1 営業日目のループと同じ内容で作成できる。
![スクリーンショット 2022-09-02 143635.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/dfc1ce32-9096-3f3e-1b76-807eac7cd060.jpeg)
判定条件も 1 営業日目のループ内で実施した土曜日判定と同じだ。
![スクリーンショット 2022-09-02 143658.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/b85e1985-19de-ad7f-6a91-700d445aecd2.jpeg)
違うのはループ内ではないため、「変数の値を増やす」フローで変数「holiday」を +2 するだけで変数「weekday」の変更は実施しない。

そして「Do until」フローを追加して変数「weekday」が`true`になるまで繰り返す。
![スクリーンショット 2022-09-02 143723.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/edd4b81a-e21a-6a44-cf0d-5bd47209b863.jpeg)
内容は 1 日目の判定と同じだ。まずは「カレンダーのイベントの一覧を表示する」フローを追加して情報を取得。次に「条件」フローを追加して祝日かどうか判定を行う。最後に「条件」フローを追加して土曜日判定を実施して繰り返しだ。

2営業日目までの日数が判定できたら「変数を初期化する」フローを追加して変数「timelimit」を設定する。
![スクリーンショット 2022-09-01 142422.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12937/9417ff56-0543-6190-21c0-8798b3f23b0d.jpeg)
```c#
addDays(triggerOutputs()?['body/createdDateTime'],variables('holiday'))
```
値は`addDays()`関数を使用して Teams の投稿時間に最終的に決まった日数の変数である「holiday」を足して日付データとして設定する。
この変数をタスク作成時の「期限日時」に設定すれば 2営業日後を期限としたタスクが作成可能となる。