ある日、Gmailで新規メールを受信したらLineへ通知したいと思いました。
実現する方法がわかったので、その方法について書いていきます
Google apps script(以下GAS) × Line Messaging APIで実現します
※Line notifyは2025/3/31で使えなくなるとのことなので使いません。
概要の説明、手順の説明の順で進めます。
仕様の概要
処理の概要はこちらです。
- 特定のラベルがついたGmailの受信状況を定期的に確認する(GAS)
- 確認したメールの中に、新着メールがあるか確認する(GAS)
- 新着メールがあった場合は、Lineの公式アカウントにメッセージを送信させる(GAS、Line Messaging API)
本記事に記載する方法には、以下の制約があります。
これらを受け入れられる場合は読み進めてください。
- メール受信からLine通知までタイムラグがある(設定次第ですが、5~10分)
- 1時間ごとの通知対象メール受信数が300を超えると、Lineへの通知が一定時間できなくなる(最遅で1時間ほどで復活する)
手順
概要
これからやることの概要です。細かいことは各項目で説明していきます。
各手順に確認ポイントを設けるので、確認ができたら次の手順へ進むようにしてみてください。
- Lineの公式アカウント作成、友達登録
- Line Messaging APIのアクセストークン取得
- Gmailの設定
- GASのプロジェクト作成
- GASを書く
- GASの設定
Lineの公式アカウント作成、友達登録
Lineの通知先として必要なので、公式アカウントを作成して友達になります。
下記リンクの「LINE公式アカウントを始める」ボタンから、Lineへのログインと公式アカウントの作成をします。
メールアドレスでログインすることもできますが、
Lineアカウントでログインすると公式アカウントの作成から友達登録までスムーズに行えます。
確認ポイント
-
以下リンクのアカウントリストに、作成した公式アカウントが表示されていること
https://manager.line.biz -
作成した公式アカウントとLineの友達になれていること
Line Messaging APIのアクセストークン取得
アクセストークンは、Lineにメッセージを送るための認証に使う長い文字列です。
GASからLine messagin APIを叩く(Lineに通知を送る)のに必要なので、取得しておきます。
-
MessagingApiを利用するための設定画面へ移動する
以下リンクへ飛んで、
https://manager.line.biz/
以下の順で画面を移動していってください。
> 先程の手順で作成した公式アカウントをクリック
> 画面右上の歯車アイコンをクリック
> 画面左のMessaging APIをクリック「Messaging APIを利用する」ボタンが表示されているはずです。
-
Messaging APIを利用するための設定を行う
「Messaging APIを利用する」をクリックします。
プロバイダを選択する画面になるので、この時点でプロバイダがない場合は「プロバイダを作成」を選択して作成してください。利用規約などの欄は空欄で良いです。 -
アクセストークンを取得
以下リンクへ飛び、
https://developers.line.biz/ja/
以下の順で画面を移動していってください。
> 画面右上の「コンソール」をクリック
> プロバイダーから、先程作成したプロバイダを選択
> チャネル設定から、先程作成した公式アカウントを選択
> 「Messaging API設定」をクリック
> チャネルアクセストークンの、「発行」ボタンをクリック(下の方にスクロールするとあると思います)
ここで発行したアクセストークンは後ほど使用するので、後でコピペできるようにどこかにメモしておいてください
確認ポイント
- アクセストークンが取得できたこと
Gmailの設定
Gmail側で設定を行います。
Gmailの自動振り分け機能を使って、Lineへ通知したいメールにラベルをつけるよう設定します。
詳細は省きますが、以下の手順で作成することになります。
- ラベルを作成
- フィルタを作成し、ラベルをつけるように設定
ここで設定したラベルの名前を後で使うので、メモしておいてください。
確認ポイント
- Gmailの自動振り分け機能でラベルをつけることができたこと
GASのプロジェクト作成
-
プロジェクト作成
以下リンクから、GASのダッシュボードに飛びますhttps://script.google.com/home
ログインなどが済んだら、「新しいプロジェクト」をクリックしてプロジェクトを作成します。
わかりやすいようにプロジェクト名を変更しておくと良いです。 -
ファイル作成
ファイルの+ボタンをクリック>「スクリプト」を選択して、ファイルを追加できます
ファイル名を変更し、最終的に以下3つのファイルを作成してください。
確認ポイント
新規プロジェクトを作成し、main.gs, config.gs, lineAPI.gsが作成できたこと
それ以外のファイルがないこと
GASを書く
デフォルトで書いてあったコードは削除し、各ファイルに以下のコードを貼り付けます。
貼り付けたら、Ctrl+sを押してファイルの変更を保存します。
main.gs
/**
* 定数定義
*/
// メールの受信を確認した最後の日時を格納するスクリプトプロパティのキー
const PROPERTY_KEY_LAST_CHECK_DATETIME = "LAST_CHECK_DATETIME";
// lineAPIのアクセストークンを格納するスクリプトプロパティのキー
const PROPERTY_KEY_LINE_API_TOKEN = "LINE_API_TOKEN";
/**
* メイン処理
*/
// メイン関数
function main() { // () -> null
// 設定ができているかどうか確認する。falseだったら検証失敗と判定して中断する
if (validateConfig() == false){
return;
}
// 新着メールを確認する
const {latestMessageDateTime, newMessages} = checkGmailInbox();
// Gmail受信チェックが異常終了したら、終了する
if (latestMessageDateTime == null || newMessages == null){
return;
}
// 新着メールがなかったらログを出して終了する
if (newMessages.length == 0) {
Logger.log("[INFO] 新着メールはありませんでした。終了します。");
return;
}
// 新着メールをLineに通知する
notifyGmailsToLine(newMessages);
// 最終チェック日時のスクリプトプロパティを更新する
setLastCheckDateTime(latestMessageDateTime);
}
/**
* 設定検証処理
*/
function validateConfig(){// () -> Boolean
// Line Message APIのアクセストークンが設定されているかどうか検証する
const lineApiToken = getLineApiToken();
if (lineApiToken == null) {
console.error("[ERROR] Lineのアクセストークンが設定されていません。config.gsのconfigureLineApiTokenを実行して設定してください。");
return false;
}
// フィルタにラベルが指定されているかどうか検証する
const labelConfigLength = TARGET_LABEL_LIST.length;
if (labelConfigLength == 0){
console.error("[ERROR] 受信チェック対象にするGmailのラベルが設定されていません。設定しないとすべてのメールが対象になり通知の量が制限できないため、必ずラベルを設定してください。")
return false;
}
// 最終確認日がスクリプトプロパティに設定されているかどうか検証する
const lastCheckDateTime = getLastCheckDateTime();
// 初回実行時などlastCheckDateTimeに値が入っていない場合は現在の時刻を設定し、終了する。
// (次回実行時からは機能するようになる)
if (lastCheckDateTime == null) {
console.warn("[INFO] スクリプトプロパティ「" + PROPERTY_KEY_LAST_CHECK_DATETIME + "」に値が設定されていなかったため、現在の時刻を設定します");
setLastCheckDateTime(new Date());
console.warn("[INFO] 今回は処理を終了します。次回の実行から受信検知を開始できます");
return false;
}
return true;
}
/**
* メール検知処理
*/
// Gmailの受信ボックスを確認して、最新メールの受信日時と新着メールの配列を返す
function checkGmailInbox(){ // () -> DateTime, GmailMessage[]
// 前回受信ボックスをチェックしたときの最新メールの日時をスクリプトプロパティから取得する
const lastCheckDateTime = getLastCheckDateTime();
Logger.log("[INFO] 前回実行時の最新メール受信日時: " + lastCheckDateTime);
// 最終チェック日以降のメールを取得する(searchメソッドのクエリに時間を設定できないため、日付が最終チェック日以降かどうかのみで検索することになる)
// 念のため、取得するメールの数に上限を設ける。
const query = createSearchQuery(lastCheckDateTime);
const threads = GmailApp.search(query, 0, MAX_RETRIEVE_THREADS_COUNT);
const threadsLength = threads.length;
Logger.log("[INFO] 取得したスレッド数: " + threadsLength);
// チェックしたメールのうち一番最新のメールの受信日時を入れる変数。
// 全メールチェック後にこの変数に入っていた日時を最終チェック日時とする。
// 初期値は現在の最終チェック日時とする。
let latestMessageDateTime = lastCheckDateTime;
// 新着メールを保持する配列
let newMessages = [];
// スレッドごとに処理を行う。
// スレッド内のメッセージ郡を取り出し、メッセージを処理する関数に渡す。
for (let i = 0; i<threadsLength; i++){
const thread = threads[i];
const lastMessageDatetime = thread.getLastMessageDate();
Logger.log("[INFO] 確認したスレッドの最終受信日時: " + lastMessageDatetime);
// スレッドの最新メッセージ最終受信(送信?)日時が最終チェック日時より新しいかどうか判定
if (lastMessageDatetime > lastCheckDateTime) {
// 新しいスレッドの場合は、メッセージの判定処理に回す
const messages = thread.getMessages();
const temporaryNewMessages = checkMessages(messages, lastCheckDateTime);
newMessages = newMessages.concat(temporaryNewMessages);
}
// チェックしたメールの中で最新のメールが存在していた場合は、値を更新する
if (lastMessageDatetime > latestMessageDateTime){
latestMessageDateTime = lastMessageDatetime;
}
}
//最新メールの受信日時と新着メールの配列を返す
return {latestMessageDateTime, newMessages};
}
// メール取得時のクエリを作成する
function createSearchQuery(lastCheckDateTime){ // (DateTime) -> String
// query仕様メモ
// {}内はOR検索、{}で囲まない場合はAND検索になる
// 指定日付後に受信したメールを取得する ex)after: 2025/03/18
const afterFilter = "after:" + Utilities.formatDate(lastCheckDateTime, 'JST', 'yyyy/MM/dd')
// 指定のラベルのメールを取得する ex){label:example label:test}
let labelFilter = "{";
for (let i=0; i< TARGET_LABEL_LIST.length; i++) {
labelFilter = labelFilter + " label:" + TARGET_LABEL_LIST[i];
}
labelFilter = labelFilter + " }";
// 作成したフィルタを組み合わせる
const query = afterFilter + " " + labelFilter;
Logger.log("[INFO] Gmail取得フィルタ: " + query)
return query;
}
// 取得したmessagesに含まれるメールを確認し、新着メールの配列を返す
function checkMessages(messages, lastCheckDateTime){ //(GmailMessage[], Date) -> GmailMessage[]
let newMessages = []
for (let i =0; i<messages.length ; i++){
const message = messages[i];
const updateTime = message.getDate();
Logger.log("[INFO] 確認したメールの受信日時: " + updateTime);
// メッセージ受信日時が最終チェック日時より新しいかどうか判定
if (updateTime > lastCheckDateTime) {
// 新しい場合は新着メールの配列に追加する
newMessages.push(message)
}
}
return newMessages;
}
/**
* メール検知後処理
*/
function notifyGmailsToLine(gmailMessages){ // (GmailMessage[]) -> null
// トークンを取得
const lineApiToken = getLineApiToken();
// トークンがスクリプトプロパティに設定されている確認
if ( lineApiToken == null) {
throw new Error("Line message APIのアクセストークンが設定されていません。アクセストークンをスクリプトプロパティ「" + PROPERTY_KEY_LINE_API_TOKEN + "」に設定してください。")
}
// lineApi初期化
const lineApi = new LineApi(lineApiToken);
// 送るリクエストの数を計算する(メッセージ数/1リクエストあたりのメッセージ上限 の小数点以下切り上げ)
const reqestCount = Math.ceil(gmailMessages.length / LINE_MESSAGE_LIMIT_PER_REQEST);
// 受け取ったgmailのメッセージをすべてLineに通知する
for (let i=0; i < reqestCount; i++){
// 1リクエストあたりの上限メッセージ数までメッセージを追加する
for (let j=0; j < LINE_MESSAGE_LIMIT_PER_REQEST; j++){
// 送信するメッセージの配列のインデックスを取得
const messageIndex = i * LINE_MESSAGE_LIMIT_PER_REQEST + j;
if (messageIndex >= gmailMessages.length) {
break;
}
// gmailのメッセージを取得
const gmailMessage = gmailMessages[messageIndex];
// gmailのメッセージをもとにLineのメッセージを作成する
const lineMsgBody = createLineMsgByGmailMsg(lineApi, gmailMessage);
lineApi.addTextMsgData(lineMsgBody);
}
// Lineに通知を送信し、レスポンスをログに出力する
const res = lineApi.sendBroadCastMsg();
Logger.log("[INFO] line messaging APIのレスポンス: " + res);
}
}
//gmailのメッセージをもとに、Lineに通知するメッセージを作成する
function createLineMsgByGmailMsg(lineApi, gmailMessage){ // (LineApi, GmailMessage) -> String
// lineで送信するメッセージを作成
const mailSubject = gmailMessage.getSubject();
const mailFrom = gmailMessage.getFrom();
const mailTo = gmailMessage.getTo();
const mailDate = Utilities.formatDate(gmailMessage.getDate(), 'JST', 'yyyy/MM/dd HH:mm');
let lineMsgBody = lineApi.insertEmoji(LineEmoji.mailRcv) + "新規メールを受信したよ";
lineMsgBody = lineMsgBody + "\n■日時\n " + mailDate;
lineMsgBody = lineMsgBody + "\n■件名\n " + mailSubject;
lineMsgBody = lineMsgBody + "\n■from\n " + mailFrom;
lineMsgBody = lineMsgBody + "\n■to\n " + mailTo;
return lineMsgBody
}
/**
* スクリプトプロパティ処理
*/
// LineAPIのトークンをスクリプトプロパティから取得する
function getLineApiToken(){ // () -> String
return getScriptPropertie(PROPERTY_KEY_LINE_API_TOKEN);
}
// LineAPIのトークンをスクリプトプロパティにセットする
function setLineApiToken(value){ // () -> null
return setScriptPropertie(PROPERTY_KEY_LINE_API_TOKEN, value);
}
// メールを最後にチェックした日時をスクリプトプロパティから取得する
function getLastCheckDateTime(){ // () -> DateTime|null
// スクリプトプロパティから、日時の文字列を取得する
const formattedDate = getScriptPropertie(PROPERTY_KEY_LAST_CHECK_DATETIME)
// 値がnullだったらnullを返す
if (formattedDate == null) return null;
// Date型にして返す
const lastCheckDateTime = new Date(formattedDate);
return lastCheckDateTime;
}
// メールを最後にチェックした日時をスクリプトプロパティにセットする
function setLastCheckDateTime(lastCheckDateTime){ // (DateTime) -> null
// 値はDate型で受けとり、スクリプトプロパティに入れるときは文字列にする
const formattedDate = Utilities.formatDate(lastCheckDateTime, 'JST', 'yyyy-MM-dd HH:mm:ss')
// 値をセット
setScriptPropertie(PROPERTY_KEY_LAST_CHECK_DATETIME, formattedDate);
}
// keyに合致するスクリプトプロパティを取得する
function getScriptPropertie(key){ // (String) -> String
return PropertiesService.getScriptProperties().getProperty(key);
}
// keyに合致するスクリプトプロパティに値をセットする
function setScriptPropertie(key, value){ // (String, String) -> null
PropertiesService.getScriptProperties().setProperty(key, value);
}
config.gs
※このファイルは後ほど書き換えますが、一旦そのままコピペしてください
// 設定用スクリプト
/**
* 受信監視対象にするGmailのラベルの設定
* ※Gmail側でラベルをつけるように設定する必要がある
*/
const TARGET_LABEL_LIST = [
// 受信監視対象にするラベルを以下のように追加する。
// "exampleLabel1",
// "exampleLable2"
"ここにラベルを追記する",
]
/**
* 取得するGmailのスレッドの最大件数の設定
* 少なくすると通知が漏れる可能性があるため注意。
*/
const MAX_RETRIEVE_THREADS_COUNT = 100;
/**
* Lineで通知を送るために必要な設定
* Lineでメッセージを送るための認証トークンをスクリプトプロパティに設定するための関数
* ※初期設定をするときだけ以下の手順でこの関数を実行する※
* 初期設定の手順は以下
* 1. 「ここにLineMessageAPIの〜」のところに、LineMessageAPIのアクセストークンを記載する。
* 2. 画面上部の実行ボタンを押し、実行する(ここでアクセストークンが設定される)
* 3. 記載したアクセストークンを削除し、Ctrl+sでスクリプトを保存する
*/
function configureLineApiToken() {
// 例(桁などは本物と違います)
// const LINE_API_TOKEN = "xy/abcD0efgHIjklmnOpQRsT1WXYZa3cDe/FGHijkl4abcD0efgpQRsT1WXYZa3cDe/abcD0efgHIjklmnOpQRsT1WXYZa3cDe//abcDefghIabcD0efgpQRsT1WXYZa3cDe/00/abcDefghIJ="
const LINE_API_TOKEN = "ここにLineMessageAPIのアクセストークンを記載する"
setLineApiToken(LINE_API_TOKEN);
}
lineApi.gs
/**
* 定数定義
*/
// line message apiの1リクエストあたりに送信できるメッセージ数の上限
const LINE_MESSAGE_LIMIT_PER_REQEST = 5;
// ユーザ側で絵文字のタイプを指定するときに使用する絵文字名の定数
const LineEmoji = Object.freeze({
bang: "bang",
mailRcv: "mailRcv"
})
// line messages apiに関連する定数を格納する
const LineUtil = Object.freeze({
// 絵文字のID
emojis: {
bang: {
productId: "5ac22e85040ab15980c9b44f",
emojiId: "208"
},
mailRcv: {
productId: "5ac21a18040ab15980c9b43e",
emojiId: "123"
}
}
})
class LineApi {
constructor(accessToken){ // (String) ->
this._accessToken = accessToken; // line messaging API のアクセストークン
// アクセストークンがnullの場合はエラーを吐かせる
if (this._accessToken == null) {
// TODO エラー検証
throw new Error("アクセストークンは必須です");
}
this._apiUrlBroadCast = "https://api.line.me/v2/bot/message/broadcast"; // 本番用
//this.apiUrl = "https://api.line.me/v2/bot/message/validate/broadcast"; // 検証用
this._reqestHeader = {}; //リクエストヘッダ
this._requestData = ""; //リクエストデータ(json文字列)
this._reqestOptions = {}; //urlFetchAppのoption
// テキストメッセージ用
// lineで送信するテキストメッセージを保持する
this._textMessages = [];
// テキストメッセージ内のlineの置換文字列オブジェクトを保持する
this._substitution = {};
}
// UrlFetchApp.featch実行時のオプションを作成する
_createRequestOptions(){ // () -> null
this._reqestHeader = this._createReqestHeader();
this._requestData = this._createReqestData();
this._reqestOptions = {
'method' : "post",
'headers' : this._reqestHeader,
'payload' : this._requestData,
'muteHttpExceptions': true
};
}
// リクエストヘッダを作成する
_createReqestHeader(){ // () -> ReqestHeader
// -H 'Authorization: Bearer {channel access token}'
// -H 'Content-Type: application/json'
const header = {
'Authorization': "Bearer " + this._accessToken,
'Content-Type': "application/json; charset=UTF-8"
}
return header;
}
// リクエストデータを作成する
_createReqestData(){ // () -> String
const data = {
"messages": this._textMessages,
}
// 共通で設定が必要なことがあったら追記する
return JSON.stringify(data);
}
// テキストメッセージに絵文字を追加する際に使用する
// 絵文字オブジェクトを作成し、置換文字列を返す
insertEmoji(type){// (String) -> String
// 絵文字オブジェクトを作成
const emoji = LineUtil.emojis[type]
const emojiObj = {
type: "emoji",
productId: emoji.productId,
emojiId: emoji.emojiId
}
// 置換文字列オブジェクトに絵文字を追加
this._substitution[type] = emojiObj;
// 置換文字列を作成
const substr = "{"+ type +"}";
return substr;
}
// 送信するメッセージを追加する
addTextMsgData(txtMsg){ //public (String) -> null
const textMsgData = {
'type': 'textV2',
'text': txtMsg,
'substitution': JSON.parse(JSON.stringify(this._substitution))//ディープコピーで渡す
}
// リクエストデータにメッセージを追加する
this._textMessages.push(textMsgData);
// 置換文字列オブジェクトを初期化する
this._substitution = {}
}
// broadcastメッセージを送信する
sendBroadCastMsg(){ // public (void) -> response
this._createRequestOptions();
Logger.log("[INFO] Line messaging APIへ以下のリクエストを送信します。")
Logger.log(this._reqestOptions)
const res = UrlFetchApp.fetch(this._apiUrlBroadCast, this._reqestOptions);
// テキストメッセージの配列を初期化する
this._textMessages = [];
return res;
}
}
確認ポイント
main.gs, config.gs, lineApi.gsにコードをコピーできたこと
GASの設定
この後の手順でスクリプトを実行する手順がありますが、初めて実行した時だけ承認を求められます。
以下の手順で承認してください。
> 「権限を確認」をクリック
> Googleアカウントでログイン
> 「詳細」をクリック
> 「<プロジェクト名>(安全ではないページ)に移動」をクリック
> 「すべて選択」をクリック
> 「続行」をクリックf
-
config.gsに設定を記載する
- 受信確認対象にするGmailのラベルの設定(必須)
「ここにラベルを〜」のところに、Gmail側で設定したラベルの名前を追記してくだださい。
複数指定可能することも可能です。その場合はGASの配列の書き方に従ってください。
config.gsconst TARGET_LABEL_LIST = [ // 受信監視対象にするラベルを以下のように追加する。 // "exampleLabel1", // "exampleLable2" "ここにラベルを追記する", ]
- 取得するGmailのスレッドの最大件数の設定
特にいじらなくて大丈夫です。
スクリプト実行時に取得するメールの数を指定できるようにしています。
config.gsに記載のコメントや関連するコードを読んでいただき、必要であれば変更してください。
config.gsconst MAX_RETRIEVE_THREADS_COUNT = 100;
- 受信確認対象にするGmailのラベルの設定(必須)
-
GASのスクリプトプロパティにアクセストークンを設定する(必須)
コード中のコメントに記載している手順通りに、Lipe Message APIのアクセストークンを設定してくださいconfig.gs/** * Lineで通知を送るために必要な設定 * Lineでメッセージを送るための認証トークンをスクリプトプロパティに設定するための関数 * ※初期設定をするときだけ以下の手順でこの関数を実行する※ * 初期設定の手順は以下 * 1. 「ここにLineMessageAPIの〜」のところに、LineMessageAPIのアクセストークンを記載する。 * 2. 画面上部の実行ボタンを押し、実行する(ここでアクセストークンが設定される) * 3. 記載したアクセストークンを削除し、Ctrl+sでスクリプトを保存する */ function configureLineApiToken() { // 例(桁などは本物と違います) // const LINE_API_TOKEN = "xy/abcD0efgHIjklmnOpQRsT1WXYZa3cDe/FGHijkl4abcD0efgpQRsT1WXYZa3cDe/abcD0efgHIjklmnOpQRsT1WXYZa3cDe//abcDefghIabcD0efgpQRsT1WXYZa3cDe/00/abcDefghIJ=" const LINE_API_TOKEN = "ここにLimeMessageAPIのアクセストークンを記載する" setLineApiToken(LINE_API_TOKEN); }
-
トリガーを設定する
作成したスクリプトを定期的に実行するための設定をします。
以下の順で画面移動して設定してください。
> 画面左メニューから「トリガー」をクリック
>画面右下の「トリガーを追加」をクリック
>以下の画像と同じように選択し、「保存」をクリック
-
ログを確認する
時間をおいてから、画面左メニューの「実行数」をクリックして、実行ログを確認してみてください。
main関数の1度目の実行は途中で終わる想定ですが、2度目からは受信メールを確認しているログが確認できるはずです。もしエラーログが出ている場合は、設定やファイルなどを見直してみてください。もしトリガー発火まで待てない場合は、main.gsのmain関数を手動で実行してみてください。
確認ポイント
- config.gsでスクリプトの設定ができたこと
- トリガーが追加できたこと
これですべての手順が完了しました。
Gmailでラベルをつけたメールが、Lineの公式アカウントへ通知されるようになったと思います。
最後に
ここまでお疲れ様でした。無事通知が届くようになったでしょうか。
コードを書き換えて通知するメッセージのフォーマットを変えたり、公式アカウントのアイコンを設定したりといろいろ変えてみると楽しいかもしれません。
もし不具合など見つかった場合は、コメントをお願いします。