スプレッドシートの名簿からGmailで差し込みメールを一斉送信|一人ずつ送らなくていい【基礎編】

名簿から、一斉にメール(記事04アイキャッチ) つまずき帳

同じ文面を、宛名だけ変えて、1通ずつ送る。下書きをコピーして、「佐藤さんへ」を「鈴木さんへ」に直して、本文のお願いの箇所も直して、送信。また複製して、直して、送信。

わたしはずっとそうやって送っていました。この作業でいちばん神経を使うのは、書くことではなくて「直し忘れていないかの確認」です。「佐藤さんへ」のまま鈴木さんに届いてしまう事故が怖くて、毎回、宛名と宛先を二度見してから送信ボタンを押す。全員ぶん終わるころには、文面の中身なんてもう頭に入っていません。

前回(記事03:フォーム回答を毎朝自動集計してLINEに通知)の末尾で予告した、「同じ連絡を、一人ひとり名前だけ変えて送る」あの作業。今回はこれを自動化します。ゴールはこれ。

スプレッドシートに名簿(メールアドレス・名前…)を作っておけば、テンプレの {{名前}} がその人の値に差し替わって、Gmailから1人1通ずつ送られる。誰に送れたかは、名簿に✅で残る。

例によってコードは全部AIに書かせました(この方針のいきさつは創刊号に)。わたしがやったのは、貼って、押しただけです。

完成形:受信箱に「宛名違いの3通」が同時に届く

Gmailの受信箱。「佐藤さんへ」「鈴木さんへ」「山田さんへ」と宛名の違う3通が同時刻に届いている
↑ 完成形。宛名違いの3通が同時着信(6/12 5:53)。

これは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の初回実行で表示された許可画面。求められている項目はスプレッドシートとメール送信の2つだけ
↑ 初回の許可画面。今回は項目2つだけ(トリガーを使わないぶん、記事01〜03より少ない)。

ここで大事な注意をひとつ。実行したら、Apps Scriptの画面ではなく、スプレッドシートのタブに切り替えてください。完了メッセージも、このあと出てくる確認画面も、ぜんぶシート側に表示されます(わたしはエディタを見つめたまま待ち続けて、見つけられませんでした。詳しくはつまずきの章で)。

シートに戻ると、「名簿」シートが自動でできています。宛先3行はぜんぶ自分のアドレス、名前は山田・佐藤・鈴木、「ひとこと」列つき。本物に送る前の、安全な練習台です。

自動生成されたダミー名簿。宛先3行はすべて自分のアドレスで、画面右下に完了メッセージが出ている
↑ 「ダミー名簿作成」で自動生成された練習用名簿。誰宛てに見えても、届くのは自分の受信箱だけ。

手順3:「自分にテスト送信」を実行する

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

【テスト】の件名で届いた見本メール。名簿1人目のデータで宛名と本文が差し込まれ、末尾にテストである旨の説明がある
↑ 「自分にテスト送信」の見本メール。名簿1人目(山田)のデータで差し込み済み。名簿の人には送られません。

手順4:「一斉送信」を実行する

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

スプレッドシートに表示された確認ダイアログ。「これから 3 件に本物のメールを送ります。先に『自分にテスト送信』で見本を確認しましたか?」
↑ 「一斉送信」の確認画面。毎回、これから送る件数を見せて聞いてくる安全設計。

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

結果ダイアログ「送信 3件・失敗 0件」と、名簿の送信結果列に✅と時刻が3行入った画面
↑ 結果画面「送信 3件・失敗 0件」。誰にいつ送れたかが、名簿の「送信結果」列に残る。

おまけの実験:もう一度「一斉送信」を押してみた

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行壊れていても全体は止まらず、誰に送れて誰に送れていないかがシートに残ります。アドレスを直してもう一度押せば、❌の行だけ再挑戦してくれます。

わざと壊した失敗テストの結果。名簿に❌が1行、✅が2行残り、結果ダイアログに失敗1件と対処法が表示されている
↑ わざと壊した失敗テスト。❌は1行だけで、✅の2行は無事。どこで失敗したかが名簿に残る。

過去回の教訓も、念のため再掲

コードを追記するときは、必ず関数の外(いちばん下の } のさらに後ろ)へ。コピペするのはコードブロックの中身だけ。このあたりで一度盛大にやらかした記録は、記事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

コメント

タイトルとURLをコピーしました