Gmailで特定のラベルがついたメールをスプシに抽出して通知する

目的

Gmailでフィルタでラベルを付けて、そのメールを受信した際にスプレッドシートに書き込み、画面上に通知を行うのを目的としています。(需要は無いと思うので自分用のメモw)

Chrome拡張機能を使えばそういうの出来るかもしれないけど。AppScriptとNode.jsとElectronというのを使ってやってみました。(というかchatGPT先生に教えてもらって)

手順として以下の様に進めた

  1. Gmailのフィルタ機能でラベル付の設定
  2. Gmailからスプシに取り込む スプレッドシートを作成
  3. AppScriptを作成 
  4. Electronアプリを作成する
    • Node.jsをインストール
    • Electronをnpmで追加
    • package.jsonを作成
    • axiosをインストール
    • main.jsを作成
  5. 起動 npm start

具体的な手順とコード

①Gmailのフィルタ機能でラベル付の設定

設定>全ての設定を表示>フィルタとブロック中のメールアドレス

新しいフィルタを作成:特定の送信先メールアドレスや、含む条件を設定し、「フィルタを作成」。
含む条件で、メール本文中に特定のワードが含まれる場合は、 ("ワードA" OR "ワードB") と設定する。これでワードAかワードBの文字列を含むメールを受信すればラベルが付与される。
受信トレイをスキップする(アーカイブする)にチェックを入れると、受信トレイ上では見えなくなりラベルの方でのみ見えるようになる。
ラベルを付けるにチェックを入れ、分類するラベルを選択する。●通の一致するメールにもフィルタを適用するにチェックを入れると既存のメールにもラベルが付与される。

②Gmailからスプシに取り込む スプレッドシートを作成

スプレッドシートを作成する。

A1セル:ID
B1セル:日付
C1セル:送信者
D1セル:題名
E1セル:本文
F1セル:ラベル
J1セル:最終実行時間 と入力
K1セル:今日の日付を入力しておく (例)2024/3/1

③AppScriptを作成

スプレッドシート>拡張機能>AppScript

以下のコードを入力。今回は2つのラベルA、ラベルBを取り込むようにしてあります。

function doPost(e) {
  // saveEmailsToSheet関数から新着メールがあったかどうかの結果を取得
  var foundNewEmails = saveEmailsToSheet();
  
  // 新着メールの有無に応じてレスポンスメッセージを動的に変更
  var result;
  if (foundNewEmails) {
    result = {
      status: "new email",
      message: "New emails found and processed."
    };
  } else {
    result = {
      status: "no new email",
      message: "No new emails found."
    };
  }
  
  // レスポンスをJSON形式で返す
  return ContentService.createTextOutput(JSON.stringify(result))
    .setMimeType(ContentService.MimeType.JSON);
}



function saveEmailsToSheet() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');

  // スプレッドシートのK1セルから前回の実行時刻を取得
  var lastRun = sheet.getRange('K1').getValue();
  
  // 前回の実行時刻が空であれば、適当に古い日付を設定
  if (!lastRun) {
    lastRun = new Date('2024-03-01T00:00:00Z');
  }
  
  // 現在の時刻を取得
  var now = new Date();
  // 検索クエリに前回の実行時刻以降を条件に追加
  // Google Apps ScriptではDateオブジェクトを'yyyy-mm-dd'形式の文字列に変換するためにUtilities.formatDateを使用
  var formattedLastRun = Utilities.formatDate(lastRun, Session.getScriptTimeZone(), "yyyy-MM-dd"); //afterオペレータは日付のみしか扱えず時分秒は考慮されない

  var lastRow = sheet.getLastRow();
  var emailRange;
  if (lastRow > 1) {
    emailRange = sheet.getRange(2, 1, lastRow - 1, 1); // 実際にデータが存在する場合
  } else {
    emailRange = null; // データが存在しない場合
  }
  var emails = emailRange ? emailRange.getValues().flat() : []; // emailRangeがnullでなければ値を取得し、そうでなければ空の配列を設定


  // ラベルAのメールを処理
  var messages1Data = processLabel('ラベルA', formattedLastRun, sheet, emails);
  
  // ラベルBのメールを処理
  var messages2Data = processLabel('ラベルB', formattedLastRun, sheet, emails);

  // 全メールを結合する
  var messagesData = messages1Data.concat(messages2Data);

  // メッセージを受信時間でソートしてスプレッドシートに書き込む
  messagesData.sort(function(a, b) {
    return a.date - b.date; // 昇順ソート
  }).forEach(function(data) {
    // F列にラベル名を含めて書き込む
    sheet.appendRow([data.messageId, data.date, data.from, data.subject, data.body, data.label]);
  });


  // 現在の実行時刻をK1セルに記録
  sheet.getRange('K1').setValue(now);

  // 新着メールの有無に応じたブール値を返す
  var foundNewEmails = messagesData.length > 0;
  return foundNewEmails;
}

function processLabel(label, formattedLastRun, sheet, existingEmails) {
  var query = 'label:' + label + ' after:' + formattedLastRun;
  var threads = GmailApp.search(query);
  var messagesData = [];

  threads.forEach(function(thread) {
    var messages = thread.getMessages();
    messages.forEach(function(message) {
      var messageId = message.getId();
      if (!existingEmails.includes(messageId)) { // 既存のメールIDと比較
        var date = message.getDate();
        var from = message.getFrom();
        var subject = message.getSubject();
        var body = message.getPlainBody();
        
        // メッセージのデータを配列に追加
        messagesData.push({ messageId: messageId, date: date, from: from, subject: subject, body: body, label: label });
      }
    });
  });

  return messagesData;
}

デプロイする。

デプロイボタンを押して、ウェブアプリとしてデプロイをする。デプロイURLをメモしておく。

④Electronアプリを作成する

Windowsの中で動作するElectronアプリと言うのを作って、そのアプリからAppScriptにリクエストを送って、それがトリガーとなってGmailを読み込みにいきます。Gmailで新着メールがあれば、その情報をElectronアプリに戻し、Electronアプリ内でブラウザ画面を表示して、アラート画面で最前面に出すという流れにしました。(ChatGPTによってなぜかそうなった)

1.Node.jsのインストール

Node.jsの公式サイトからダウンロードしてインストールする。

2. 新しいプロジェクトの作成

コマンドライン(ターミナル)を開き、新しいプロジェクトディレクトリを作成し、そのディレクトリに移動します。

mkdir my-electron-app
cd my-electron-app

3. npmでプロジェクトを初期化

次に、以下のコマンドを実行してnpmプロジェクトを初期化し、package.jsonファイルを作成します。

npm init -y

4. Electronのインストール

Electronをプロジェクトに追加するため、以下のコマンドを実行します。

npm install --save-dev electron

5. package.jsonファイルを作成

{
  "name": "label mail checker",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "electron": "^29.1.0"
  },
  "dependencies": {
    "axios": "^1.6.7"
  }
}

6.Axiosのインストール

ターミナルを開いて、プロジェクトディレクトリで以下のコマンドを実行し、axiosをインストールします。

npm install axios

7.main.jsを作成

Electronアプリのメインプロセスとなるmain.jsを作っていきます。

const { app, BrowserWindow, dialog } = require('electron');
const axios = require('axios'); // 正しいインポート方法
const endpointUrl = 'デプロイURL';

let mainWindow = null;

function createWindow() {
  // BrowserWindow インスタンスを作成
  mainWindow = new BrowserWindow({
    width: 500,
    height: 100,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false, // セキュリティリスクに注意
    }
  });

  // mainWindow.loadURL('ここにフロントエンドのHTMLファイルのパスを指定');
  // 将来ウェブコンテンツを表示したり、JavaScriptを実行する場合は、セキュリティを考慮した保護設計が必要となります。
}

function showDialog(title, message) {
  mainWindow.setAlwaysOnTop(true, 'normal');
  dialog.showMessageBox(mainWindow, {
    type: 'info',
    buttons: ['OK'],
    title: title,
    message: message,
  }).then(() => {
    mainWindow.setAlwaysOnTop(false); // ダイアログ閉じた後、最前面表示を解除
  });
}

app.whenReady().then(() => {
  createWindow();

  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });

  // 1分おきにsendRequestを実行
  setInterval(sendRequest, 60000);
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit();
});

function sendRequest() {
  axios.post(endpointUrl)
    .then(response => {
      console.log(response.data);
      if (response.data.status === "new email") {
        showDialog('新着メッセージ', response.data.message);
      } else if (response.data.status === "no new email") {
        //console.log(response.data.message);
      }
    })
    .catch(error => {
      console.error('Error:', error);
      showDialog('エラー', 'メールの確認中に問題が発生しました。');
    });
}

8.起動する

npm start

これで小さいブラウザ(中身は空っぽ)が出て来てプログラムが動き始める。

テストしてみる

Gmailにテストメールを送ってみて、ラベル付けがされて、それがスプレッドシートに格納され、ブラウザ上にダイアログ画面が最前面に出てくればOKです。