※本記事はプロモーションを含みます
目次
Google スプレッドシートで管理している顧客名簿や請求先一覧を使い、それぞれの宛先に向けて個別に内容を差し替えたメールを送りたい場面は日常の業務で頻繁に発生します。BCCに大量のアドレスを詰め込んで送る方法では、相手ごとに異なる案内金額や固有の管理番号を届けることができません。なので、誤送信による情報漏洩のリスクも拭えません。ただし、スプレッドシートのリストから一括でメール送信するスクリプトを組み、シート上の1行を1通の個別メールに対応させて送信する自動化を進めれば、この問題を解決できます。
ただし、単にツールからメール関数を繰り返し処理で呼び出すだけだと、実際の業務ではさまざまなズレが生じます。というのは、シート側のセルの並び順が少し変わっただけで全く違う相手に他人の請求金額が届いてしまったり、1日に送信できる上限数に達して途中で処理がエラー停止したりするからです。なので、誰に送れて誰に送れていないのかが分からなくなります。これらのトラブルを防ぐために、安定して業務連絡を届けるための実装の手順を確認しましょう。
宛先行ごとに違う本文を組むときに起きるズレと事故
スプレッドシートの行データを順に処理してメールを組み立てる際、最も危険で頻発するズレは、列の番号の想定違いと、セルの空欄による予期せぬ挙動です。開発者がスクリプトを書いた時点では、A列に氏名、B列にメールアドレス、C列に請求金額が並んでいたとします。ただし、現場の運用担当者が業務の都合でA列とB列の間に会社名や部署名を後から挿入してしまうケースは珍しくありません。なので、列の順番が変わった状態で古い前提のままデータを展開すると、メールアドレスとして会社名が渡されて即座にエラーが発生します。というのは、最悪の場合は別の列に書かれた連絡先へあべこべな内容が送信されてしまうからです。
また、テンプレート文字列を使って文面を動的に組み立てる場合、セルに値が入っていない空のセルの扱いにも注意が必要です。スプレッドシートから取得した値が空文字や null だった場合、JavaScriptのテンプレートリテラルの中ではそのまま空文字として展開されます。なので、場合によっては意図しない undefined という文字列がそのまま本文に印字されて相手に届きます。ただし、特に金額や期日といった重要な連絡事項で、ご請求金額はundefined円です、といった文言が送信されてしまうと、受け取った側の不信感は計り知れません。
さらに見落としがちなのが、スプレッドシートの表示形式と、スクリプトが取得する生のデータの食い違いです。シート上では 10,000 とカンマ区切りで表示されていても、スクリプト側で普通にセルの値を取り出すと、数値の 10000 として扱われます。なので、これをそのまま差し込むと、桁区切りのない読みづらい案内文になってしまいます。ただし、日付に至っては、シート上では 2025/04/01 と見えていても、GAS側ではJavaScriptの Date オブジェクトとして取得されます。というのは、そのまま文字列に結合すると標準時の長い英語表記に化けてしまい、読めないメールが出来上がってしまうからです。なので、行ごとに固有のデータを流し込む処理では、こうした型や並びのズレをあらかじめ吸収する配慮を確認しましょう。
リストから値を読み取って1行ずつGmailAppを呼び出す
こうした個別送信の基本となるのは、スプレッドシートのデータ範囲を一括で二次元配列として取得し、ヘッダー行を除外した上で1行ずつ宛先と本文を組み立てて GmailApp.sendEmail を実行する流れです。以下のコードは、アクティブなシートの全データを読み込み、2行目以降のデータに対して宛先アドレスが存在する場合にのみパーソナライズされたメールを送信する最小限の構成です。
const sheet = SpreadsheetApp.getActiveSheet(); // アクティブなシートを取得します。データを取り出すために使用します。自由に変更可能です。データ範囲を自動計算します。全データを配列にします。2行目以降のデータを繰り返し処理します。名前とメールと金額に分解します。メールがあれば送信します。メールアドレスです。件名です。本文を動的に作成します。1行ずつ送信されます。配列の長さ分だけ処理が続きます。データが尽きると終了します。エラーハンドリングは含みません。最小限の構成です。動作確認用に使えます。標準的な書き方です。効率的に処理できます。API制限に注意します。1日の上限があります。無料版は100通までです。Workspaceは1500通までです。自動化に役立ちます。手動作業を減らせます。エラーを防ぎます。設定を確認しましょう。送信前にテストしましょう。安全に運用します。実務で活用できます。作業時間を短縮できます。便利に活用しましょう。
このコードをいつ使うかというと、締め日ごとに確定した請求リストや、イベントの参加者名簿に対して、一人ひとり異なる金額や整理番号を割り振って連絡を一斉に通達したい局面です。ただし、手作業で1件ずつ新規メール画面を開いて宛先を貼り付け、本文の金額を書き換えて送信ボタンを押す作業を数十件繰り返している現場なら、この短い処理だけで作業時間を大幅に短縮できます。なので、作業の効率化のためにこのコードを活用しましょう。
ただし、このコードには実際の現場で動かした際にずれる点がいくつか存在します。というのは、先頭行のヘッダーを data.slice(1) で切り捨てていますが、シート上で1行目が空行だったり、2行にわたる複合ヘッダーになっていたりすると、データ行の開始位置がずれてヘッダーの文字列そのものに対してメールを送ろうとしてしまうからです。なので、分割代入を行っている [name, email, amount] の部分も、シートの左から1列目、2列目、3列目に固定されている前提であるため、列の追加や並び替えに対して非常に弱いです。
これを回避するためには、ヘッダー行のテキスト(氏名やメールアドレスや請求金額など)をスクリプト側で調べ、それぞれの列が何番目にあるのかを動的に特定してから値を取り出す設計にしましょう。なので、送信前に必ず指定のフォーマットになっているかを確認するチェック処理を挟む必要があります。ただし、金額部分についても、あらかじめスクリプト内でロケールに応じた数値の表示形式(toLocaleStringなど)を適用してからテンプレート文字列に埋め込むことで、人間にとって読みやすい形式を保つことができます。なので、送信内容の表示形式を確認しましょう。
送信制限クォータと途中で落ちたときの二重送信問題
スプレッドシートのリスト宛にメールを一括送信するツールを導入する上で、絶対に避けて通れない制約がGoogle側の送信制限(Quotas)です。Google Apps Script経由でのメール送信には1日あたりの上限数が厳格に定められており、通常の無料Googleアカウント(@gmail.com)では1日あたり100通、Google Workspaceのアカウントであっても1日あたり1500通までとなっています。ただし、この制限は24時間のローリングウィンドウで管理されており、上限を超えて送信メソッドを呼び出すと、スクリプトは例外(Service invoked too many times)を出して即座に強制終了します。なので、送信通数の上限を確認しましょう。
ここで極めて深刻な問題となるのが、途中停止による二重送信です。たとえば300件の宛先リストに対して無料アカウントから一括送信スクリプトを走らせたとします。ただし、スクリプトは快調にメールを送り続けますが、100通目を送った瞬間にGoogleの制限に引っかかり、101行目の処理でエラーを出してスクリプト全体がクラッシュします。というのは、プログラムにはどこまで送ったかを記録する仕組みがないため、エラーメッセージを見た担当者が何か失敗したと考えてスプレッドシートを開き、原因をよく理解しないままスクリプトを再実行してしまう事故が後を絶たないからです。なので、エラー時の対応手順を確認しましょう。
再実行されると、スクリプトは再びリストの先頭行から順に処理をやり直します。なので、先ほど送られた1〜100件目の宛先に対して、全く同じ案内メールがもう一度送信されてしまいます。ただし、その日はすでに送信枠を使い切っているため、1件目の再送を試みた時点で再び即座にエラーになります。というのは、結果として一部の顧客には同じメールが2通届き、それ以降の200人の顧客には1通も届かないという最悪の状態に陥るからです。
この問題を回避するためには、送信処理を実行する前に MailApp.getRemainingDailyQuota() を呼び出して本日の残り送信可能数を確認しましょう。なので、リストの件数が残枠を超えている場合は最初から処理を開始しない処理を設けることが基本となります。ただし、後述するように1通送信するごとにスプレッドシート側へ送信完了の目印をリアルタイムに書き戻し、次回実行時には目印が立っていない行だけを対象にするという再開可能なステータス設計が必須となります。
空白行や壊れたアドレスが混ざった時の安全設計
実務で管理されているスプレッドシートの名簿は、常に綺麗なデータで満たされているとは限りません。というのは、担当者が行を削除したつもりで値だけを消して空白行がリストの途中に残っていたり、アドレスの末尾に余計な全角スペースや改行が混入していたり、あからさまに @ が含まれていない不正な文字列が入力されていたりするからです。なので、普通のスクリプトでは、こうした壊れたデータにぶつかった瞬間にエラーが発生し、後続の正しい宛先への送信まで巻き添えで中断してしまいます。
提示したシンプルなコードでは、if(email) という条件式によって、アドレス列が空である行をスキップする最低限のガードは入っています。ただし、これだけでは不十分です。というのは、セルに全角スペースが1文字だけ入っていた場合、JavaScriptとしては正しい値と判定されるため、ifブロックの中に入ってしまい、GmailApp.sendEmail に全角スペースを渡して無効な受信者アドレスというエラーでスクリプトが止まるからです。なので、入力データの形式を確認しましょう。
このような事態を防ぐには、メールアドレス文字列の前後から空白を取り除く trim 処理を施した上で、正規表現による簡易的な形式チェックを行いましょう。ただし、厳密な規格に準拠した複雑な正規表現まで持ち出す必要はありません。というのは、少なくとも前後に不要な空白がないことや、アットマークが含まれておりドメイン部分が存在することを確認するだけでも、予期せぬエラー落ちの大部分を未然に防ぐことができるからです。
さらに、1件の送信失敗がリスト全体の処理を道連れにしないよう、繰り返し処理の内部を try-catch 構文で囲みましょう。なので、ある特定の1行でアドレス形式エラーや送信エラーが起きたとしても、catchブロックでそのエラー内容をログやシートに記録しつつ、繰り返し処理自体は継続して次の行の処理に進むように実装します。ただし、こうすることで300件中1件のデータミスがあったとしても、残りの299件には無事にメールを届け切ることができ、業務全体の停止を防ぐことが可能になります。
実務で運用し続けるための送信ログ記録とステータス管理
スプレッドシートを元のデータとしてメール送信を業務化する場合、スクリプトの役割は単にメールを送ることだけでは完結しません。というのは、誰に、いつ、どのメールを送ったのかという実績をスプレッドシート上に書き戻すところまでが、業務システムとしての責任だからです。なので、送信状態を管理する専用の列(たとえば送信ステータスや送信日時列)をあらかじめリストの右端に設けておきましょう。
スクリプトが1行分のメール送信に成功したら、その行のステータス列に送信済という文字列と現在の日時を書き込みます。なので、次回の実行時には送信ステータスが空欄の行だけを抽出して送信処理を行うように条件分岐を組み立てます。ただし、この仕組みが整っていれば、前述の送信制限に達して処理が途中で落ちたとしても、翌日送信枠が回復したタイミングで同じスクリプトを再実行するだけで、未送信の行から自動的に処理を再開できます。というのは、担当者が手作業でどこまで送ったかを探し出して行を削除したり、スクリプト側の開始行番号を書き換えたりする危険な作業は一切不要になるからです。
同じシート操作の続きは、GASのメール列の形式がおかしい行を、見つける に書いてあります。

