Gmailで受信した内容をSlackに通知する

# はじめに

この記事ではGmailの内容をGASを使ってSlackに通知してみます。

# 作るもの

今回の想定はチームで一つのGoogleグループのメーリングリストを持っている場合に
受信したメールをSlack通知したい。
さらに、送信元ごとにSlackチャンネルを分けたい！というのをGASで実装します。

![無題の図形描画 (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445600/80d8918e-faad-97af-6dc1-93692a43f2de.png)

# この記事で話さないこと

- GASのGmail操作の詳細な説明

# 事前準備

## Slackアプリの設定&トークンの取得

以下の記事がとてもわかりやすくまとめてくださっています。

https://qiita.com/kobayashi_ryo/items/a194e620b49edad27364

## 取得したトークンをスクリプトプロパティに保存

`SLACK_TOKEN`という名前でSlackのトークンを保存します。

![無題のプロジェクト-プロジェクトの設定-Apps-Script (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445600/e15b5639-ef45-0fd7-33fc-9d57c5c3e3ee.png)


# GAS作成

## 参考にさせていただいた記事

https://zenn.dev/c_hero/articles/88cf874394b5e0

## 完成したコード

```javascript
// 送信元アドレスと、受信したら通知したいSlackのチャンネルIDを書く
const CHANNEL_LIST = {
  '送信元メールアドレス': { 
    'channel': '通知先SlackチャンネルID', 
    'icon_emoji': ':email:'
  },
}

function main(){
  const results = search();
  if(results){
    for(const result of results){
      const channelInfo = CHANNEL_LIST[result.from];
      console.log(channelInfo)
      sendToSlack(result.body, channelInfo.channel, channelInfo.icon_emoji || ':email:' );
    }
  }else{
    // 該当のメールが存在しない場合はコンソールログを出力
    console.log('There are not messages.');
  }
}

function sendToSlack(text, channel, icon_emoji) {
  const SLACK_TOKEN = PropertiesService.getScriptProperties().getProperty('SLACK_TOKEN');
  console.log(SLACK_TOKEN)
  const url = "https://slack.com/api/chat.postMessage";
  const payload = {
    "token" : SLACK_TOKEN,
    "channel" : channel,
    "username": "メール通知",
    "icon_emoji": icon_emoji,
    "text" : text,
  };
  
  const params = {
    "method" : "post",
    "payload" : payload
  };
  
  const result = UrlFetchApp.fetch(url, params);
  console.log(result)
}

function search() {
  const now = new Date();
  // 10分前（Unixタイム）: Gmailでの時分秒単位での検索はUnixタイムにする必要あり
  const tenMinBeforeUnix = parseInt((now.getTime() - 10 * 60 * 1000)/1000);
  const senders = Object.keys(CHANNEL_LIST).map(sender => `from:${sender}`).join(' OR ')
  const query = `after:${tenMinBeforeUnix} AND (${senders})`;
  console.log(query)
  const threads = GmailApp.search(query);
  let results = [];
  for(const thread of threads){
    const threadId = thread.getId();
    const subject = thread.getFirstMessageSubject();
    const messages = thread.getMessages();
   for(const message of messages) {
      const sender = message.getFrom();
      const receivedDate = Utilities.formatDate(message.getDate(), "JST", "yyyy/MM/dd HH:mm:ss");
      
      const baseUrl = 'https://mail.google.com/mail/u/0/#inbox';
      const mailUrl = `${baseUrl}/${threadId}`;
      results.push(
       {
         from: sender.match(/([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*)/)[0],
         body: `受信日時:${receivedDate}\n送信者:${sender}\n件名:${subject}\nメールURL:${mailUrl}\n本文:\`\`\`${message.getPlainBody()}\`\`\``
       }
      )
    }

  }

  return results;
}
```

## 通知対象を設定

`channel`に送信元メールアドレス。`icon_emoji`にSlack通知時のbotのアイコンを設定してください。

```javascript
// 送信元アドレスと、受信したら通知したいSlackのチャンネルIDを書く
const CHANNEL_LIST = {
  '送信元メールアドレス': { 
    'channel': '通知先SlackチャンネルID', 
    'icon_emoji': ':email:'
  },
}
```


## 実行する

手動でmainを実行します。

![無題のプロジェクト-プロジェクト編集者-Apps-Script (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445600/5efc1198-e87a-4155-404d-c994c45a1965.png)


Slackにメールが通知されました。

![スクリーンショット 2023-12-23 21.51.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445600/11244f80-d62a-14bf-9a9c-37c9cb60a57f.png)

## スケジュール登録

トリガーを10分ごとに設定すれば
定期的にメールをチェックしてSlack通知されます。


![スクリーンショット 2023-12-23 22.12.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445600/7259bd99-528b-40ff-cc46-231779f2c328.png)


# 細かい実装について

## Gmailのフィルター

`CHANNEL_LIST`のKEYを送信元アドレスにしており、`senders`で送信元アドレスをフィルタリングするための文字列を作成しています。

以下が作成される文字列です。
`CHANNEL_LIST`のメールアドレスが増えると`from: 送信元メールアドレス OR from: 送信元メールアドレス`と増えてきいきます。

```
after:1703335800 AND (from: 送信元メールアドレス)
```

```javascript
const CHANNEL_LIST = {
  '送信元メールアドレス': { 
    'channel': '通知先SlackチャンネルID', 
    'icon_emoji': ':email:'
  },
}
const tenMinBeforeUnix = parseInt((now.getTime() - 10 * 60 * 1000)/1000);
const senders = Object.keys(CHANNEL_LIST).map(sender => `from:${sender}`).join(' OR ')
const query = `after:${tenMinBeforeUnix} AND (${senders})`;
```

## Slack通知先仕分け

取得したメールを送信元と本文ごとに配列挿入。

`getFrom()`はメールアドレスが取得されるわけではなく `名前（メールアドレス）`のようにメールのFromに入ってくるテキストが返ってきます。
後で、`CHANNEL_LIST`との照合に使いたいのでメールアドレス部分だけ抽出するようにします。

```javascript
 from: sender.match(/([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*)/)[0],
```

```diff:javascript
 let results = [];
  for(const thread of threads){
    const threadId = thread.getId();
    const subject = thread.getFirstMessageSubject();
    const messages = thread.getMessages();
   for(const message of messages) {
      const sender = message.getFrom();
      const receivedDate = Utilities.formatDate(message.getDate(), "JST", "yyyy/MM/dd HH:mm:ss");
      
      const baseUrl = 'https://mail.google.com/mail/u/0/#inbox';
      const mailUrl = `${baseUrl}/${threadId}`;
+      results.push(
+       {
+         from: sender.match(/([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*)/)[0],
+         body: `受信日時:${receivedDate}\n送信者:${sender}\n件名:${subject}\nメールURL:${mailUrl}\n本文:\`\`\`${message.getPlainBody()}\`\`\``
+       }
      )
    }
```

`result.from`には正規表現で抽出した送信元が入っているので、送信元メールアドレスの
通知先SlackチャンネルIDを取得し、Slackの`chat.postMessage`のchannelをチャンネルIDをメールごとに切り替えることで通知先を仕分けてます。

```javascript
const CHANNEL_LIST = {
  '送信元メールアドレス': { 
    'channel': '通知先SlackチャンネルID', 
    'icon_emoji': ':email:'
  },
}
```


```javascript
function main(){
  const results = search();
  if(results){
    for(const result of results){
      const channelInfo = CHANNEL_LIST[result.from];
      console.log(channelInfo)
      sendToSlack(result.body, channelInfo.channel, channelInfo.icon_emoji || ':email:' );
    }
  }else{
    // 該当のメールが存在しない場合はコンソールログを出力
    console.log('There are not messages.');
  }
}
```

# 最後に

GASはGgoole Workspace周りのことは基本なんでもできるので楽しいですよね。
今回は受信したメールの送信元ごとにチャンネルを分けてSlackに通知してみました。
ぜひ、システム系メールをまとめて一個のアドレスで受け取っているみたいな場合にお試しください。