同じ文面を、宛名だけ変えて、1通ずつ送る。下書きをコピーして、「佐藤さんへ」を「鈴木さんへ」に直して、本文のお願いの箇所も直して、送信。また複製して、直して、送信。
わたしはずっとそうやって送っていました。この作業でいちばん神経を使うのは、書くことではなくて「直し忘れていないかの確認」です。「佐藤さんへ」のまま鈴木さんに届いてしまう事故が怖くて、毎回、宛名と宛先を二度見してから送信ボタンを押す。全員ぶん終わるころには、文面の中身なんてもう頭に入っていません。
前回(記事03:フォーム回答を毎朝自動集計してLINEに通知)の末尾で予告した、「同じ連絡を、一人ひとり名前だけ変えて送る」あの作業。今回はこれを自動化します。ゴールはこれ。
スプレッドシートに名簿(メールアドレス・名前…)を作っておけば、テンプレの {{名前}} がその人の値に差し替わって、Gmailから1人1通ずつ送られる。誰に送れたかは、名簿に✅で残る。
例によってコードは全部AIに書かせました(この方針のいきさつは創刊号に)。わたしがやったのは、貼って、押しただけです。
完成形:受信箱に「宛名違いの3通」が同時に届く

これは6/12の朝5:53の、わたしの受信箱です。「佐藤さんへ」「鈴木さんへ」「山田さんへ」——宛名違いの3通が同時に届いています。宛名だけではなく、本文のお願いの箇所も、山田さんには資料A、佐藤さんには資料B、鈴木さんには会場係と、それぞれ差し込み済み。
そして種明かしをすると、この3通の宛先はぜんぶわたし自身のアドレスです。今回のコードには「宛先がぜんぶ自分のダミー名簿を作る」練習モードが入っていて、本物の相手に送る前に、ここまで安全に試せます。この記事では、その「練習→テスト→本番」の流れごと紹介します。
作戦会議:差し込みメール=宛名と本文が「その人専用」になる一斉送信
仕組みはシンプルです。名簿の1行目に見出し(メールアドレス・名前・ひとこと…)を並べておき、メールのテンプレに {{名前}} のように二重カッコで書く。送信のとき、その部分が名簿の各行の値に置き換わって、1人1通ずつ送られます。いわゆる「差し込みメール」です。
BCC一斉送信と何が違うのか
BCCは「全員に同じ1通」を隠して送る方法。一人ひとりの宛名にはできません。差し込みは、宛名も本文もその人専用になったうえで、誰に送れたかの記録が名簿に残ります。「〇〇さんへ」と呼びかけたい連絡、人ごとに一部だけ違う連絡は、こちらの出番です。
正直ポイント:Gmailには1日の送信上限がある
先に正直に書いておきます。Gmailには1日の送信上限があり、個人のGmailで約100通、Google Workspace(会社や学校で契約する有料版のGoogle)で約1500通です。今回のコードは送る前に残数を自動確認して、名簿が上限を超える場合は「今日の上限まで送って停止」。翌日もう一度「一斉送信」を押せば、続きから送ってくれます。
速さについても正直に。わたしの実測では3件で数秒でしたが、1通ずつ送る仕組みなので、件数が増えればそのぶん時間はかかります。
なお今回は、記事02:フォーム回答をLINEに即時通知や記事03で使ってきたトリガー(自動実行の予約)を使いません。ボタンを押したときだけ動きます。そのぶん、初回の許可画面で求められる項目も少なめです(次の章で実物をお見せします)。
手順:貼って、練習して、テストして、送る
手順1:スプレッドシートからApps Scriptを開いて、コードを貼る
スプレッドシートを用意します(新規で大丈夫です)。メニューの「拡張機能」→「Apps Script」。開き方の基礎は記事01にあります。もとから入っているコードを全部消して、コードを丸ごと貼り付けます。コードはこの少し下、「貼り付けるコード(完全版)」の章にあります。貼ったら保存します(Ctrl+S / ⌘S。フロッピーのアイコンでもOK)。保存しないと次の手順の関数がメニューに出てこないので、ここは飛ばさないでください。
手順2:「ダミー名簿作成」を実行する(初回の許可画面はここ)
画面上部、「実行」「デバッグ」ボタンの並びにある関数名のメニューで「ダミー名簿作成」を選び、「実行」を押します。わたしの場合、コードを貼ってここを実行したのが5:51でした。初回は許可画面が出ます。今回求められたのは2項目だけ——スプレッドシートの操作と、あなた本人に代わってのメール送信です。進み方は記事01〜03とまったく同じ「すべて選択→続行」。

ここで大事な注意をひとつ。実行したら、Apps Scriptの画面ではなく、スプレッドシートのタブに切り替えてください。完了メッセージも、このあと出てくる確認画面も、ぜんぶシート側に表示されます(わたしはエディタを見つめたまま待ち続けて、見つけられませんでした。詳しくはつまずきの章で)。
シートに戻ると、「名簿」シートが自動でできています。宛先3行はぜんぶ自分のアドレス、名前は山田・佐藤・鈴木、「ひとこと」列つき。本物に送る前の、安全な練習台です。

手順3:「自分にテスト送信」を実行する
関数を「自分にテスト送信」に切り替えて実行。受信箱に【テスト】と付いたメールが1通届きます。わたしのときは5:52に届きました。名簿の1人目(山田)のデータで宛名と本文が差し込まれた見本で、末尾に「これはテストです」の説明つき。仕上がりをここで確認します。

手順4:「一斉送信」を実行する
関数を「一斉送信」に切り替えて実行。すると、スプレッドシート側にこの確認画面が出ます。

「これから 3 件に本物のメールを送ります。先に『自分にテスト送信』で見本を確認しましたか?」——毎回、件数を見せてから聞いてくる仕掛けです。「はい」を押すと、結果画面「送信 3件・失敗 0件」。名簿の「送信結果」列には「✅ 6/12 05:53」が3行並び(この「送信結果」列は、初回の「一斉送信」のときにコードが自動で作ります)、受信箱には冒頭でお見せした宛名違いの3通が届いていました。

おまけの実験:もう一度「一斉送信」を押してみた
5:58、わざともう一度「一斉送信」を実行してみました。結果は「新しく送る行はありませんでした(すべて✅送信済みです)。」——うっかり2回押しても、同じ人に二重送信されません。

本物の名簿に差し替えるとき
「名簿」シートの宛先を本物のアドレスに書き換え、行を人数分増やすだけです。列は自由に増やせます(見出しと同じ {{列名}} をテンプレに書けば差し込まれます)。ただし、1行目の見出しのうち「メールアドレス」だけは、この文字のまま残してください。そして、大事な相手に送る前は、必ずもう一度「自分にテスト送信」を実行してください。コード側も確認画面で毎回聞いてきますが、見本を自分の目で見るのがいちばん確実です。ちなみに「ダミー名簿作成」は、すでにデータの入った名簿シートがあると何もせずに止まります(大事な名簿を上書きしないため)。
貼り付けるコード(完全版)
あなたがさわるのは、いちばん上の設定3つだけです。
- 件名テンプレ:メールの件名。{{名前}} のように二重カッコで囲んだ部分が、名簿の同じ見出しの列の値に置き換わります
- 本文テンプレ:メールの本文。同じく {{列名}} が使えます。最初と最後の
`“(バッククォート)は消さないでください(詳しくは次の章) - 名簿シート名:名簿のシート名。「ダミー名簿作成」を使えば自動で作られるので、最初はそのままでOK
{{列名}} は、名簿の1行目の見出しと同じ文字にしてください。見出しに無い {{〇〇}} は置き換わらずそのまま残るので、テスト送信の見本を見れば、打ち間違いにすぐ気づけます。関数は上から「ダミー名簿作成」→「自分にテスト送信」→「一斉送信」の順に並べてあります。つまり、上から順に実行すればいいようになっています。
// === 設定(ここだけ変えればOK)===
const 件名テンプレ = '{{名前}}さんへ:〇〇のご案内';
const 本文テンプレ = `{{名前}}さん
こんにちは。〇〇です。
(ここに本文を書きます。二重カッコで {{名前}} のように囲んだ部分が、名簿の同じ見出しの列の値に置き換わります。例:{{ひとこと}})
どうぞよろしくお願いします。`; // ←最初と最後の ` の記号は消さないでください
const 名簿シート名 = '名簿'; // 名簿のシート名(「ダミー名簿作成」を使えば自動で作られます)
// お試し用:宛先がぜんぶ自分の“安全なダミー名簿”を作る(すでにある名簿は消しません)
function ダミー名簿作成() {
const ss = SpreadsheetApp.getActive();
const me = myEmail();
let sheet = ss.getSheetByName(名簿シート名);
if (sheet && sheet.getLastRow() > 0) {
throw new Error('シート「' + 名簿シート名 + '」にはすでにデータがあります。大事な名簿を消さないため、何もしませんでした。練習し直したい場合は、いまのシートの名前を変えてから実行してください。');
}
if (!sheet) sheet = ss.insertSheet(名簿シート名);
sheet.getRange(1, 1, 4, 3).setValues([
['メールアドレス', '名前', 'ひとこと'],
[me, '山田', '資料Aの担当をお願いします'],
[me, '佐藤', '資料Bの担当をお願いします'],
[me, '鈴木', '当日は会場係をお願いします'],
]);
ss.toast('ダミー名簿を作りました。宛先はすべて自分のアドレスなので、安全に試せます。');
}
// まず実行:1人目の内容を“自分宛て”に送って仕上がりを確認(名簿の人には送られません)
function 自分にテスト送信() {
const sheet = SpreadsheetApp.getActive().getSheetByName(名簿シート名);
if (!sheet) throw new Error('シート「' + 名簿シート名 + '」が見つかりません。先に「ダミー名簿作成」を実行するか、設定のシート名を確認してください。');
if (MailApp.getRemainingDailyQuota() < 1) throw new Error('今日のメール送信上限に達しています。明日また試してください。');
const data = sheet.getDataRange().getDisplayValues();
const headers = data[0].map(function(h){ return h.trim(); });
if (headers.indexOf('メールアドレス') < 0) throw new Error('名簿の1行目に「メールアドレス」という見出しの列が必要です。');
const row = data[1];
if (!row) throw new Error('名簿の2行目に1人分のデータを入れてください。');
MailApp.sendEmail(myEmail(),
'【テスト】' + fillTemplate(件名テンプレ, headers, row),
fillTemplate(本文テンプレ, headers, row) + '\n\n----\nこれはテストです。名簿1人目のデータで作った見本を、あなた自身に送りました。');
SpreadsheetApp.getActive().toast('テストを ' + myEmail() + ' に送りました。受信箱を確認してください。');
}
// 本番:一人ひとり宛名を差し替えて送る(実行すると、スプレッドシート側に確認画面が出ます)
function 一斉送信() {
const ss = SpreadsheetApp.getActive();
const sheet = ss.getSheetByName(名簿シート名);
if (!sheet) throw new Error('シート「' + 名簿シート名 + '」が見つかりません。先に「ダミー名簿作成」を実行するか、設定のシート名を確認してください。');
const data = sheet.getDataRange().getDisplayValues();
const headers = data[0].map(function(h){ return h.trim(); });
const mailCol = headers.indexOf('メールアドレス');
if (mailCol < 0) throw new Error('名簿の1行目に「メールアドレス」という見出しの列が必要です。');
let 結果Col = headers.indexOf('送信結果');
if (結果Col < 0) { 結果Col = headers.length; sheet.getRange(1, 結果Col + 1).setValue('送信結果'); }
// 送る行=アドレスがあって、まだ ✅ が付いていない行(❌の行は再挑戦します)
const 対象 = [];
for (let i = 1; i < data.length; i++) {
const r = data[i];
if (String(r[mailCol] || '').trim() && !String(r[結果Col] || '').startsWith('✅')) 対象.push(i);
}
if (対象.length === 0) {
try {
const ui = SpreadsheetApp.getUi();
ui.alert('一斉送信', '新しく送る行はありませんでした(すべて✅送信済みです)。', ui.ButtonSet.OK);
} catch (e) { ss.toast('新しく送る行はありませんでした(すべて送信済みです)。'); }
return;
}
const 残り = MailApp.getRemainingDailyQuota();
if (残り < 1) throw new Error('今日のメール送信上限に達しています。明日「一斉送信」を実行すると、続きから送れます。');
let ui;
try { ui = SpreadsheetApp.getUi(); }
catch (e) { throw new Error('確認画面を出せませんでした。スプレッドシート本体を開いた状態で、もう一度実行してください。'); }
const 今回送る = Math.min(対象.length, 残り);
const ans = ui.alert('一斉送信の確認',
'これから ' + 今回送る + ' 件に本物のメールを送ります。' +
(対象.length > 残り ? '(未送信' + 対象.length + '件のうち、今日送れる上限までの' + 残り + '件。残りは明日、続きから送れます)' : '') +
'\n\n先に「自分にテスト送信」で見本を確認しましたか?',
ui.ButtonSet.YES_NO);
if (ans !== ui.Button.YES) { ss.toast('送信を中止しました(1通も送っていません)。'); return; }
let sent = 0, failed = 0;
for (const i of 対象) {
if (sent + failed >= 今回送る) break;
const row = data[i];
try {
MailApp.sendEmail(String(row[mailCol]).trim(),
fillTemplate(件名テンプレ, headers, row),
fillTemplate(本文テンプレ, headers, row));
sheet.getRange(i + 1, 結果Col + 1).setValue('✅ ' + Utilities.formatDate(new Date(), 'Asia/Tokyo', 'M/d HH:mm'));
sent++;
} catch (e) {
sheet.getRange(i + 1, 結果Col + 1).setValue('❌ 送れませんでした(アドレスを確認)');
failed++;
}
SpreadsheetApp.flush(); // 1通ごとに記録を確定=途中で止まっても二重送信しない
}
const 未了 = 対象.length - sent - failed;
ui.alert('一斉送信の結果',
'送信 ' + sent + '件・失敗 ' + failed + '件' +
(未了 > 0 ? '・今日の上限により残り' + 未了 + '件(明日「一斉送信」で続きから送れます)' : '') +
(failed > 0 ? '\n❌の行のアドレスを直して、もう一度「一斉送信」を実行してください(✅の行には送られません)。' : ''),
ui.ButtonSet.OK);
}
// ── ここから下は内部部品(実行メニューから選ぶのは上の3つだけ)──
function fillTemplate(template, headers, row) {
return template.replace(/\{\{(.+?)\}\}/g, function(m, name) {
const key = name.trim();
const i = key ? headers.indexOf(key) : -1;
return i >= 0 ? row[i] : m; // 見つからない {{列名}} はそのまま残す=タイポに気づける
});
}
function myEmail() {
const me = Session.getActiveUser().getEmail() || Session.getEffectiveUser().getEmail();
if (!me) throw new Error('あなたのメールアドレスを自動で取得できませんでした。Googleアカウントにログインし直してから、もう一度実行してください。');
return me;
}
つまずき先回り:わたしが固まったところ+わざと失敗させた実録
つまずき1:実行結果が「どこにも出ない」ように見える
6/12の朝の、わたしの実体験です。「ダミー名簿作成」を実行したあと、Apps Scriptのエディタ画面で完了の表示をじっと待っていました。何も出ません。正解は、メッセージも確認画面も、スプレッドシート本体の画面に出るでした。手順にも書きましたが、もう一度だけ。実行したら、シートのタブに切り替えてください。
つまずき2:「送信結果」列は消さない
✅は「この人にはもう送った」という印で、二重送信を防ぐ仕組みそのものです。きれいにしようとして列ごと消して実行すると、同じ人にもう一度送られます。見た目が気になっても、そっとしておいてください。
つまずき3:本文テンプレの「`」を消さない
本文テンプレの最初と最後にある ` `(バッククォート)は、コードの一部です。消すとエラーで動きません。また、本文の中では ` と ${` の2つの記号は使えません。普通の日本語の連絡文ならまず登場しない記号なので、「この2つだけは使わない」とだけ覚えておけば大丈夫です。
実録:わざとアドレスを壊して送ってみた
失敗したらどうなるかも、自分で確かめておきました。6:00、名簿1行目のアドレスをわざと「abc」に書き換え、✅を消して「一斉送信」。確認画面は「これから 1 件に…」(✅の付いた2行は、ちゃんと対象から外れています)。結果画面には「送信 0件・失敗 1件」、その下に「❌の行のアドレスを直して、もう一度『一斉送信』を実行してください(✅の行には送られません)。」と続きました。
名簿は❌1行+✅2行。1行壊れていても全体は止まらず、誰に送れて誰に送れていないかがシートに残ります。アドレスを直してもう一度押せば、❌の行だけ再挑戦してくれます。

過去回の教訓も、念のため再掲
コードを追記するときは、必ず関数の外(いちばん下の } のさらに後ろ)へ。コピペするのはコードブロックの中身だけ。このあたりで一度盛大にやらかした記録は、記事03の事故集にあります。
まとめ:「名前だけ変えて、もう1通」が消えた
「下書きを複製して、名前を直して、二度見して、送信」のループが、「名簿を整えて、テストして、一斉送信を押す」に変わりました。宛名の直し忘れにおびえる必要はもうありません——機械は直し忘れないからです。誰に送れたかも、自分の記憶ではなく名簿の✅が覚えていてくれます。
実測は3件で数秒。件数が増えればそのぶん時間はかかりますが、確認画面が毎回件数を見せてくれて、失敗しても❌で残って続きから送り直せる。「速い」より「事故らない」が、この仕組みのいちばんの価値だと思います。正直にいうと、送信前に二度見する癖だけは、しばらく残りそうですが——もう、直すところはないんですけどね。
次回予告
次回は、毎朝「今日の予定」が勝手にLINEに届く、自分専用の秘書を作ります。予定表をAIに読ませて、毎朝・前夜・週1で通知してもらう仕組みです:これまでの自動化を“合体”させたら、自分専用のAI秘書になった
それでは、定時で帰りましょう。
このシリーズのこれまで:創刊号(入口)/記事01:フォーム回答をメールで受け取る/記事02:フォーム回答をLINEに即時通知/記事03:フォーム回答を毎朝自動集計してLINEに通知/記事04:名簿から差し込みメールを一斉送信(この記事)/記事05:長いGmailをAIで3行+やることリストに/記事06:毎朝、未読メールを自動で要約して1通に/記事07:たまった自由記述を、AIでテーマ別に分類・集計/記事08:登録キーワードのニュースを、毎朝LINEに/応用編その1:AI秘書「ぽけっと手帳」(全体像)/応用編その2:リサーチ秘書(追いたい分野のニュースが毎朝1通)/育てる編①:ためた見出しに"質問"できる(台帳Q&A)/育てる編②:「医療も追って」で集める分野を増減(設定変更)/育てる編③:「今週どうだった?」でためた台帳を週次まとめ/育てる編④:「地図にして」でためた見出しを放射状の地図に(見出しマップ)/育てる編⑤:「再エネ政策を追いたい」で検索語まで含む設定をAIが設計(設定アシスタント・完結)/応用編その3:受発注・請求 秘書(受注が来たら、選ぶだけで見積書・請求書のPDFに)/受発注 育てる編①:入金消込・督促・領収書PDF/受発注 育てる編②:受注した瞬間に在庫を押さえる/受発注 育てる編③:発注点アラートと発注書PDF/受発注 育てる編④:月次集計・売上グラフ・会計CSV(最終回)/基礎編:URLひとつで“自分の申込ページ”を作る(Webアプリ)/基礎編:申し込まれた瞬間に、確認メールが自動で返る/基礎編:申し込んだら、控えのPDFが自動で届く/基礎編:申込の控えが、勝手にドライブへ積み上がっていく/基礎編:申込のひとことを、届いた瞬間にAIが仕分ける/基礎編:誰も画面を開いていないのに、毎朝まとめが届く/基礎編:申込が来た瞬間、ポケットのLINEが鳴る(最終回)/応用編その4:申込・予約 秘書(満席の瞬間、ページが自分から閉まる)/申込・予約 育てる編①:キャンセル待ちと自動繰り上げ/申込・予約 育てる編②:承認制とお断り下書き/申込・予約 育てる編③:前日リマインダーと毎朝まとめ/申込・予約 育てる編④:管理ページ(最終回)
#GAS #Gmail #差し込みメール #コピペOK



コメント