@memo

ゆるくインプット、ゆるくアウトプット

GmailをSlackのチャンネルに通知する超シンプルなGAS

こんにちは。asatoです。

spaces.bzは友人と開発してまして、各種サービスは共有のGoogleアカウントを使っています。 どちらかしか通知を受けられない、操作できないのは面倒なので。

しかし、自分たちもGoogleアカウントを持っており、Gmailを見るためにアカウントを切り替えるのは面倒ですし、 コミュニケーションはSlackで行っているので、GmailをSlackのチャンネルに通知してSlackで会話できるといいなーと思っていました。

色々と調べると

  1. Slackの転送先メールアドレスをGmailの転送先に指定する
  2. Google Workspace Marketplaceの「Slack for Gmail」を使う
  3. Slackの「Email」アプリを使う
  4. GAS

など、いろいろな方法が紹介されていました。 しかしながら、どれも要件を満たすのが難しそう。

  1. slackbotへのDMになってしまいチャンネルポストができなそうです。
  2. ブラウザでGmailを開いて、メールごとチャンネルごとに手作業が必要です。
  3. 「Email」アプリが有料ワークスペースでないと使えなそうです。
  4. 検索で引っかかるコードを見る限り、割と複雑そうです...

んー、どうしたものか。

ん?4、本当に複雑なのか?

あれ?お前、実はもっとシンプルになるんじゃないのか?

ということで、GmailをSlackに通知するGASはすごくシンプルだよ!の例を示していきたいと思います。

サンプルコード

いきなりサンプルコードです。(メールをSlackに転送したいアカウントで作成します)

function main() {
  const threads = GmailApp.search('in:Inbox is:Unread', 0, 100)
  
  threads.forEach((thread) => {
    thread.getMessages().forEach((message) => {
      if (!message.isUnread()) { return }
      const text = create_message(message)
      send_to_slack(text)
      message.markRead()
    })
  })
}

function create_message(message) {
  return  `[Date] ${message.getDate()}`
          + `\n[From] ${message.getFrom()}` 
          + `\n[Subject] ${message.getSubject()}`
          + `\n[Body]\n${message.getPlainBody()}`
}

function send_to_slack(text) {
  const webhook_url = <SlackのIncoming webhook URL>
  const headers = { "Content-type": "application/json" }
  const data = { "text": text }
  const options = {
    "method": "post",
    "headers": headers,
    "payload": JSON.stringify(data),
    "muteHttpExceptions": true
  }
  UrlFetchApp.fetch(webhook_url, options)
}

send_to_slack関数はSlackにテキストメッセージを送るところなのでノーカウント(この記事では説明しません)とすると、めちゃくちゃシンプルじゃありません?

javascriptのコードはおいておいて、GmailApp系の部分をちょっとだけ解説します。

GmailApp.search( )

const threads = GmailApp.search('in:Inbox is:Unread', 0, 100)

GmailApp.search(query, start, max)queryの条件に合うスレッドの配列(GmailThread[])を取得する関数です。 今回は'in:Inbox is:Unread'を指定しているので、「受信ボックス」の中で「未読」のものを拾ってきます。 また、startに0、maxに100を指定しているので、最新のスレッドから100件までの中からqueryを検索してもらっています。

thread.getMessages( )

threads.forEach((thread) => {
  thread.getMessages().forEach((message) => {
    ...
  })
})

GmailThread.getMessages()でそのスレッド内のメッセージ(メール)の配列(GmailMessage[])を取得してます。 それをforEachで回すことで一つ一つのメッセージを扱えます。こいつがメール本体。主役の登場です。

message.isUnread( )

function main()
if (!message.isUnread()) { return }

GmailMessage.inUnread()はそのメッセージが未読ならtrue、既読ならfalseを返却します。 このコードではそれの否定(!)でif文を書いているので、既読の場合return、つまり後続の処理をせずにforEachを継続させています。

message.getXXX( )

function main()
const text = create_message(message)
 
...

function create_message(message) {
  return  `[Date] ${message.getDate()}`
          + `\n[From] ${message.getFrom()}` 
          + `\n[Subject] ${message.getSubject()}`
          + `\n[Body]\n${message.getPlainBody()}`
}

Slackに投稿するテキストメッセージを生成しているのがこの辺です。 GmailMessage.getXXX()を使ってメッセージを作ってます。getXXX()はいろいろな種類があるので、投稿したいテキストメッセージに合わせてカスタマイズしてください! 今回は、「受信日時(getDate())」「送信者(getFrom)」「タイトル(getSubject())」「プレーンテキストの本文(getPlainBody())」を利用してみました。

message.markRead( )

最後にmessage.markRead()でメッセージを既読にしています。 これで、次回以降は!message.isUnread()に弾かれて処理されなくなります。

まとめ

どうですか?意外にシンプルですよね。 あとはGASのトリガーを好きな時間単位で指定すれば出来上がりです。 僕たちはGASのエラー通知をすぐに受信したいので1分単位でトリガーをセットしています。

GASでこういうことできるよ!という記事はいっぱいあるのですが、Googleで検索してもなかなか公式ドキュメントにたどり着けないんですよね。 実はめちゃくちゃわかりやすいので公式ドキュメントを眺めてやりたいことを実現していきましょう!!👏

例えばGmailなら

誰かのためになれば嬉しいです。

こんな感じでGoogleからのエラー通知をバンバン受け取っているspaces.bzの応援もよろしくお願いします!