GASで予定通知メールを自動送信する方法【サンプルコード付き】 - 杉岡システム公式ブログ

GASで予定通知メールを自動送信する方法【サンプルコード付き】

自動メール送信イメージ

請求予定日の自動メール通知 GASコード

今回は、GoogleスプレッドシートGoogle Apps Script(GAS)を連携させて「取引先への請求予定日が近づくと、自動で通知メールを送信する」処理を行うコードをご紹介します。
※コピペで使用できるサンプルコード付き


以前も似たような自動メール通知コードをご紹介しましたが、今回はChat GPT-4の助けもあり、さらに改良されたものとなっています。

▼前回の「仕入予定日の自動メール通知」コードに関する記事はこちら
blog.sugiokasystem.co.jp


コード処理の流れと主な内容

今回ご紹介するGoogle Apps Script(GAS)のコード「Googleスプレッドシートのデータと連動させて請求予定日の自動メール通知を行う」処理の流れと主な内容は以下の通りです。

  1. スプレッドシートの設定
    ・GASと連携させたいスプレッドシートのIDとシート名を指定。

  2. データの取得
    ・上記で指定したシートから、すべてのデータを取得。
    ・シートのヘッダー名を取得。
    ・シートのヘッダー名を使用して、各カラム名に対応するインデックスを取得。

  3. 今日の日付の取得
    ・通知を送るかどうかの基準として、今日の日付を取得。

  4. メール送信先および送信タイミングの設定とメール件名の作成
    ・メール送信先の設定。
    ・メール送信タイミングの設定とメールの件名を作成。
      ①請求予定日を確認。
      ②今日と請求予定日との間の日数差を計算。
      ③日数差に応じてメールの件名を生成し、メール送信タイミングを設定。

  5. メール本文の作成
    ・メールの本文を作成。
    ・送信するメールの情報を配列に保存。

  6. メールの送信
    ・上記のステップで保存したメールの情報をもとに、メールを実際に送信。

  7. 日付の差分を計算する補助関数
    ・2つの日付の間の日数差を計算する関数を定義し、上記の「4. メール送信先と送信タイミングの設定」ステップで利用。


GASコード作成・編集方法

GASで使われているコードの言語は「JavaScript」に近いです。
コードの編集はGoogle Apps Scriptで直接組むことをオススメします。
デバッグ機能もあるので、操作性は高いです。
www.google.com


初期設定

GoogleスプレッドシートとGASの連携

以下の手順でスプレッドシートGoogle Apps Scriptを連携させます。

  1. 設定したいスプレッドシートを開く。
  2. スプレッドシート上メニュー「拡張機能」>「Apps Script」を選択。
  3. Google Apps Scriptのプロジェクト編集画面(コード編集画面)に遷移。
  4. プロジェクト編集画面上部の「無題のプロジェクト」部分を編集して、適当なプロジェクトタイトルを設定。

GASプロジェクト編集画面

以上で、設定したいスプレッドシートGoogle Apps Scriptの連携設定は完了です。
続いて、スプレッドシートと今回作成したGASプロジェクトの「タイムゾーン」を設定していきます。


タイムゾーンの設定

スプレッドシートとGAS、それぞれのタイムゾーンが異なっている場合、コードを組んでいく上で時間の計算に狂いが生じる可能性があります。先にタイムゾーンを統一しておきましょう。

スプレッドシートタイムゾーンを設定する

スプレッドシートの設定画面

スプレッドシート上メニュー「ファイル」 > 「設定」を選択。
「このスプレッドシートの設定」画面が開いたら、「全般」タブ内のタイムゾーンを「(GMT+09:00) Tokyo」に設定。(日本時間を使用する場合)
右下の「設定を保存」ボタンをクリックして設定を保存。


Google Apps Script(GAS)のタイムゾーンを設定する

GASプロジェクトの設定画面

  1. GAS(https://script.google.com/home)を開く。
  2. 「自分のプロジェクト」の中から設定したいプロジェクトを選択。(※プロジェクトを作成していない場合は上記「スプレッドシートとGASの連携」の手順で先にプロジェクトを作成してください。)
  3. 左メニューの「歯車マーク」を選択。
  4. 全般設定内のタイムゾーンを先程設定したスプレッドシートと同じタイムゾーンに設定。(日本時間にする場合は「(GMT+09:00) 日本標準時 - 東京」を選択。)


GASコードで「請求予定日の自動メール通知処理」を行う

各ステップごとにコードを紹介・解説していきます。
コードの変数部分を必要に応じて修正し、ご利用ください。


ステップ1.必要な定数や変数を初期化

function sendInvoiceNotifications() {
  // スプレッドシートのIDと対象のシートを指定
  const spreadsheetId = 'スプレッドシートID';
  const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName('シート名');

  // シートのデータを取得
  const data = sheet.getDataRange().getValues();

  // ヘッダー情報を取得し、カラム名からインデックスを取得する関数を定義
  const headers = data[0]; // シート1行目をヘッダーとして取得
  const getColIndex = (name) => headers.indexOf(name);

  // 現在の日付と時間を表す新しいDateオブジェクトを生成
  const today = new Date();

  // 後で送信するメールの情報を格納するための空の配列を定義
  const emailsToSend = [];

スプレッドシートIDは、対象のスプレッドシートのURLから確認できます。

docs.google.com/spreadsheets/d/スプレッドシートID/edit#gid=シートID


ステップ2.各カラムのインデックスを取得

スプレッドシートカラム名(ヘッダー名)からインデックスを取得する方式

サンプルコードでは、スプレッドシートのインデックス(データ)取得を、カラム名(ヘッダー名)に依存する方式を取っています。

  const indices = {
    CUSTOMER_NAME: getColIndex("取引先名"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名1")
    ITEM_NAME: getColIndex("品名"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名2")
    UNIT_PRICE: getColIndex("単価(税抜)"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名3")
    QUANTITY: getColIndex("数量"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名4")
    PRICE: getColIndex("小計(税抜)"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名5")
    TOTAL_AMOUNT: getColIndex("合計(税込)"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名6")
    INVOICE_DATE: getColIndex("請求予定日"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名7")
    INVOICE_NUMBER: getColIndex("請求番号"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名8")
    ORDER_DATE: getColIndex("受注日"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名9")
    ORDER_NUMBER: getColIndex("注文番号") // COLUMN_NAME: getColIndex("スプレッドシートのカラム名10")
  };

getColIndex("")内で定義したカラム名と一致するスプレッドシートカラム名のインデックスを取得します。
ここで定義したカラム名スプレッドシートカラム名が一致しない場合は、エラーが発生する場合があります。
ここで定義したカラム名スプレッドシートカラム名が一致する場合は、スプレッドシートのカラムの順序(列の並び)を変更しても正常に動作します。


スプレッドシートのカラムの順序(列の並び)からインデックスを取得する方式

カラム名に左右されず、スプレッドシートのカラムの順序(列の並び)からインデックスを取得したい場合は、以下のコードをご利用ください。
ただし、スプレッドシートのカラムの順序を変更した場合、同時にコードも修正しないと、処理が正常に行われなくなるのでご注意ください。

  // カラムの順序に基づくインデックスを定義
  const indices = {
    CUSTOMER_NAME: 0,  // 「取引先名」が1列目にあると仮定
    ITEM_NAME: 1,      // 「品名」が2列目にあると仮定
    UNIT_PRICE: 2,     // 「単価(税抜)」が3列目にあると仮定
    QUANTITY: 3,       // 「数量」が4列目にあると仮定
    PRICE: 4,          // 「小計(税抜)」が5列目にあると仮定
    TOTAL_AMOUNT: 5,   // 「合計(税込)」が6列目にあると仮定
    INVOICE_NUMBER: 6, // 「請求予定日」が7列目にあると仮定
    INVOICE_NUMBER: 7, // 「請求番号」が8列目にあると仮定
    ORDER_DATE: 8,     // 「受注日」が9列目にあると仮定
    ORDER_NUMBER: 9    // 「注文番号」が10列目にあると仮定
  };


ステップ3.メール送信先および送信タイミングの設定とメール件名の作成

  // 請求予定日に基づいて通知内容を作成
  for (let i = 1; i < data.length; i++) { // ヘッダー行(i=0)より後の行を取得
    // 現在の行(i行目)のデータを取得
    const row = data[i];

    // 請求予定日を取得
    const invoiceDate = new Date(row[indices.INVOICE_DATE]);

    // 日付が不正であれば、次の行に移動
    if (!invoiceDate) continue;

    // 現在の日付と請求予定日との差を計算
    const daysDifference = dateDiffInDays(today, invoiceDate);

    // メイン受信者とCC受信者のメールアドレスを設定
    const mainRecipients = ['YOUR_MAIN_EMAIL1', 'YOUR_MAIN_EMAIL2'];
    const ccRecipients = ['YOUR_CC_EMAIL1', 'YOUR_CC_EMAIL2']; // ccが要らない場合は削除

    // 取引先の名前を取得
    const customerName = row[indices.CUSTOMER_NAME];
    let subject;

    // 残りの日数にもとづいてメールの件名を変更し、メールを送信
    switch (daysDifference) {
      case 7: // 請求予定日まであと7日
      case 3: // 請求予定日まであと3日
      case 1: // 請求予定日まであと1日
      case 0: // 請求予定日当日
        subject = `${customerName}様への請求予定日まであと${daysDifference}日`;
        // 請求予定日が当日の場合、件名を更新
        if (daysDifference === 0) subject = `${customerName}様への請求予定日当日です`;
        break;
      default:
        // 上記のいずれのケースも該当しない場合、次の行に移動
        continue;
    }


subjectオブジェクトの補足

subjectオブジェクトで、請求予定日までの残り日数に応じてメール件名を作成し、同時にメール送信タイミングも設定します。(件名作成処理が実行される = メール送信処理実行)

subjectオブジェクトには、テンプレートリテラルを使用しています。
テンプレートリテラルは、JavaScriptのES6(ECMAScript 2015)で導入された文字列リテラルの新しい構文です。
バッククォート( ` ` )で囲まれた文字列の中に変数や式を埋め込むことができます。
これにより、文字列の結合や複数行の文字列を簡単に扱うことができます。

subject = `メール件名`;


◆注意点:

  1. テンプレートリテラルの開始と終了の記号( ` )の間の、すべてのスペースや改行は、そのまま文字列に反映されます。不要なスペースや改行を入れないように注意してください。
  2. ${}内部で式の評価エラーが発生した場合、エラーがスロー(throw)されるので、存在しない変数や不正なアクセスを避ける必要があります。


ステップ4.メール本文の作成

    // メール本文の単価、小計、合計金額を日本円形式にフォーマット
    const unitPrice = `${Number(row[indices.UNIT_PRICE]).toLocaleString('ja-JP')}円`;
    const price = `${Number(row[indices.PRICE]).toLocaleString('ja-JP')}円`;
    const formattedTotalAmount = `${Number(row[indices.TOTAL_AMOUNT]).toLocaleString('ja-JP')}円`;

    // メール本文の日付を「yyyy年m月d日」形式にフォーマット
    const options = { year: 'numeric', month: 'long', day: 'numeric' };

    // メールの本文を作成
    const body = `取引先名: ${row[indices.CUSTOMER_NAME]}
受注日: ${row[indices.ORDER_DATE].toLocaleDateString('ja-JP', options)}
注文番号: ${row[indices.ORDER_NUMBER]}

請求予定日: ${row[indices.INVOICE_DATE].toLocaleDateString('ja-JP', options)}
請求番号: ${row[indices.INVOICE_NUMBER]}

品名: ${row[indices.ITEM_NAME]}
単価(税抜): ${unitPrice}
数量: ${row[indices.QUANTITY]}

小計(税抜): ${price}
合計(税込): ${formattedTotalAmount}

請求予定表を見る: スプレッドシートURL`;


    // 送信するメールの情報を保存
    emailsToSend.push({
      to: mainRecipients.join(', '),
      cc: ccRecipients.join(', '), // ccが要らない場合は削除
      subject,
      body
    });
  }


unitPricepriceformattedTotalAmountbody オブジェクトの補足

メール本文作成用bodyオブジェクトおよび、メール本文で使用する金額の形式をフォーマットするunitPricepriceformattedTotalAmountオブジェクトには、「ステップ3」のsubjectオブジェクトと同じく、テンプレートリテラルを使用しています。

// メールの本文を作成
const body = `
メール本文
`;

テンプレートリテラル内では、改行やスペースの処理を直接文字列内で行うことができ、テンプレートリテラル内で行った改行やスペースは、実際のメール本文にそのまま反映されます。
メール内でのリンクや参考URLなどの情報も、文字列内に埋め込むことができます。

◆注意点:

  1. テンプレートリテラルの開始と終了の記号( ` )の間の、すべてのスペースや改行は、そのまま文字列に反映されます。不要なスペースや改行を入れないように注意してください。
  2. ${}内部で式の評価エラーが発生した場合、エラーがスロー(throw)されるので、存在しない変数や不正なアクセスを避ける必要があります。


optionsオブジェクトの補足

サンプルコードでは、下記の通りoptionsオブジェクトで「yyyy年m月d日」表示になるよう指定しています。

// メール本文の日付を「yyyy年m月d日」形式にフォーマット
const options = { year: 'numeric', month: 'long', day: 'numeric' };



optionsオブジェクト内で指定した形式を変更することで、いくつものパターンに変更可能です。具体例は以下の通りです。

const date = new Date(2023, 8, 8); // 2023年9月8日

// 「yy年m月dd日」表示 - Year 2-digit, Month 2-digit, Day 2-digit
const options1 = { year: '2-digit', month: '2-digit', day: '2-digit' };
console.log(date.toLocaleDateString('ja-JP', options1)); // → 23/09/08

// 「yyyy年m月d日(w)」表示 - Weekday short, Year numeric, Month long, Day numeric
const options2 = { weekday: 'short', year: 'numeric', month: 'long', day: 'numeric' };
console.log(date.toLocaleDateString('ja-JP', options2)); // → 2023年9月8日(金)



その他toLocaleDateString()メソッドに関する詳細は、下記サイトをご覧ください。
developer.mozilla.org
js.studio-kingdom.com


ステップ5.用意したメールリストを使って、メールを送信

  // メールを送信
  emailsToSend.forEach(email => {
      MailApp.sendEmail({
        to: email.to,
        cc: email.cc, // ccが要らない場合は削除
        subject: email.subject,
        body: email.body,
        name: 'Your Sender Name' // 送信者名の設定
      });
  });
}


ステップ6.日付の差を計算する補助関数

/**
 * 2つの日付間の差を日数で計算する関数
 *
 * @param {Date} a - 開始日
 * @param {Date} b - 終了日
 * @returns {number} - 日付の差分の日数
 */
function dateDiffInDays(a, b) {
  // 1日のミリ秒数を定義 (1000ミリ秒 x 60秒 x 60分 x 24時間)
  const _MS_PER_DAY = 1000 * 60 * 60 * 24;

  // 入力された日付aをUTC(協定世界時)に変換。時、分、秒は考慮せず、日付のみを基に計算
  const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());

  // 入力された日付bをUTC(協定世界時)に変換。時、分、秒は考慮せず、日付のみを基に計算
  const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());

  // 2つのUTC時間の差を計算し、ミリ秒単位での差を日単位に変換。小数点以下は切り捨て
  return Math.floor((utc2 - utc1) / _MS_PER_DAY);
}


サンプルコード全体

コードの変数部分を必要に応じて修正し、ご利用ください。

function sendInvoiceNotifications() {
  // スプレッドシートのIDと対象のシートを指定
  const spreadsheetId = 'スプレッドシートID';
  const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName('シート名');

  // シートのデータを取得
  const data = sheet.getDataRange().getValues();

  // ヘッダー情報を取得し、カラム名からインデックスを取得する関数を定義
  const headers = data[0]; // シート1行目をヘッダーとして取得
  const getColIndex = (name) => headers.indexOf(name);

  // 現在の日付と時間を表す新しいDateオブジェクトを生成
  const today = new Date();

  // 後で送信するメールの情報を格納するための空の配列を定義
  const emailsToSend = [];


  const indices = {
    CUSTOMER_NAME: getColIndex("取引先名"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名1")
    ITEM_NAME: getColIndex("品名"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名2")
    UNIT_PRICE: getColIndex("単価(税抜)"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名3")
    QUANTITY: getColIndex("数量"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名4")
    PRICE: getColIndex("小計(税抜)"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名5")
    TOTAL_AMOUNT: getColIndex("合計(税込)"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名6")
    INVOICE_DATE: getColIndex("請求予定日"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名7")
    INVOICE_NUMBER: getColIndex("請求番号"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名8")
    ORDER_DATE: getColIndex("受注日"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名9")
    ORDER_NUMBER: getColIndex("注文番号") // COLUMN_NAME: getColIndex("スプレッドシートのカラム名10")
  };

  // 請求予定日に基づいて通知内容を作成
  for (let i = 1; i < data.length; i++) { // ヘッダー行(i=0)より後の行を取得
    // 現在の行(i行目)のデータを取得
    const row = data[i];

    // 請求予定日を取得
    const invoiceDate = new Date(row[indices.INVOICE_DATE]);

    // 日付が不正であれば、次の行に移動
    if (!invoiceDate) continue;

    // 現在の日付と請求予定日との差を計算
    const daysDifference = dateDiffInDays(today, invoiceDate);

    // メイン受信者とCC受信者のメールアドレスを設定
    const mainRecipients = ['YOUR_MAIN_EMAIL1', 'YOUR_MAIN_EMAIL2'];
    const ccRecipients = ['YOUR_CC_EMAIL1', 'YOUR_CC_EMAIL2']; // ccが要らない場合は削除

    // 取引先の名前を取得
    const customerName = row[indices.CUSTOMER_NAME];
    let subject;

    // 残りの日数にもとづいてメールの件名を変更し、メールを送信
    switch (daysDifference) {
      case 7: // 請求予定日まであと7日
      case 3: // 請求予定日まであと3日
      case 1: // 請求予定日まであと1日
      case 0: // 請求予定日当日
        subject = `${customerName}様への請求予定日まであと${daysDifference}日`;
        // 請求予定日が当日の場合、件名を更新
        if (daysDifference === 0) subject = `${customerName}様への請求予定日当日です`;
        break;
      default:
        // 上記のいずれのケースも該当しない場合、次の行に移動
        continue;
    }

    // メール本文の単価、小計、合計金額を日本円形式にフォーマット
    const unitPrice = `${Number(row[indices.UNIT_PRICE]).toLocaleString('ja-JP')}円`;
    const price = `${Number(row[indices.PRICE]).toLocaleString('ja-JP')}円`;
    const formattedTotalAmount = `${Number(row[indices.TOTAL_AMOUNT]).toLocaleString('ja-JP')}円`;

    // メール本文の日付を「yyyy年m月d日」形式にフォーマット
    const options = { year: 'numeric', month: 'long', day: 'numeric' };

    // メールの本文を作成
    const body = `取引先名: ${row[indices.CUSTOMER_NAME]}
受注日: ${row[indices.ORDER_DATE].toLocaleDateString('ja-JP', options)}
注文番号: ${row[indices.ORDER_NUMBER]}

請求予定日: ${row[indices.INVOICE_DATE].toLocaleDateString('ja-JP', options)}
請求番号: ${row[indices.INVOICE_NUMBER]}

品名: ${row[indices.ITEM_NAME]}
単価(税抜): ${unitPrice}
数量: ${row[indices.QUANTITY]}

小計(税抜): ${price}
合計(税込): ${formattedTotalAmount}

請求予定表を見る: スプレッドシートURL`;


    // 送信するメールの情報を保存
    emailsToSend.push({
      to: mainRecipients.join(', '),
      cc: ccRecipients.join(', '), // ccが要らない場合は削除
      subject,
      body
    });
  }


  // メールを送信
  emailsToSend.forEach(email => {
      MailApp.sendEmail({
        to: email.to,
        cc: email.cc, // ccが要らない場合は削除
        subject: email.subject,
        body: email.body,
        name: 'Your Sender Name' // 送信者名の設定
      });
  });
}


/**
 * 2つの日付間の差を日数で計算する関数
 *
 * @param {Date} a - 開始日
 * @param {Date} b - 終了日
 * @returns {number} - 日付の差分の日数
 */
function dateDiffInDays(a, b) {
  // 1日のミリ秒数を定義 (1000ミリ秒 x 60秒 x 60分 x 24時間)
  const _MS_PER_DAY = 1000 * 60 * 60 * 24;

  // 入力された日付aをUTC(協定世界時)に変換。時、分、秒は考慮せず、日付のみを基に計算
  const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());

  // 入力された日付bをUTC(協定世界時)に変換。時、分、秒は考慮せず、日付のみを基に計算
  const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());

  // 2つのUTC時間の差を計算し、ミリ秒単位での差を日単位に変換。小数点以下は切り捨て
  return Math.floor((utc2 - utc1) / _MS_PER_DAY);
}


GASコード実行トリガーの設定

GAS編集画面の左メニュー「トリガー」を選択して、スクリプトコードを実行するタイミングを設定します。
「毎朝9時頃にコードを実行する」設定にする場合は、

  • トリガー設定画面の右下「トリガーを追加」をクリック
  • 実行する関数を選択で「sendInvoiceNotifications」(実行したいコードの関数名)を選択
  • 実行するデプロイを選択で「Head」を選択(ここで特定のバージョンを選ぶことも可能)
  • イベントのソースを選択で「時間主導型」を選択
  • 時間ベースのトリガーのタイプを選択で「日付ベースのタイマー」を選択
  • 時刻を選択で「午前8時~9時」を選択
  • 「保存」をクリック

と設定することで、「毎朝8時~9時の間にコードを実行し、コードで設定した条件に該当した時、予定通知メールが送信される」ようになります。

以上で、GASとGoogleスプレッドシートを連携させた予定日通知メールの自動送信設定の完了です。



杉岡システム株式会社

電子機器(ハード・ソフト)の開発・製造は、杉岡システムにお任せください。
自社製品「映像遅延再生装置」および「映像遅延再生メモリ」の販売も行っています。

営業時間

平日10:00 - 17:30(土日祝休み)

住所

〒573-0046
大阪府枚方市宮之下町8番2号(ABCセンター 西棟3階)
杉岡システム株式会社

連絡先

  • TEL : 072-853-3553(平日10:00 - 17:30)
  • FAX : 072-853-3577(平日10:00 - 17:30)
  • MAIL : support@sugiokasystem.co.jp
  • WEB:https://sugiokasystem.co.jp

※営業時間外にお問い合わせいただいた場合は、翌営業日以降に対応いたします。