Gmailの新着メールをLINEに転送する by Google Apps Script

@Panda_Program

Gmailで受信したメールをGASでLINEに転送する

私事ながら2019年に結婚しました。それから結婚式の式場を選び、日取りを決めて、当日の準備に当たります。すると、式場から打ち合わせのメールが不定期に飛んできます。

私はメールの受信箱を頻繁に見ないので、やりとりはSlackにしたいと式場に申し出てもあえなく断られました。そこで、GmailからLINEにメールを転送すれば見逃しがなくなると考え、Google Apps Script(以下、GAS)で実装しました。

本記事では、GmailからLINEに特定のメールを転送する方法をご紹介します。

関連記事:

GASでGmailの受信メールをLINEに転送するコードを解説します

まずコードを掲載します。その後、コメントを付与した箇所に解説を加えていきます。

main.js
const ENDPOINT = 'https://notify-api.line.me/api/notify'

// 1. 転送したいメールの送信元アドレスを指定する
const FROM_ADDRESS = [''].join(' OR ')
// 2. トリガーの設定間隔と合わせる
const MINUTES_INTERVAL = 5

function fetchNotices() {
  const now = Math.floor(new Date().getTime() / 1000)
  const intervalMinutesAgo = now - (60 * MINUTES_INTERVAL)
  // 3. 検索条件を設定
  const query = `is:unread from:(${FROM_ADDRESS}) after:${intervalMinutesAgo}`

  // 4. メールを取得する
  const threads = GmailApp.search(query)
  const mails = GmailApp.getMessagesForThreads(threads)
  const notices = []

  for (const messages of mails) {
    const latestMessage = messages.pop()
    const notice = `
--------------------------------------
件名: ${latestMessage.getSubject()}
受信日: ${latestMessage.getDate().toLocaleString()}
From: ${latestMessage.getFrom()}
--------------------------------------

${latestMessage.getPlainBody().slice(0, 350)}
`
    notices.push(notice)

    // 5. メールを既読にする
    latestMessage.markRead()
  }

  return notices
}

1. 転送したいメールの送信元アドレスを指定する

const FROM_ADDRESS = [''].join(' OR ')

**配列内に転送したいメールの送信者のアドレスを追加します。**これは@example.comのようにドメインを指定しても構いません。これで、全ての新着メールではなく、特定の送信者からのメールだけ転送するような設定ができます。

もしこの配列が空である場合、定数FROM_ADDRESSには空文字列が代入されるため、全てのメールを取得することになります。

このため、全てのメールをGmailからLINEに転送したい場合、特にメールアドレスを記述する必要はありません。

2. 5分前までの新着メールを取得する

const MINUTES_INTERVAL = 5

定数MINUTES_INTERVALで5分前までの新着メールを転送対象とします。この時間は、あとで設定するトリガー(関数の実行タイミング)と合わせるようにします。

3. 検索条件を設定(フィルター設定)

const query =
  `is:unread from:(${FROM_ADDRESS}) after:${intervalMinutesAgo}`

queryはメールの検索条件です。この条件に該当するメールだけを転送対象とします。以下が今回使う絞り込みの条件です。

| key | value | 条件 | 例 | |--|--|--|--| | is | unread | 未読のメールのみ | is:unread | | from | [email protected] OR yyy.com | 転送するメールの送信元 | from([email protected] OR yyy.com) | | after | 1592707480 | この時間以降のメール | after:1592707480 |

この検索条件は、Gmailのフィルター設定をすると自動で作成されます。条件を変更する場合は、Gmailのフィルター設定から条件を指定して、その結果を使ってコードを書き換えましょう。

Gmailのフィルターを設定する画面

なお、検索条件の組み立てはビルダーパターンで設計するのが定石です。

ただし、メールの検索条件を頻繁に変更することはないので、今回は文字列で必要十分です。

4. 新着メールを取得する

  const threads = GmailApp.search(query)
  const mails = GmailApp.getMessagesForThreads(threads)
  const notices = []

  for (const messages of mails) {
    const latestMessage = messages.pop()
    // ...
  }

検索条件に該当するメールは、GmailAppのsearchメソッドで取得します。返り値の型はGmailThread[]です。

次にgetMessagesForThreads(threads)メソッドで、それぞれのメールの一連のやりとりを取得します。返り値はGmailMessage[][]です。

for (const messages of mails)で2次元配列mailsの値をmessagesに格納します。for (const a of b)は、配列bの値を変数aに格納する記法です。

これと似た記法でfor (const a in b)というものもありますが、こちらは配列のindexを取得する記法です。

const array =  ['a', 'b', 'c']

for (const char of array) {
    console.log(char)
}

// a
// b
// c

for (const index in array) {
    console.log(index)
}

// 0
// 1
// 2

これで最新のメールを取得できました。

GmailにおけるThreadとMessageの違い

ここでThreadMessageという用語の整理をしましょう。細かい内容なので読み飛ばしてもらっても構いません。

searchメソッドで取得できるThreadは「あるメールとそのメールに対する一連の返信」です。あるメールを送った後そのメールに返信がつき、さらにそのメールに返信する、というのがメールの使い方です。Threadには一番最初のメールとそれに付随する返信を含んだものです。

一方、getMessagesForThreadsメソッドで取得するMessageは単体のメールです。元のメールならそのメール、返信ならその返信です。

つまり、Threadは最初のメールとそれに対する返信メールです。一件一件のメールそのものはMessageと呼ばれているのです。これがThreadとMessgeの違いです。

5. 新着メールを既読にする

latestMessage.markRead()

markRead()メソッドでメールを既読にできます。これで毎回Gmailの未読件数の増加を防げますね。

5分単位で新着メールをチェックする

新着メールを取得する処理を記述できました。次は、5分ごとにLINEに転送する設定を書きます。

GASのトリガーを5分単位で設定する

次に、トリガーを設定しましょう。上記で設定した間隔(今回は5分)間隔でGASを動かすようにします。

トリガー設定画面

GASのトリガー設定方法は「GASのトリガーを設定する」をご覧ください。

実行する関数はmain関数にします(記事最後のコード全文を参照)。

LINEにメールを転送する

最後にLINEにメールを転送する関数を作成しましょう。

const LINE_NOTIFY_TOKEN = PropertiesService
  .getScriptProperties()
  .getProperty('LINE_NOTIFY_TOKEN')
const ENDPOINT = 'https://notify-api.line.me/api/notify'

function send(mail) {
  const options = {
    'method': 'POST',
    'headers': {'Authorization': `Bearer ${LINE_NOTIFY_TOKEN}`},
    'payload': {'message': mail},
  }

  UrlFetchApp.fetch(ENDPOINT, options)
}

LINEに転送するためには、LINE Notifyのトークンを取得する必要があります。

また、取得したトークンはPropertiesServiceで使えるように、GASのプロパティに格納しておきましょう。

これでメールの転送準備ができました。

実際にメールを転送してみる

実際に自分にメールを送ってみて、LINEに転送できているか確認してみましょう。

LINEの画面

きちんと転送されていますね。なお、コード全文は記事の最後に記載しています。

まとめ

式場を決めてすぐの頃、妻との間で「式場からのメール確認した?」というやりとりを何度かしたので、これは二人がよく見るLINEに転送せねばと思いコードを実装しました。

GASのおかげで妻との会話の始まりが「メールチェックした?してない?」ではなく、「返信内容をどうする?」という本質的な内容に変化し、効果を実感できました。

非同期コミュニケーションが生まれる仕組みを作り、時間を有効に活用しましょう 🎉

コード全文を記載します

今回使ったコードの全文を記載します。

main.js
const LINE_NOTIFY_TOKEN = PropertiesService
  .getScriptProperties()
  .getProperty('LINE_NOTIFY_TOKEN')
const ENDPOINT = 'https://notify-api.line.me/api/notify'

const FROM_ADDRESS = [''].join(' OR ')
const MINUTES_INTERVAL = 5

function main() {
  const notices = fetchNotices()

  if (notices.length === 0) {
    return
  }

  for (const notice of notices) {
    send(notice)
  }
}

function fetchNotices() {
  const now = Math.floor(new Date().getTime() / 1000)
  const intervalMinutesAgo = now - (60 * MINUTES_INTERVAL)
  const query = `(is:unread from:(${FROM_ADDRESS}) after:${intervalMinutesAgo})`

  const threads = GmailApp.search(query)

  if (threads.length === 0) {
    return []
  }

  const mails = GmailApp.getMessagesForThreads(threads)
  const notices = []

  for (const messages of mails) {
    const latestMessage = messages.pop()
    const notice = `
--------------------------------------
件名: ${latestMessage.getSubject()}
受信日: ${latestMessage.getDate().toLocaleString()}
From: ${latestMessage.getFrom()}
--------------------------------------

${latestMessage.getPlainBody().slice(0, 350)}
`
    notices.push(notice)

    latestMessage.markRead()
  }

  return notices
}

function send(notice) {
  if (LINE_NOTIFY_TOKEN === null) {
    Logger.log('LINE_NOTIFY_TOKEN is not set.')
    return
  }

  const options = {
    'method': 'POST',
    'headers': {'Authorization': `Bearer ${LINE_NOTIFY_TOKEN}`},
    'payload': {'message': notice},
  }

  UrlFetchApp.fetch(ENDPOINT, options)
}

Happy Coding 🎉

パンダのイラスト
パンダ

記事が面白いと思ったらツイートやはてブをお願いします!皆さんの感想が執筆のモチベーションになります。最後まで読んでくれてありがとう。

  • Share on Hatena
  • Share on Twitter
  • Share on Line
  • Copy to clipboard