今日つくるもの = 案件フォローアップの下書き係。
やった方がいいと分かっているのに、面倒でやらないものを、機械に用意させます。送信はしません。
下へスクロール
⚠️ 白い画面のうち、STEP 1 の「ドキュメントのコピー」だけが実際の画面写真です。 残りは押す場所が分かるように描いた図で、文言と並びは実物に合わせていますが、 Google 側の更新で細部が変わることがあります。押す場所は同じです。
やることはコピー1回だけです。ただし今日いちばん詰まるのがここなので、1画面ずつ止まって進みます。
URLを打つ必要はありません。押すのは、これ1つだけです。
この画面が出るのは、まだGoogleにログインしていない方だけです。台帳がそのまま出た方は、次へ進んでください。
Google スプレッドシートに移動
出るのはこの画面だけです。名前を決めたり、保存先を選んだりする必要はありません。押せば、あなたのマイドライブにコピーができます。
押すと、そのままあなたのコピーが開きます。(別の画面は出ません)
確かめるのは、題名の最後だけです。
元のファイルは変わりません。コピーの中は、いくら書き換えても大丈夫です。
シートは2枚。いま見えているのが「台帳」で、列は5つだけです。
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | 会社名 | 担当者名 | メールアドレス | 案件完了日 | 下書き作成日 |
| 2 | 株式会社あさひ工務店 | 田中 太郎 | jibun@example.com | 2026/07/10 | |
| 3 | 有限会社みなみ設備 | 佐藤 花子 | jibun@example.com | 2026/07/28 | |
| 4 | 株式会社北山電機 | 鈴木 一郎 | jibun@example.com | 2026/08/05 | |
| 5 | 大和建材株式会社 | 高橋 健 | jibun@example.com | 2026/09/01 | |
| 6 | 株式会社さくら商会 | 伊藤 美咲 | jibun@example.com | 2026/09/04 |
⚠️ C列は架空のアドレスのままで構いません(送信しないので事故は起きません)。ご自身のアドレスに変えると、下書きの見た目まで確かめられます。余裕のある方だけどうぞ。
題名に「のコピー」がついた台帳が、自分のドライブで開いている。それだけです。タブは閉じないでください。
20分かけて作った後に「動きません」は避けたいので、先に30秒で確かめます。
会社のアカウントでは、管理者がこの機能を止めていることがあります。いま分かれば、すぐ切り替えられます。
タブが2つ開いている(台帳と Apps Script)。ここを越えられれば、あとは詰まりません。
AIにシートの形を伝えます。説明を書くより、画面を見せるほうが速い。
Windows の方 Windows キー + Shift + S Mac の方 ⌘(コマンド)+ Shift + 4 押すと画面が少し暗くなります。そのまま、撮りたいところを マウスでドラッグして囲んでください。
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | 会社名 | 担当者名 | メールアドレス | 案件完了日 | 下書き作成日 |
| 2 | 株式会社あさひ工務店 | 田中 太郎 | jibun@example.com | 2026/07/10 | |
| 3 | 有限会社みなみ設備 | 佐藤 花子 | jibun@example.com | 2026/07/28 | |
| 4 | 株式会社北山電機 | 鈴木 一郎 | jibun@example.com | 2026/08/05 |
下のタブで「設定」を押します。ここに、できあがるメールの件名と本文が入っています。
| A | B | |
|---|---|---|
| 1 | 項目 | 内容 |
| 2 | 件名 | 【その後のご状況伺い】{{会社名}} {{担当者名}}様 |
| 3 | 本文 | {{会社名}} / {{担当者名}} 様 / いつもお世話になっております。…… |
| 4 | 経過日数 | 30 |
| 5 | ||
| 6 | 差し込みタグ | {{会社名}} / {{担当者名}} / {{案件完了日}} が台帳の各行の値に置き換わります |
文面を変えたくなったら、このシートを直すだけです。コードのほうは触らなくて済みます。
写真が2枚(台帳と設定)撮れている。どちらも下のタブが写っている。保存しなくても、次の画面にそのまま貼れます。
頼む文はたった3行。これで100行のプログラムが返ってきます。
GASで、メールの下書きを作るコードを書いてほしいです。 対象は「台帳」シートのうち、下書き作成日が空で、案件完了日から「設定」シートの経過日数がたっている行です。 件名と本文も「設定」シートのとおりに作ってください。画像は、その2つのシートです。
これだけです。専門用語も細かい指定もありません。列の名前も、文面も、日数も、全部2枚の写真から読み取ってもらいます。
Claude・ChatGPT・Gemini、どれでも構いません。
⚠️ 写真が貼れないときは、入力欄の左にあるクリップまたは「+」のボタンから、画像を選んでも同じです。
承知しました。画像から列の並びを読み取りました。以下をそのまま貼り付けてください。
コードが返ってきて、コピーのボタンを押した。うまく出なかった方は、下の「うまくいかないときは」の答えから合流できます。
STEP 2 で開いた、もうひとつのタブに戻ります。
覚える必要はありません。今日使うのは3か所だけです。
⚠️ 実物には、このほかにもボタンがあります(左端の細い列など)。今日は一度も触りません。上の3つを、書いてある文字で探してください。
すでに短いコードが入っています。残したまま貼ると動きません。
1. コードの文字の上を、どこでもいいのでクリック 2. Ctrl + A (Macは ⌘ + A) … 全部が青く選ばれます 3. Delete キー(Macは delete) … まっさらになります
⚠️ 消して大丈夫です。この4行は何もしないひな型で、台帳には影響しません。
エディタに100行ほどのコードが入っている。そして「関数の名前」が「myFunction」から変わっている。この2つです。
はじめて動かすときだけ、Googleが確認の画面を5つ出してきます。順番に越えます。
台帳のメニューからではなく、エディタから動かします。そのほうが、どんなコードでも確実だからです。
使うのは、STEP 5 で「myFunction」から名前が変わったところです。「実行」「デバッグ」の右に並んでいます。
⚠️ 「onOpen」は選ばないでください。これはメニューを足すだけの関数で、下書きは作りません。どれを選べばいいか迷ったら、手を挙げてください。
⚠️ ボタンの並びは、Googleの更新で変わることがあります。位置ではなく「保存」「実行」という文字で探してください。関数の名前はそのすぐ右です。
このプロジェクトがデータにアクセスするには、あなたの許可が必要です。
「無題のプロジェクト」に移動
ここで、ほとんどの方が手を止めます。止まって正解です。
このアプリは、Googleによる確認が済んでいません。よく知っている開発者のアプリのみご使用ください。
詳細このアプリは、Googleによる確認が済んでいません。よく知っている開発者のアプリのみご使用ください。
詳細を非表示無題のプロジェクトに以下を許可します:
動いたかどうかは、エディタの下に出る「実行ログ」で分かります。
⚠️ この2行のあいだに「◯件つくりました」などの行が出ることもあります。コードがそう書かれていた場合だけです。出なくても異常ではありません。できたかどうかは、次のGmailで確かめます。
ここが今日のゴールです。5件のうち3件だけできていれば正解 ── あとの2件はまだ30日たっていないので対象外です。
⚠️ 文面がこれと違っていても、失敗ではありません。AIが「設定」シートを使わずに自分で文章を書いた、というだけです。「件名と本文は設定シートのとおりにして」と追加で頼めば直せます。
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | 会社名 | 担当者名 | メールアドレス | 案件完了日 | 下書き作成日 |
| 2 | 株式会社あさひ工務店 | 田中 太郎 | jibun@example.com | 2026/07/10 | 2026/09/07 |
| 3 | 有限会社みなみ設備 | 佐藤 花子 | jibun@example.com | 2026/07/28 | 2026/09/07 |
| 4 | 株式会社北山電機 | 鈴木 一郎 | jibun@example.com | 2026/08/05 | 2026/09/07 |
| 5 | 大和建材株式会社 | 高橋 健 | jibun@example.com | 2026/09/01 | |
| 6 | 株式会社さくら商会 | 伊藤 美咲 | jibun@example.com | 2026/09/04 |
⚠️ E列が空のままだった方へ。AIがそこまで書かなかっただけです。「下書きを作った行は、E列に日付を入れて、二度作らないようにして」と追加で頼めば足せます。
実行ログに「実行完了」、そしてGmailの下書きが3通。ここまで来た方は、もう自分ひとりで作れます。
AIが書いたコードは、一度で動かないことがあります。そのときにやることは、たった1つです。
赤い文字を、そのままAIに渡す。
意味は分からなくていいです。文字をコピーして貼るか、写真を撮って貼るだけ。ここだけ覚えて帰れば、明日から自分ひとりで前に進めます。
さきほどのコードを実行したら、次のエラーが出ました。 直したコードを、また全部まとめて出してください。 (ここに、赤い文字をそのまま貼るか、写真を貼ってください)
あとは STEP 5 と同じ。全部消して、貼って、保存。何度やっても壊れません。
| こうなったら | こうします |
|---|---|
| 「拡張機能 → Apps Script」が開かない | 会社のアカウントで、管理者が Apps Script をオフにしています。個人のGmailアカウントで配られたリンクを開き直してください。 |
| 関数の一覧に、それらしい名前が出てこない | エディタで「保存」を押し忘れています。保存してから、「実行」の右の関数名をもう一度押してください。 |
| 実行は終わったのに、下書きが1通もできない | 正常なこともあります。「E列が空」かつ「案件完了日から30日以上」の行が無いだけです。D列の日付を1か月以上前に書き換えてもう一度実行してみてください。 |
| 承認の画面で先に進めない | ログイン中のアカウントが違います。画面右上で確かめてください。「このアプリはブロックされます」と出た場合も個人のGmailアカウントで。 |
| 「詳細」が見つからない | 画面の左下にある小さな文字です。ウィンドウが小さいと隠れるので、大きくするか下へスクロールしてください。 |
| 赤い文字(エラー)が出た | そのままコピーして、AIに貼ります。上の枠の文と一緒に送ってください。 |
| そもそもコードが出てこない/変になった | 下の「答え」をそのまま使ってください。今日は先に進むことが目的です。 |
件名と本文は、このコードの中ではなく台帳の「設定」シートにあります。文面を変えたいときは、そちらを直してください。
/**
* 案件フォローアップの下書き係
*
* 実行すると、「台帳」シートのうち
* ・E列(下書き作成日)がまだ空
* ・D列(案件完了日)から「設定」シートの経過日数がたっている
* この2つに当てはまる行について、Gmail に下書きをつくる。
* 送信はしない。送信ボタンは、最後まで人が押す。
*
* 「台帳」シート A 会社名 / B 担当者名 / C メールアドレス / D 案件完了日 / E 下書き作成日
* 「設定」シート A 項目 / B 内容(件名・本文・経過日数)
*
* ⚠️ シートは名前で指定する。getActiveSheet() だと、
* 「設定」のタブを開いたまま実行した人が、別のシートを読んでしまう。
* ⚠️ 結果はダイアログ(ui.alert)ではなく実行ログに出す。
* ダイアログは台帳のタブに出るので、エディタから実行すると
* ボタンを押すまで終わらず、エディタ側は「実行中」のまま止まって見える。
*/
const SHEET_DATA = '台帳'; // 案件のデータが入っているシート
const SHEET_CONF = '設定'; // 件名・本文・経過日数が入っているシート
const DEFAULT_DAYS = 30; // 「設定」に経過日数が無かったときに使う日数
/** シートを開いたときにメニューを足す。エディタから実行してもよい */
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('フォローアップ')
.addItem('下書きをつくる', 'createFollowUpDrafts')
.addToUi();
}
/** 条件に合う行について、Gmail の下書きをつくる */
function createFollowUpDrafts() {
const book = SpreadsheetApp.getActiveSpreadsheet();
const sheet = book.getSheetByName(SHEET_DATA);
if (!sheet) {
return report(book, '「' + SHEET_DATA + '」という名前のシートが見つかりませんでした。');
}
const conf = readSettings(book);
const lastRow = sheet.getLastRow();
if (lastRow < 2) {
return report(book, '「' + SHEET_DATA + '」にデータがありません。2行目から下に入れてください。');
}
const rows = sheet.getRange(2, 1, lastRow - 1, 5).getValues();
const today = new Date();
const made = [];
let skipped = 0;
rows.forEach(function (row, i) {
const rowNumber = i + 2;
const company = String(row[0]).trim();
const person = String(row[1]).trim();
const address = String(row[2]).trim();
const doneDate = row[3];
const draftDate = row[4];
// もう下書きをつくった行は、対象にしない
if (String(draftDate).trim() !== '') return;
// 会社名・メールアドレス・案件完了日が揃っていない行は飛ばす
if (company === '' || address === '' || !(doneDate instanceof Date)) {
skipped += 1;
return;
}
// 案件完了から何日たったかを数える(86400000 は 1日のミリ秒)
const days = Math.floor((today.getTime() - doneDate.getTime()) / 86400000);
if (days < conf.afterDays) return;
GmailApp.createDraft(
address,
fillIn(conf.subject, company, person, doneDate),
fillIn(conf.body, company, person, doneDate)
);
sheet.getRange(rowNumber, 5).setValue(today); // E列に下書き作成日を入れる
made.push(rowNumber);
});
let message;
if (made.length === 0) {
message = '今回つくるものはありませんでした。'
+ '(「下書き作成日が空」かつ「案件完了日から' + conf.afterDays + '日以上」の行がありません)';
} else {
message = made.length + ' 件の下書きをつくりました。(' + made.join('行目・') + '行目)'
+ ' Gmail の「下書き」を開いて、中身を確かめてから送信してください。';
}
if (skipped > 0) {
message += ' ※ 会社名・メールアドレス・案件完了日のどれかが空だった '
+ skipped + ' 行は飛ばしました。';
}
report(book, message);
}
/** 「設定」シートから、件名・本文・経過日数を読む */
function readSettings(book) {
// 「設定」シートが無い・空のときでも止まらないように、最低限の文面を持っておく
const fallback = {
subject: 'その後のご状況について({{会社名}} {{担当者名}}様)',
body: '{{会社名}}\n{{担当者名}} 様\n\nいつもお世話になっております。\n\n'
+ '{{案件完了日}}に完了した案件から、しばらく経ちました。\n'
+ 'その後、お困りのことなどはございませんでしょうか。',
afterDays: DEFAULT_DAYS
};
const sheet = book.getSheetByName(SHEET_CONF);
if (!sheet || sheet.getLastRow() < 2) return fallback;
// A列を項目名、B列を内容として読む
const rows = sheet.getRange(1, 1, sheet.getLastRow(), 2).getValues();
const found = {};
rows.forEach(function (row) {
found[String(row[0]).trim()] = row[1];
});
const days = Number(found['経過日数']);
return {
subject: String(found['件名'] || fallback.subject),
body: String(found['本文'] || fallback.body),
afterDays: (days > 0 ? days : DEFAULT_DAYS)
};
}
/** {{会社名}} などの差し込みタグを、その行の値に置き換える */
function fillIn(text, company, person, doneDate) {
const day = Utilities.formatDate(doneDate, Session.getScriptTimeZone(), 'yyyy年M月d日');
return String(text)
.split('{{会社名}}').join(company)
.split('{{担当者名}}').join(person === '' ? 'ご担当者' : person)
.split('{{案件完了日}}').join(day);
}
/** 結果を知らせる。エディタの実行ログと、台帳の右下に出る小さな帯の両方に出す */
function report(book, message) {
Logger.log(message);
book.toast(message, '下書き係', 8);
}
実務では「この会社は今回は送らない」があります。それを選べるようにします。
作ったものを直す ── これができれば、あとは何でも足せます。やることは、さっきと同じ「日本語で頼む」だけ。
「台帳」のタブで作業します。「設定」シートは触りません。
| A | D | E | F | |
|---|---|---|---|---|
| 1 | 会社名 | 案件完了日 | 下書き作成日 | 対象 |
| 2 | 株式会社あさひ工務店 | 2026/07/10 | 2026/09/07 | |
| 3 | 有限会社みなみ設備 | 2026/07/28 | 2026/09/07 | |
| 4 | 株式会社北山電機 | 2026/08/05 | 2026/09/07 |
作り直す必要はありません。さきほどの会話の続きに、これを送るだけ。
台帳に「対象」という列(F列)を追加して、チェックボックスを入れました。 チェックが入っている行だけを対象にするように直してください。
E列の日付を3つとも消して対象に戻し、1件だけチェックを外して実行してみてください。
| A | D | E | F | |
|---|---|---|---|---|
| 1 | 会社名 | 案件完了日 | 下書き作成日 | 対象 |
| 2 | 株式会社あさひ工務店 | 2026/07/10 | 2026/09/07 | |
| 3 | 有限会社みなみ設備 | 2026/07/28 | (作られない) | |
| 4 | 株式会社北山電機 | 2026/08/05 | 2026/09/07 |
さきほどのコードとの違いは3か所だけです(読む必要はありません)。
/**
* 案件フォローアップの下書き係(「対象」列つき)
*
* 基本の版との違いは3か所だけ(F列「対象」のチェックを見るようにした)。
*
* 実行すると、「台帳」シートのうち
* ・F列(対象)にチェックが入っている ← 足したのはここ
* ・E列(下書き作成日)がまだ空
* ・D列(案件完了日)から「設定」シートの経過日数がたっている
* この3つに当てはまる行について、Gmail に下書きをつくる。
* 送信はしない。送信ボタンは、最後まで人が押す。
*
* 「台帳」シート A 会社名 / B 担当者名 / C メールアドレス / D 案件完了日 / E 下書き作成日 / F 対象
* 「設定」シート A 項目 / B 内容(件名・本文・経過日数)
*
* ⚠️ シートは名前で指定する。getActiveSheet() だと、
* 「設定」のタブを開いたまま実行した人が、別のシートを読んでしまう。
* ⚠️ 結果はダイアログ(ui.alert)ではなく実行ログに出す。
* ダイアログは台帳のタブに出るので、エディタから実行すると
* ボタンを押すまで終わらず、エディタ側は「実行中」のまま止まって見える。
*/
const SHEET_DATA = '台帳'; // 案件のデータが入っているシート
const SHEET_CONF = '設定'; // 件名・本文・経過日数が入っているシート
const DEFAULT_DAYS = 30; // 「設定」に経過日数が無かったときに使う日数
/** シートを開いたときにメニューを足す。エディタから実行してもよい */
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('フォローアップ')
.addItem('下書きをつくる', 'createFollowUpDrafts')
.addToUi();
}
/** 条件に合う行について、Gmail の下書きをつくる */
function createFollowUpDrafts() {
const book = SpreadsheetApp.getActiveSpreadsheet();
const sheet = book.getSheetByName(SHEET_DATA);
if (!sheet) {
return report(book, '「' + SHEET_DATA + '」という名前のシートが見つかりませんでした。');
}
const conf = readSettings(book);
const lastRow = sheet.getLastRow();
if (lastRow < 2) {
return report(book, '「' + SHEET_DATA + '」にデータがありません。2行目から下に入れてください。');
}
const rows = sheet.getRange(2, 1, lastRow - 1, 6).getValues(); // ① F列まで読む
const today = new Date();
const made = [];
let skipped = 0;
rows.forEach(function (row, i) {
const rowNumber = i + 2;
const company = String(row[0]).trim();
const person = String(row[1]).trim();
const address = String(row[2]).trim();
const doneDate = row[3];
const draftDate = row[4];
const wanted = row[5]; // ② F列「対象」のチェック(入っていれば true)
// チェックが入っていない行は、条件を満たしていても対象にしない
if (wanted !== true) return; // ③ ここが足した1行
// もう下書きをつくった行は、対象にしない
if (String(draftDate).trim() !== '') return;
// 会社名・メールアドレス・案件完了日が揃っていない行は飛ばす
if (company === '' || address === '' || !(doneDate instanceof Date)) {
skipped += 1;
return;
}
// 案件完了から何日たったかを数える(86400000 は 1日のミリ秒)
const days = Math.floor((today.getTime() - doneDate.getTime()) / 86400000);
if (days < conf.afterDays) return;
GmailApp.createDraft(
address,
fillIn(conf.subject, company, person, doneDate),
fillIn(conf.body, company, person, doneDate)
);
sheet.getRange(rowNumber, 5).setValue(today); // E列に下書き作成日を入れる
made.push(rowNumber);
});
let message;
if (made.length === 0) {
message = '今回つくるものはありませんでした。'
+ '(「対象にチェック」かつ「下書き作成日が空」かつ'
+ '「案件完了日から' + conf.afterDays + '日以上」の行がありません)';
} else {
message = made.length + ' 件の下書きをつくりました。(' + made.join('行目・') + '行目)'
+ ' Gmail の「下書き」を開いて、中身を確かめてから送信してください。';
}
if (skipped > 0) {
message += ' ※ 会社名・メールアドレス・案件完了日のどれかが空だった '
+ skipped + ' 行は飛ばしました。';
}
report(book, message);
}
/** 「設定」シートから、件名・本文・経過日数を読む */
function readSettings(book) {
// 「設定」シートが無い・空のときでも止まらないように、最低限の文面を持っておく
const fallback = {
subject: 'その後のご状況について({{会社名}} {{担当者名}}様)',
body: '{{会社名}}\n{{担当者名}} 様\n\nいつもお世話になっております。\n\n'
+ '{{案件完了日}}に完了した案件から、しばらく経ちました。\n'
+ 'その後、お困りのことなどはございませんでしょうか。',
afterDays: DEFAULT_DAYS
};
const sheet = book.getSheetByName(SHEET_CONF);
if (!sheet || sheet.getLastRow() < 2) return fallback;
// A列を項目名、B列を内容として読む
const rows = sheet.getRange(1, 1, sheet.getLastRow(), 2).getValues();
const found = {};
rows.forEach(function (row) {
found[String(row[0]).trim()] = row[1];
});
const days = Number(found['経過日数']);
return {
subject: String(found['件名'] || fallback.subject),
body: String(found['本文'] || fallback.body),
afterDays: (days > 0 ? days : DEFAULT_DAYS)
};
}
/** {{会社名}} などの差し込みタグを、その行の値に置き換える */
function fillIn(text, company, person, doneDate) {
const day = Utilities.formatDate(doneDate, Session.getScriptTimeZone(), 'yyyy年M月d日');
return String(text)
.split('{{会社名}}').join(company)
.split('{{担当者名}}').join(person === '' ? 'ご担当者' : person)
.split('{{案件完了日}}').join(day);
}
/** 結果を知らせる。エディタの実行ログと、台帳の右下に出る小さな帯の両方に出す */
function report(book, message) {
Logger.log(message);
book.toast(message, '下書き係', 8);
}
「作って、動かして、直す」を全部やりました。この3つができれば、あとは日本語で頼むだけ。次のページで、何が足せるかをお話しします。