Google Formsは、jane@gmial.comのような入力ミスのアドレスも、使い捨てドメインのアドレスも、2年前に消滅した会社のアドレスも、そのまま受け付けます。標準の「有効なメールアドレス」設定が確認するのは、文字列がアドレスの形をしているかどうかだけです。実在するメールボックスがあるかどうかは確認しません。Google Formsのメールアドレスを実際に検証するには、もう1段階の手順として、メールシステムに対する本物のチェックが必要です。回答が届いた瞬間に行う方法と、あとからまとめて行う方法があります。このガイドでは、Google Apps Scriptを間に挟んで両方を紹介します。
結論:2つの方法#
判定をリアルタイムで受け取りたいか、まとめて処理してよいかによって、方法を選びます。
- リアルタイム:Apps Scriptに
onFormSubmitトリガーを追加します。送信されたアドレスを読み取り、UrlFetchAppでQualisend APIを呼び出し、判定を連携先のGoogleスプレッドシートに書き戻します。新しい回答は届くたびにスコアが付きます。 - 一括:回答はすでにスプレッドシートにあるため、CSVとしてダウンロードし、1回の一括検証にかけます。長い間回答を集めてきたフォームや、コードに触れたくない場合に向いています。
どちらも同じ判定を使います。判定はdeliverable、risky、undeliverable、unknownの4種類で、使い捨て、役割アドレス、フリーメールのサブフラグが付きます。違いはタイミングだけです。
方法1:Apps ScriptでGoogle Formsのメールアドレスをリアルタイムに検証する#
Google Forms自体は外部APIを呼び出せません。しかし、回答が流れ込むスプレッドシートならApps Script経由で呼び出せます。手順はシンプルです。送信のたびに起動し、メールアドレスを取り出してQualisendに問い合わせ、結果を該当行の隣に書き込みます。
スクリプトを準備します。 回答を集めているGoogleスプレッドシートを開きます(まだ連携していない場合は、フォームエディターで Responses → Link to Sheets を選びます)。スプレッドシートから Extensions → Apps Script を開きます。最初にAPIキーを保存し、コードに直接書かないようにします。スクリプトエディターで Project Settings → Script Properties を開き、QUALISEND_API_KEYにキーの値を追加します。スクリプトは実行時にPropertiesServiceでこの値を読み取ります。
続いて、次のようなハンドラーを貼り付けます。このハンドラーはPOST https://app.qualisend.com/api/v1/verifyを呼び出します。リクエストとレスポンスの詳細は、APIリファレンスに記載されています。
// Bound to the responses spreadsheet.
// Runs on each new Form submission (see trigger setup below).
const QUALISEND_URL = 'https://app.qualisend.com/api/v1/verify';
const EMAIL_QUESTION = 'Email address'; // your exact question title
const STATUS_COL = 8; // first of 3 empty columns: verdict, score, pending job id
function onFormSubmit(e) {
const email = (e.namedValues[EMAIL_QUESTION] || [])[0];
if (!email) return;
const apiKey = PropertiesService.getScriptProperties().getProperty('QUALISEND_API_KEY');
const res = UrlFetchApp.fetch(QUALISEND_URL, {
method: 'post',
contentType: 'application/json',
headers: { Authorization: 'Bearer ' + apiKey },
payload: JSON.stringify({ email }),
muteHttpExceptions: true, // handle errors ourselves instead of throwing
});
// Fail open: if the check errors, keep the response — never lose a real lead.
if (res.getResponseCode() !== 200) return;
const { job_id, result } = JSON.parse(res.getContentText());
const row = e.range.getRow();
const sheet = e.range.getSheet();
sheet.getRange(row, STATUS_COL).setValue(result.status); // deliverable | risky | undeliverable | unknown
sheet.getRange(row, STATUS_COL + 1).setValue(result.score); // 0-100
// SMTP probe still running: keep the job id so finishPending() can fetch the final verdict.
if (result.smtp === 'pending') sheet.getRange(row, STATUS_COL + 2).setValue(job_id);
}
フォーム送信時に渡されるeオブジェクトが重要です。e.namedValuesは各質問のタイトルを回答に対応付け、e.rangeは書き込まれたばかりの行を指します。そのため、e.range.getRow()で結果を書き込む行が正確にわかります。EMAIL_QUESTIONは質問のタイトルと完全に一致させてください。STATUS_COLは、空の3列(判定、スコア、保留中のジョブID)の先頭列に合わせます。
トリガーを追加します。 シンプルトリガー(onFormSubmitという名前だけの関数)ではUrlFetchAppを呼び出せません。外部サービスへのアクセスには承認が必要ですが、シンプルトリガーにはその承認がないためです。代わりにインストール可能なトリガーを使います。スクリプトエディターで Triggers(時計のアイコン)→ Add Trigger を開き、次のように選択します。
- Function:
onFormSubmit - Event source: From spreadsheet
- Event type: On form submit
保存して、表示される承認画面で一度だけ許可すれば、準備は完了です。テスト用の回答を送信し、指定した列にステータスとスコアが表示されることを確認してください。
タイミングに関する注意点です。 Qualisendは、構文、ドメイン、使い捨て、役割アドレス、入力ミスといった高速なローカルチェックの結果をすぐに返します。より詳細なSMTPによるメールボックス確認は、少し遅れて完了します。確認の実行中は、レスポンスのresult.smtpは"pending"になり、書き込んだステータスは暫定の判定です(cachedまたはskippedの場合は、すでに最終結果です)。SMTPで確定した結果はジョブに反映されます。ジョブはGET https://app.qualisend.com/api/v1/jobs/{job_id}で取得できます。上のハンドラーはそのためにジョブIDを保存しています。同じスクリプト内の2つ目の関数が、ジョブの完了後に最終判定へ差し替えます。
// Same script. Run by a time-driven trigger: once a pending job completes,
// replace the instant verdict with the SMTP-confirmed one.
function finishPending() {
const sheet = SpreadsheetApp.getActive().getSheetByName('Form Responses 1'); // your responses tab
const apiKey = PropertiesService.getScriptProperties().getProperty('QUALISEND_API_KEY');
const lastRow = sheet.getLastRow();
if (lastRow < 2) return;
const jobIds = sheet.getRange(2, STATUS_COL + 2, lastRow - 1, 1).getValues();
jobIds.forEach(([jobId], i) => {
if (!jobId) return;
const res = UrlFetchApp.fetch(
'https://app.qualisend.com/api/v1/jobs/' + jobId + '?include=results',
{ headers: { Authorization: 'Bearer ' + apiKey }, muteHttpExceptions: true }
);
if (res.getResponseCode() !== 200) return; // try again on the next run
const job = JSON.parse(res.getContentText());
if (job.status === 'pending' || job.status === 'processing') return; // still probing
const row = i + 2;
const verdict = (job.results || [])[0];
if (verdict) {
sheet.getRange(row, STATUS_COL).setValue(verdict.status);
sheet.getRange(row, STATUS_COL + 1).setValue(verdict.score);
}
sheet.getRange(row, STATUS_COL + 2).clearContent(); // settled: stop checking this row
});
}
この関数は、2つ目のインストール可能なトリガーから実行します。FunctionはfinishPending、Event sourceはTime-driven、タイプは Minutes timer、間隔は5分ごとにします。APIを呼び出すのは、ジョブIDが残っている行だけです。行を更新するために/verifyを再度呼び出さないでください。2回目のチェックが始まり、さらに1クレジットを消費します。SMTPの段階に時間がかかる理由は、メールアドレス検証の仕組みで解説しています。
方法2:回答シートを一括検証する#
フォームが数か月にわたって回答を集めている場合や、スクリプトを管理したくない場合は、トリガーを使わずに済ませられます。回答はすでにスプレッドシートになっているため、1回の処理でまとめて検証します。
- 連携先のGoogleスプレッドシートを開きます(またはフォームの Responses タブ → Link to Sheets)。
- File → Download → Comma-separated values (.csv) を選びます。
- そのCSVをQualisendの一括検証にアップロードし、メールアドレスの列を指定してジョブを実行します。
- 結果をダウンロードし、メールアドレスをキーに元の行と突き合わせます。または、ステータス列をスプレッドシートに貼り付けます。
一括検証では、すべての行でSMTPによる確認を含む全工程を実行します。そのため、構文チェックでは見つからない存在しないメールボックスも検出できます。これはエクスポートしたリストに共通のやり方です。重複の除去、役割アドレスの扱い、リスクありのグループの対処については、メールリストをクリーニングする方法のガイドで解説しています。1つのアドレスを手軽に確認したいだけなら、無料の単一アドレスチェッカーを使えば、CSVは不要です。
判定結果への対応#
判定は、次の行動を変えてはじめて役に立ちます。どの方法で取得した場合でも、4つのステータスは次のように扱います。
| 判定 | 回答への対応 |
|---|---|
deliverable | 残します。実在するメールボックスが確認を受け付けたため、安全にメールを送れます。 |
undeliverable | 除外します。送信するとハードバウンスになり、バウンスは送信者レピュテーションを損ないます。 |
risky | セグメントを分けます。キャッチオールドメインであることが多いため、慎重に送信し、エンゲージメントを見てから信頼してください。 |
unknown | あとで再試行します。メールサーバーが明確な回答を返さなかった状態です。実在するかもしれないリードを、これだけで捨てないでください。 |
サブフラグを見ると、状況がさらに詳しくわかります。プレゼント企画の応募にdisposableフラグが付いていれば、たいていは使い捨てのアドレスなので、除外して問題ありません。roleフラグ(info@、support@)は、フォームの目的によって重要度が変わります。それぞれをフィルタリングすべき場面は、役割アドレス、使い捨て、フリーメールで解説しています。リアルタイムのスクリプトなら、その場で対応できます。たとえばresult.sub_flags.disposableがtrueのときに、列へ「review」というメモを書き込めます。
どちらの方法を選ぶべきか#
デモ依頼、ウェイティングリスト、ゲーテッドコンテンツのように、回答者がすぐにフォローすべきリードで、届いた瞬間に各行の隣で判定を見たい場合は、リアルタイムのApps Scriptトリガーを選びます。送信1件につき1クレジットを消費し、スクリプトの保守が少し必要です。
たまっている回答を整理したい場合、コードを避けたい場合、アクセスの多いフォームで個人アカウントのUrlFetchAppの上限を超えてしまう場合は、一括検証を選びます。両方を併用することもできます。新しい回答にはトリガーを使い、月に1回の一括検証で、古くなったアドレスを再チェックします。
どちらの場合も、サインアップ時の検証ガイドと同じ原則が当てはまります。アドレスが配信ツールに届く前に検証すれば、入力ミスは、後で代償を払うバウンスではなく、修正されたリードになります。複数のツールにフォームがある場合も、同じパターンを応用できます。ノーコードのZapierステップやTypeformのwebhookでも、それぞれのプラットフォームで同じ役割を果たせます。
よくある質問
01Google Formsだけでメールアドレスを検証できますか?
いいえ。Google Formsの「Response validation」オプションが確認するのは、回答が正しい形、つまり@とドメインを含むメールアドレスの形式かどうかだけです。メールサーバーには接続しないため、実在するメールボックスと、入力ミスや使い捨てドメインを区別できません。この形式チェックは最初のフィルターにすぎず、jane@gmial.comも問題なく通過します。実際の検証には、上で紹介したAPIまたは一括検証の手順が必要です。
02Google Formsの回答を検証するには、コードを書く必要がありますか?
必要なのはリアルタイムの方法だけです。Apps Scriptのトリガーには、短いスクリプトの貼り付けとトリガーの設定が必要ですが、コードは小さく、再利用できます。コードを完全に避けたい場合は、一括検証の方法なら操作はすべてクリックだけです。回答をCSVでダウンロードし、一括検証にアップロードして、結果を元のデータに統合します。スクリプトもトリガーも不要です。
03検証によって、フォームの回答者が待たされることはありますか?
いいえ。Apps Scriptのトリガーを使う場合、チェックは送信の後に実行されます。回答者はすでに完了画面を見ており、バックグラウンドでスプレッドシートに結果が書き込まれるだけです。フォームの動作は妨げられません。検証を送信の条件にしたい場合は、標準のGoogle Formsではなく、サーバーレスのエンドポイントを備えたカスタムフォームで行います。
04QualisendのAPIキーをスクリプトに書かずに管理するには?
コードではなく Script Properties に保存します。Apps Scriptエディターで Project Settings → Script Properties を開き、QUALISEND_API_KEYを追加します。実行時には、上のスニペットのとおりPropertiesService.getScriptProperties().getProperty(...)で読み取ります。Qualisendダッシュボードで、検証の権限だけを持つスコープ付きのキーを使い、漏えいした場合はローテーションしてください。
Google Formsの回答をクリーンにしませんか。無料プランには100クレジットが含まれており、どちらの方法も試せます。無料の単一アドレスチェッカーは、セットアップなしで使えます。APIリファレンスには、/verifyエンドポイントと、7言語のコピー可能なサンプルがあります。



