GAS(Google Apps Script)でフォームの自動返信メールを送るには、「フォーム送信時」のインストール型トリガーで関数を動かし、引数のイベントオブジェクト(e)から回答を取り出して MailApp.sendEmail で送ります。 トリガーはフォームにもスプレッドシートにも付けられ、どちらに付けたかで e の中身が変わります。

この記事では、2つのトリガーで受け取れる値の早見表、両方の方式の自動返信コード、管理者への通知、回答内容による文面の出し分け、try-catch での記録、FormApp での質問の一括作成、送信数の上限と権限をまとめます。メソッド名・引数・上限の数値は、Google の Apps Script 公式リファレンスを開いて照合したものだけを載せています。

確認環境:Google Apps Script 公式リファレンス・ガイド(developers.google.com/apps-script、英語版)と Google ドキュメント エディタ ヘルプを 2026-10-05 に照合。コードは Node.js 22 で構文を検査し、GAS のサービス(MailApp など)を置き換える簡易スタブを使って分岐の動きを確かめました。

この記事の結論

  • 回答者に回答の控えを返すだけなら、フォームの設定で足ります(「メールアドレスを収集する」と「回答のコピーを回答者に送信」)。
  • 文面を回答内容で変える・管理者にも知らせる・シートに結果を残すなら、GAS のフォーム送信時トリガーを使います。
  • シートに記録も残したいならスプレッドシートに付けたトリガー(e.namedValues)、フォームだけで完結させたいならフォームに付けたトリガー(e.response)を選びます。
  • 回答は必ず e から取り出します。 送信ごとにその回答が e に入って渡されるので、シートの最終行を読みにいく必要がありません。
  • 1日に送れる宛先の数には上限があります(無料の Google アカウントは100、Google Workspace は1,500)。送る前に MailApp.getRemainingDailyQuota() で残りを確かめます。

onFormSubmitの早見表:トリガーを付ける場所で e が変わる

比べる点 スプレッドシートに付けたトリガー フォームに付けたトリガー
スクリプトの置き場所 回答先のスプレッドシートに紐づくスクリプト フォームに紐づくスクリプト
回答の入り方 e.namedValues:質問のタイトルをキー、回答の配列を値にしたオブジェクト e.response:送信された回答を表す FormResponse
ほかに受け取れる値 e.values:回答の値を並び順で入れた配列/e.range:回答が書き込まれたセル範囲(Range)/e.authMode、e.triggerUid e.source:スクリプトが紐づくフォーム(Form)/e.triggerUid
1つの回答の取り出し方 e.namedValues['お名前'][0] e.response.getItemResponses() を回して getItem().getTitle() と getResponse()
回答の行に結果を書く e.range.getSheet() と e.range.getRow() で同じ行に書ける シートが無いので、実行ログやメールで知らせる
送信日時 シートに書き込まれた値を読む e.response.getTimestamp()
回答の編集用URL ― e.response.getEditResponseUrl()
コードで作るとき ScriptApp.newTrigger('関数名').forSpreadsheet(ss).onFormSubmit().create() ScriptApp.newTrigger('関数名').forForm(form).onFormSubmit().create()
向く使い方 回答シートに送信結果や担当者欄を足していく 回答先のシートを作らずに、フォームだけで返信する

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「GASでGoogleフォーム自動返信メール|onFormSubmit早見表」

フォームの設定とGASのどちらを使うか

条件 選ぶもの
回答の控えをそのまま回答者に送れば足りる フォームの設定「回答のコピーを回答者に送信」
新しい回答が来たことを自分が知りたいだけ フォームの「新しい回答についてのメール通知を受け取る」
回答内容に応じて案内文を変えたい GAS
件名・差出人名・返信先を決めたい GAS
回答者と管理者に別々の文面を送りたい GAS
回答の行に「送信済み」などの結果を残したい GAS(スプレッドシートに付けたトリガー)

フォームの設定では、「メールアドレスを収集する」に「確認済み」「回答者からの入力」、「回答のコピーを回答者に送信」に「リクエストされた場合」「常に表示」という選択肢があります(Google ドキュメント エディタ ヘルプの表記)。

例にするフォームと共通の関数

架空の「社内勉強会 参加申込フォーム」を例にします。質問は次の5つです。

質問のタイトル 種類 必須 選択肢
メールアドレス 記述式(1行) はい ―
お名前 記述式(1行) はい ―
参加形式 ラジオボタン はい 会場、オンライン
参加する回 プルダウン はい 第1回(10月20日)、第2回(11月17日)
質問・連絡事項 段落 いいえ ―

メールアドレスは質問として置いています。こうすると、どちらの方式でも「メールアドレス」というタイトルで同じように取り出せます。フォームの設定でメールアドレスを収集している場合、フォームに付けたトリガーでは e.response.getRespondentEmail() でも取得できます(このメソッドは、メールアドレスの収集を有効にしているフォームで使えます)。

まず、2つの方式で共通に使う「文面を作る」「2通送る」関数です。回答を「質問のタイトル → 回答」のオブジェクトにそろえてから渡す形にしておくと、方式を変えても文面の部分はそのまま使えます。

// ===== 設定(自分の環境に合わせて書き換える)=====
const ADMIN_EMAIL = 'admin@example.com';        // 管理者の通知先
const SENDER_NAME = '勉強会事務局';              // 差出人として表示する名前
const EVENT_NAME = '社内勉強会';

// 質問のタイトル(フォームの質問文と一字一句そろえる)
const Q = {
  email: 'メールアドレス',
  name: 'お名前',
  style: '参加形式',
  session: '参加する回',
  note: '質問・連絡事項',
};

/** 回答内容から、回答者あての件名と本文を作る */
function buildReply_(answers) {
  const lines = [
    answers[Q.name] + ' 様',
    '',
    EVENT_NAME + 'へのお申し込みを受け付けました。',
    '',
    '■ 参加する回:' + answers[Q.session],
    '■ 参加形式:' + answers[Q.style],
    '',
  ];
  // 回答内容による文面の出し分け
  if (answers[Q.style] === '会場') {
    lines.push('当日は開始10分前までに、3階の会議室Aへお越しください。');
  } else {
    lines.push('接続用のURLは、開催日の前日までにこのメールアドレスへお送りします。');
  }
  if (answers[Q.note]) {
    lines.push('', 'ご記入いただいた内容は、担当者が確認してあらためてご連絡します。');
  }
  lines.push('', '― ' + SENDER_NAME);
  return {
    subject: '【受付完了】' + EVENT_NAME + '(' + answers[Q.session] + ')',
    body: lines.join('\n'),
  };
}

/** 管理者あての件名と本文を作る */
function buildAdminNotice_(answers) {
  const needsReply = Boolean(answers[Q.note]);
  const body = Object.keys(answers)
    .map(function (title) { return title + ':' + answers[title]; })
    .join('\n');
  return {
    subject: (needsReply ? '【要返信】' : '【申込】') + answers[Q.name] + ' 様(' + answers[Q.session] + ')',
    body: body,
  };
}

/** 回答者と管理者の2通を送る。残りの送信数が足りなければ送らずに理由を返す */
function sendBoth_(answers) {
  if (!answers[Q.email]) {
    return 'メールアドレスが空のため保留';
  }
  if (MailApp.getRemainingDailyQuota() < 2) {
    return '本日の送信数の上限のため保留';
  }
  const reply = buildReply_(answers);
  MailApp.sendEmail(answers[Q.email], reply.subject, reply.body, {
    name: SENDER_NAME,
    replyTo: ADMIN_EMAIL,
  });
  const notice = buildAdminNotice_(answers);
  MailApp.sendEmail(ADMIN_EMAIL, notice.subject, notice.body);
  return '送信済み';
}
関数 役割
buildReply_ 回答者あての件名と本文を作る。参加形式が「会場」か「オンライン」かで案内文を変え、質問欄に記入があれば一文を足す
buildAdminNotice_ 管理者あての件名と本文を作る。質問欄に記入があれば件名を【要返信】にする
sendBoth_ 宛先が空でないこと、残りの送信数が2件以上あることを確かめてから2通送り、結果を文字列で返す

MailApp.sendEmail(recipient, subject, body, options) の options では、差出人の表示名(name)と返信先(replyTo)を指定しています。回答者がそのまま返信すると、管理者のアドレスに届きます。

方式A:スプレッドシートに付けたトリガーで自動返信する

フォームの回答先になっているスプレッドシートのスクリプトに、共通の関数と一緒に貼り付けます。

/**
 * 方式A:スプレッドシートに付けたフォーム送信時トリガーで動く関数
 * e.namedValues … { 質問のタイトル: [回答] } の形
 * e.values      … 回答の値を並び順で入れた配列
 * e.range       … 回答が書き込まれたセル範囲
 */
function onFormSubmitSheet(e) {
  const sheet = e.range.getSheet();
  const row = e.range.getRow();
  const statusColumn = e.range.getLastColumn() + 1;   // 回答の右隣の列に結果を書く

  try {
    // namedValues の値は配列なので、先頭の要素を取り出して普通の文字列にする
    const answers = {};
    Object.keys(e.namedValues).forEach(function (title) {
      answers[title] = e.namedValues[title][0];
    });

    const result = sendBoth_(answers);
    sheet.getRange(row, statusColumn).setValue(result);
  } catch (err) {
    // 止まった理由を同じ行に残し、実行ログにも出す
    sheet.getRange(row, statusColumn).setValue('エラー:' + err.message);
    console.error('onFormSubmitSheet row=' + row + ' ' + err.stack);
  }
}

/** 方式Aのトリガーを作る(スクリプトエディタで1回だけ実行する) */
function createSheetTrigger() {
  const exists = ScriptApp.getProjectTriggers().some(function (t) {
    return t.getHandlerFunction() === 'onFormSubmitSheet';
  });
  if (exists) {
    console.log('トリガーは作成済みです。');
    return;
  }
  ScriptApp.newTrigger('onFormSubmitSheet')
    .forSpreadsheet(SpreadsheetApp.getActive())
    .onFormSubmit()
    .create();
}

ポイントは3つです。

  • e.namedValues の値は配列です。 e.namedValues['お名前'] は ['山田 太郎'] のような配列なので、[0] を付けて取り出します。コードでは最初にまとめて文字列へ詰め替えています。
  • 結果は e.range と同じ行に書きます。 e.range.getRow() が今回の回答の行番号なので、同時に複数の送信があっても、それぞれの行に自分の結果が残ります。
  • try-catch で囲み、止まった理由を同じ行に残します。 あとからシートを見れば、どの回答に返信できていないかが一目で分かります。

トリガーの作り方

コードの createSheetTrigger をスクリプトエディタで1回実行すると、トリガーが作られます。同じ関数名のトリガーがすでにあれば作らないようにしてあるので、2回実行しても二重に送られません。

画面から作る場合は、公式ガイドの手順どおり、スクリプトエディタの左側で「トリガー」を開き、右下の「トリガーを追加」から、実行する関数に onFormSubmitSheet、イベントの種類に「フォーム送信時」を選んで保存します。

初回は承認の画面が表示されます。Apps Script はコードを調べて必要な権限を割り出し、足りない権限があると許可を求めます。

方式B:フォームに付けたトリガーで自動返信する

フォームに紐づくスクリプトに、共通の関数と一緒に貼り付けます。回答先のスプレッドシートが無くても動きます。

/**
 * 方式B:フォームに付けたフォーム送信時トリガーで動く関数
 * e.response … 送信された回答(FormResponse)
 * e.source   … スクリプトが紐づいているフォーム(Form)
 */
function onFormSubmitForm(e) {
  try {
    // 質問のタイトルをキーにしたオブジェクトへ詰め替える
    const answers = {};
    e.response.getItemResponses().forEach(function (itemResponse) {
      const value = itemResponse.getResponse();
      answers[itemResponse.getItem().getTitle()] =
        Array.isArray(value) ? value.join('、') : value;
    });

    const result = sendBoth_(answers);
    console.log(e.response.getId() + ' ' + result);
  } catch (err) {
    // フォーム側には書き込み先のシートが無いので、実行ログと管理者あてメールで知らせる
    console.error('onFormSubmitForm ' + err.stack);
    MailApp.sendEmail(
      ADMIN_EMAIL,
      '【要確認】自動返信が止まりました',
      '回答ID:' + e.response.getId() + '\n' +
      '送信日時:' + e.response.getTimestamp() + '\n' +
      '内容:' + err.message
    );
  }
}

/** 方式Bのトリガーを作る(スクリプトエディタで1回だけ実行する) */
function createFormTrigger() {
  const exists = ScriptApp.getProjectTriggers().some(function (t) {
    return t.getHandlerFunction() === 'onFormSubmitForm';
  });
  if (exists) {
    console.log('トリガーは作成済みです。');
    return;
  }
  ScriptApp.newTrigger('onFormSubmitForm')
    .forForm(FormApp.getActiveForm())
    .onFormSubmit()
    .create();
}
  • e.response.getItemResponses() は、フォームでの並び順どおりに1問ずつの回答(ItemResponse)を返します。 getItem().getTitle() が質問のタイトル、getResponse() が回答です。
  • getResponse() の型は質問の種類で変わります。 多くの質問は文字列、チェックボックスは文字列の配列です。コードでは配列なら「、」でつないで1つの文字列にしています。
  • フォーム側には結果を書く行が無いので、止まったときは実行ログに残し、管理者にもメールで知らせます。ログはスクリプトエディタの「実行数」で確認できます。

スタブで分岐を確かめた結果

GAS そのものは手元で動かせないため、MailApp やシートを置き換える簡易スタブを Node.js 22 で用意し、上のコードをそのまま読み込んで呼び出しました。Google のサーバーでメールを送った結果ではなく、分岐と文面の組み立てを確かめたものです。

渡した回答 回答の行に書かれた結果 送った通数 管理者あての件名
会場・質問欄は空 送信済み 2 【申込】山田 太郎 様(第1回(10月20日))
オンライン・質問欄に記入あり 送信済み 2 【要返信】山田 太郎 様(第1回(10月20日))
残りの送信数が1件 本日の送信数の上限のため保留 0 ―
メールアドレスが空 メールアドレスが空のため保留 0 ―
送信時に例外(Invalid email)を起こす エラー:Invalid email 0 ―

「会場・質問欄は空」のときに組み立てられた、回答者あての件名と本文です。

【受付完了】社内勉強会(第1回(10月20日))
山田 太郎 様

社内勉強会へのお申し込みを受け付けました。

■ 参加する回:第1回(10月20日)
■ 参加形式:会場

当日は開始10分前までに、3階の会議室Aへお越しください。

― 勉強会事務局

方式Bでも、同じ回答で2通が組み立てられること、例外が起きたときに管理者あての「【要確認】自動返信が止まりました」が1通だけ作られることを確かめました。トリガー作成の関数は、2回続けて呼んでも1つしか作られませんでした。

FormAppで質問項目を表から一括作成する

質問が多いフォームや、毎回似たフォームを作る場合は、スプレッドシートの表からまとめて作れます。「質問定義」という名前のシートに、次の表を用意します。

種類 質問のタイトル 必須 選択肢 補足の説明
テキスト メールアドレス TRUE 確認メールの送り先
テキスト お名前 TRUE
ラジオ 参加形式 TRUE 会場、オンライン
プルダウン 参加する回 TRUE 第1回(10月20日)、第2回(11月17日)
段落 質問・連絡事項 FALSE

シートの A1 に貼り付けられるタブ区切り版です。

種類  質問のタイトル 必須  選択肢 補足の説明
テキスト    メールアドレス TRUE        確認メールの送り先
テキスト    お名前 TRUE        
ラジオ 参加形式    TRUE    会場、オンライン    
プルダウン   参加する回   TRUE    第1回(10月20日)、第2回(11月17日) 
段落  質問・連絡事項 FALSE       
/**
 * 「質問定義」シートの表からフォームを一括作成する
 * A列:種類(テキスト/段落/ラジオ/プルダウン)
 * B列:質問のタイトル C列:必須(TRUE/FALSE)
 * D列:選択肢(読点「、」区切り) E列:補足の説明
 */
function createFormFromSheet() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const rows = ss.getSheetByName('質問定義').getDataRange().getValues();
  rows.shift();                                   // 1行目(見出し)を除く

  const form = FormApp.create('社内勉強会 参加申込フォーム');
  form.setDescription('参加する回と形式を選んで送信してください。');
  form.setConfirmationMessage('お申し込みを受け付けました。確認のメールをお送りします。');

  rows.forEach(function (row, index) {
    const type = row[0];
    const title = row[1];
    const required = row[2] === true;
    const choices = String(row[3]).split('、').filter(function (c) { return c !== ''; });
    const help = row[4];

    let item;
    if (type === 'テキスト') {
      item = form.addTextItem();
    } else if (type === '段落') {
      item = form.addParagraphTextItem();
    } else if (type === 'ラジオ') {
      item = form.addMultipleChoiceItem().setChoiceValues(choices);
    } else if (type === 'プルダウン') {
      item = form.addListItem().setChoiceValues(choices);
    } else {
      throw new Error((index + 2) + '行目:種類「' + type + '」には対応していません');
    }
    item.setTitle(title).setRequired(required);
    if (help) {
      item.setHelpText(help);
    }
  });

  // 回答の保存先を、このスプレッドシートにする
  form.setDestination(FormApp.DestinationType.SPREADSHEET, ss.getId());

  console.log('回答用URL:' + form.getPublishedUrl());
  console.log('編集用URL:' + form.getEditUrl());
}

スタブで呼び出したところ、表の5行から、テキスト2問・ラジオ1問・プルダウン1問・段落1問が表の順に作られ、必須と選択肢も表どおりに設定されました。

このコードで使っている FormApp まわりのメソッドです。

メソッド 戻り値 役割
FormApp.create(title) Form 新しいフォームを作る。create(title, isPublished) で公開の状態を指定することもできる
FormApp.getActiveForm() Form スクリプトが紐づいているフォームを返す
FormApp.openById(id) Form ID を指定して既存のフォームを開く
form.addTextItem() TextItem 1行のテキストを入力する質問を足す
form.addParagraphTextItem() ParagraphTextItem 複数行のテキストを入力する質問を足す
form.addMultipleChoiceItem() MultipleChoiceItem ラジオボタンで1つ選ぶ質問を足す
form.addListItem() ListItem プルダウンで1つ選ぶ質問を足す
item.setTitle(title) 同じ種類の項目 質問のタイトルを決める
item.setRequired(enabled) 同じ種類の項目 必須かどうかを決める
item.setHelpText(text) 同じ種類の項目 補足の説明を付ける
item.setChoiceValues(values) 同じ種類の項目 選択肢を文字列の配列で決める(ラジオボタン、プルダウン)
form.setDescription(description) Form フォームの説明文を決める
form.setConfirmationMessage(message) Form 送信後に表示するメッセージを決める
form.setCollectEmail(collect) Form 回答者のメールアドレスを収集するかどうかを決める
form.setDestination(type, id) Form 回答の保存先を決める。FormApp.DestinationType.SPREADSHEET とスプレッドシートの ID を渡す
form.getPublishedUrl() String 回答用のURLを返す
form.getEditUrl() String 編集用のURLを返す

回答(FormResponse)と1問ずつの回答(ItemResponse)で使えるメソッドです。

メソッド 戻り値 役割
response.getItemResponses() ItemResponse[] すべての回答を、フォームでの並び順で返す
response.getRespondentEmail() String 回答者のメールアドレス(メールアドレスの収集が有効なフォーム)
response.getTimestamp() Date 送信日時
response.getId() String 回答の ID
response.getEditResponseUrl() String 送信済みの回答を編集するためのURL
response.getResponseForItem(item) ItemResponse 指定した質問への回答
itemResponse.getItem() Item 回答した質問。getTitle() でタイトル、getType() で種類が分かる
itemResponse.getResponse() Object 回答。多くは文字列、チェックボックスは文字列の配列、グリッドは行ごとの配列

MailAppとGmailAppの違い

自動返信のように「送るだけ」の用途には MailApp が合います。

比べる点 MailApp GmailApp
できること メールの送信 送信に加えて、受信トレイのスレッドの取得・検索、ラベルの操作、下書きの作成など
受信トレイへのアクセス しない する
必要な権限(スコープ) https://www.googleapis.com/auth/script.send_mail https://mail.google.com/
送信のメソッド sendEmail(recipient, subject, body, options) ほか sendEmail(recipient, subject, body, options)
残りの送信数の取得 getRemainingDailyQuota() ―
主なオプション name、replyTo、cc、bcc、htmlBody、attachments、inlineImages、noReply from、name、replyTo、cc、bcc、htmlBody、attachments、noReply
選ぶ場面 自動返信・通知 エイリアスのアドレスを差出人にしたい、送ったあとにラベルを付けたい

公式リファレンスは MailApp について、Gmail の受信トレイにアクセスできない送信専用のサービスで、GmailApp を使うスクリプトより再承認を求められる頻度が低い、と説明しています。noReply は Google Workspace のアカウントで使えるオプションです。

1日の送信数の上限と必要な権限

上限(割り当て)

Apps Script の割り当てのページに載っている値です。

項目 無料の Google アカウント Google Workspace
1日にメールを送れる宛先の数 100 / 日 1,500 / 日
1通あたりの宛先の数 50 / 通 50 / 通
メール本文の大きさ 200 KB / 通 400 KB / 通
添付ファイルの数 250 / 通 250 / 通
添付ファイルの合計サイズ 25 MB / 通 25 MB / 通
トリガーの数 20 / ユーザー / スクリプト 20 / ユーザー / スクリプト
トリガーの合計実行時間 90 分 / 日 6 時間 / 日
1回の実行時間 6 分 / 実行 6 分 / 実行
ユーザーあたりの同時実行数 30 30
  • 割り当てはユーザーごとで、最初のリクエストから24時間後にリセットされると書かれています。
  • 数えるのは「宛先」の数です。 この記事のコードは1回の送信で回答者と管理者の2件を使うので、無料のアカウントでは1日50回答(100 ÷ 2)、Google Workspace では750回答(1,500 ÷ 2)が目安になります。ほかのスクリプトでもメールを送っていれば、その分も同じ枠から減ります。
  • MailApp.getRemainingDailyQuota() は、その日の残りで送れる宛先の数を返します。 コードでは2件に満たないときは送らず、回答の行に「本日の送信数の上限のため保留」と残しています。

権限と実行するアカウント

項目 内容
誰の権限で動くか インストール型トリガーは、常に作成した人のアカウントで動く。メールもそのアカウントから送られる
承認 トリガーは作成したユーザーの承認が必要。コードを変えて新しい権限が要るようになったら、スクリプトエディタで手動実行して承認する
権限の割り出し Apps Script がコードを調べ、使っているサービスから必要な権限を割り出す
権限を明示する マニフェスト(appsscript.json)の oauthScopes に書く
ほかの人から見えるか あるアカウントが作ったトリガーは、ほかのアカウントからは見えない
トリガーが動かない場面 スクリプトの実行や API リクエストでは、原則としてトリガーは動かない
トリガーを扱うメソッドの権限 https://www.googleapis.com/auth/script.scriptapp

つまずきやすい点と対処

症状 原因 対処
e.namedValues['お名前'] をそのまま本文に入れると期待と違う 値は配列で渡される [0] を付けて取り出す
値が undefined になる 質問のタイトルとコードのキーが一字でも違う フォームの質問文をコピーしてコードに貼る。空白の全角・半角もそろえる
e.response が undefined スプレッドシートに付けたトリガーで動いている e.namedValues を使う。または、フォームのスクリプトにトリガーを作り直す
e.namedValues が undefined フォームに付けたトリガーで動いている e.response.getItemResponses() を使う
エディタの実行ボタンで動かすと e が空で止まる フォームの送信で呼ばれたときだけ e が渡される フォームからテスト回答を送って確かめる
1回の回答で2通届く 同じ関数のトリガーが2つある 「トリガー」の画面で1つにする。コードで作るときは getProjectTriggers() で有無を確かめる
途中から送られなくなった 1日の宛先数の上限に達した getRemainingDailyQuota() で残りを確かめ、保留にした回答を翌日に送る
コードを変えたら動かなくなった 新しい権限の承認が済んでいない スクリプトエディタで関数を手動実行して承認する

確かめて分かったこと

  1. フォームに付けたトリガーの e には、namedValues も values も range もありません。 公式のイベントオブジェクトの表では、フォーム側は response・source・triggerUid、シート側は authMode・namedValues・range・triggerUid・values と、項目がきれいに分かれています。
  2. getResponse() の戻り値は文字列とは限りません。 チェックボックスは配列、グリッドは行ごとの配列です。本文に入れる前に配列かどうかを見て、文字列にそろえる処理を入れました。
  3. 上限は「通数」ではなく「宛先の数」で数えます。 回答者と管理者に1通ずつ送ると、1回答で2件です。スタブで残りを1件にすると、コードは2通とも送らずに保留の記録を残しました。
  4. トリガーは作った人のアカウントで動きます。 回答者が誰であっても、メールの差出人はトリガーを作ったアカウントです。共有のフォームでは、誰のアカウントで作るかを先に決めておくと安心です。
  5. forSpreadsheet も forForm も、公式の例ではオブジェクトを渡しています。 SpreadsheetApp.getActive() や FormApp.getActiveForm() の戻り値をそのまま渡す形にそろえました。
  6. MailApp と GmailApp では求められる権限の広さが違います。 送信だけなら MailApp の script.send_mail で足ります。

よくある質問

Q. GASなしでGoogleフォームの自動返信はできる? A. 回答の控えを返すだけならできます。フォームの設定でメールアドレスを収集し、「回答のコピーを回答者に送信」を有効にします。文面を自分で決めたい場合は GAS を使います。

Q. e.namedValuesとe.valuesの違いは? A. e.namedValues は質問のタイトルで取り出すオブジェクト、e.values は並び順で取り出す配列です。質問の順番を入れ替えても影響を受けにくいのは e.namedValues です。

Q. onFormSubmitが動かないときは? A. まず「トリガー」の画面に、その関数のフォーム送信時トリガーがあるかを確かめます。あれば「実行数」でエラーの内容を見て、権限の承認とイベントオブジェクトの種類(フォーム側かシート側か)を見直します。

Q. 自動返信メールの差出人は変えられる? A. 表示名は name オプションで変えられます。差出人のアドレスはトリガーを作ったアカウントになり、返信先だけを変えたいときは replyTo を指定します。

Q. 同時に複数の回答が来ても大丈夫? A. はい、e にはその送信の回答が入って渡されるので、回答どうしが混ざりません。シートに結果を書くときも、e.range.getRow() の行に書けば自分の行だけを更新できます。

まとめ

  • 控えを返すだけならフォームの設定、文面を変える・通知する・記録するなら GAS のフォーム送信時トリガー。
  • トリガーを付ける場所で e が変わる。シート側は e.namedValues・e.values・e.range、フォーム側は e.response・e.source。
  • 回答は「タイトル → 回答」のオブジェクトにそろえてから文面を作ると、方式を変えても同じ関数が使える。
  • try-catch で囲み、結果を回答の行(または実行ログと管理者メール)に残す。
  • 送る前に getRemainingDailyQuota()。上限は宛先の数で、無料のアカウントは100、Google Workspace は1,500。
  • 送るだけなら MailApp。必要な権限は script.send_mail で、受信トレイにはアクセスしない。
  • 質問の多いフォームは、FormApp で表から一括作成できる。

参考・出典

  • Google Apps Script「Event Objects」 https://developers.google.com/apps-script/guides/triggers/events
  • Google Apps Script「Installable Triggers」 https://developers.google.com/apps-script/guides/triggers/installable
  • Google Apps Script「Quotas for Google Services」 https://developers.google.com/apps-script/guides/services/quotas
  • Google Apps Script「Authorization for Google Services」 https://developers.google.com/apps-script/guides/services/authorization
  • Google Apps Script「Logging」 https://developers.google.com/apps-script/guides/logging
  • Google Apps Script リファレンス「MailApp」 https://developers.google.com/apps-script/reference/mail/mail-app
  • Google Apps Script リファレンス「GmailApp」 https://developers.google.com/apps-script/reference/gmail/gmail-app
  • Google Apps Script リファレンス「FormApp」「Form」「FormResponse」「ItemResponse」「Item」「TextItem」「ParagraphTextItem」「MultipleChoiceItem」「ListItem」 https://developers.google.com/apps-script/reference/forms/form-app
  • Google Apps Script リファレンス「ScriptApp」「Trigger」「FormTriggerBuilder」「SpreadsheetTriggerBuilder」 https://developers.google.com/apps-script/reference/script/script-app
  • Google Apps Script リファレンス「Spreadsheet」「Sheet」「Range」 https://developers.google.com/apps-script/reference/spreadsheet/range
  • Google ドキュメント エディタ ヘルプ「フォームの回答を表示、管理する」 https://support.google.com/docs/answer/139706?hl=ja

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「GASでGoogleフォーム自動返信メール|onFormSubmit早見表」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?