概要
今回は外部サービスに頼らず、Googleの仕組みだけで無料のお問い合わせフォームを作る方法について紹介していきます。
以前、無料で静的サイトに問い合わせフォームを実装する方法という記事でFormsubmitを使ったやり方を書きました。
あれはメール転送だけで完結する手軽さが魅力なんですが、運用していると「送られてきた内容を一覧で見返したい」「第三者サービスを挟まずに済ませたい」と思うようになってきたんですよね(- -;
そこで今回は、Google Apps Script(GAS)+スプレッドシート+Gmail通知という構成に作り替えました。
送信内容はスプレッドシートにどんどん溜まっていき、同時にGmailにも通知が飛んでくるので、見逃しもありません。
しかも使うのはGAS・スプレッドシート・GmailというすべてGoogle公式の仕組みだけなので、Formspreeのような外部サービスへの業務委託も発生しません。
プライバシーポリシー上も「保管先はGoogleのみ」とシンプルに書けます。
当ブログのお問い合わせフォームも実際にこの構成で動いているので、その中身をベースに解説していきますね(^^
料金は完全に無料で、サーバーも不要です。
それではやっていきましょう!
目次
仕組み(全体像)
まずは全体の流れを掴んでおきましょう。
今回作るのは、次のように動く仕組みです。
自作フォームの送信内容をGASがスプレッドシート記録とGmail通知に振り分ける流れ
ブログに置いた自作フォームからGoogle Apps Script(GAS)のWebアプリへデータを送り、GASがその内容をスプレッドシートに記録しつつ、Gmailで通知する、という構成です。
ポイントは、メール転送サービスやフォーム作成サービスといった第三者サービスを一切挟まないところです。
使うのはGAS・スプレッドシート・Gmailという公式の仕組みだけなので、追加のアカウント登録もいりません(^^b
それでは順番に作っていきます。
手順
1. スプレッドシートを用意
まず、送信内容を記録するスプレッドシートを作ります。
Googleドライブで新規スプレッドシートを作成し、1行目に次の見出しを入れておきましょう。
1行目の見出し
1
| 日時 送信元 ニックネーム メール 種別 内容 UA Referrer
|
作成したら、URLhttps://docs.google.com/spreadsheets/d/<この部分>/editの<この部分>がスプレッドシートIDです。
後でGASから参照するので控えておきます。
2. Google Apps Scriptを書く
次に、フォームから送られてきたデータを受け取ってスプレッドシートに記録し、Gmailで通知するGASを作ります。
スプレッドシートの「拡張機能 → Apps Script」を開き、下記のコードを貼り付けてください。
contact-handler.gs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| function doPost(e) { try { const p = (e && e.parameter) || {};
if (p.website) return _json({ ok: true });
const name = String(p.name || '').trim().slice(0, 100); const email = String(p.email || '').trim().slice(0, 200); const category = String(p.category || '').trim().slice(0, 50); const message = String(p.message || '').trim().slice(0, 4000);
if (!name || !email || !category || !message) { return _json({ ok: false, error: 'missing' }); } if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { return _json({ ok: false, error: 'email' }); }
const now = new Date();
const sheet = SpreadsheetApp.openById(_prop('SPREADSHEET_ID')).getSheets()[0]; sheet.appendRow([ now, p.source || '', name, email, category, message, String(p.ua || '').slice(0, 500), String(p.referrer || '').slice(0, 500), ]);
MailApp.sendEmail({ to: _prop('NOTIFY_TO'), subject: '[お問い合わせ] [' + category + '] ' + name + ' 様より', body: [ '日時: ' + Utilities.formatDate(now, 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss'), 'ニックネーム: ' + name, 'メール: ' + email, '種別: ' + category, '', '--- 内容 ---', message, ].join('\n'), });
return _json({ ok: true }); } catch (err) { console.error(err); return _json({ ok: false, error: 'internal' }); } }
function doGet() { return _json({ ok: true, service: 'contact-handler' }); }
function _json(obj) { return ContentService.createTextOutput(JSON.stringify(obj)) .setMimeType(ContentService.MimeType.JSON); }
function _prop(key) { const v = PropertiesService.getScriptProperties().getProperty(key); if (!v) throw new Error('Script property not set: ' + key); return v; }
|
SpreadsheetApp.openByIdでシートを開いてappendRowで1行追記し、続けてMailApp.sendEmailで通知を飛ばす、という流れです。
スプレッドシートIDや通知先メールは、コードに直書きせずスクリプトプロパティから読むようにしています。
Gmailの送信は個人アカウントで100通/日が上限です。
スパムが大量に来たときにこの枠を食い潰さないよう、本番では「1日90通を超えたらメール送信だけスキップ(スプレッドシート記録は継続)」というカウンターを足しておくと安心です。
3. スクリプトプロパティを設定
コード内の_prop('SPREADSHEET_ID')などが参照する値を登録します。
エディタ左の歯車アイコン(プロジェクトの設定)→「スクリプトプロパティ」で、次の2つを追加してください。
間違えないように注意です!!
4. ウェブアプリとしてデプロイ
GASをウェブアプリとして公開します。
右上の「デプロイ」→「新しいデプロイ」を選び、ダイアログ左上の歯車から種類を「ウェブアプリ」にして、次の設定でデプロイします。
- 次のユーザーとして実行
- 自分(自分の権限でスプレッドシートとGmailを操作する)
- アクセスできるユーザー
デプロイするとhttps://script.google.com/macros/s/.../execという形のURLが発行されます。
これが自作フォームの送信先になるので控えておきましょう。
デプロイしただけだと、外部から叩いても403になります。
`SpreadsheetApp`や`MailApp`を使うコードは別途OAuthの承認が必要で、これはエディタで`doGet`を手動実行すると承認フローが立ち上がります。
「Googleで確認されていないアプリ」という警告が出ますが、自分のスクリプトなので「詳細」→「(プロジェクト名)に移動」→「許可」で進めてOKです。
5. HTMLフォームを設置
いよいよブログ側にフォームを置きます。
当ブログのフォームをベースにしたHTMLが下記です。
contact/index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <form id="contact-form" novalidate> <div class="field"> <label for="name">ニックネーム <span class="req">必須</span></label> <input type="text" id="name" name="name" required maxlength="100"> </div>
<div class="field"> <label for="email">メールアドレス <span class="req">必須</span></label> <input type="email" id="email" name="email" required maxlength="200"> </div>
<div class="field"> <label for="category">お問い合わせ種別 <span class="req">必須</span></label> <select id="category" name="category" required> <option value="">選択してください</option> <option>利用方法の質問</option> <option>不具合のご報告</option> <option>機能追加のご要望</option> <option>その他</option> </select> </div>
<div class="field"> <label for="message">お問い合わせ内容 <span class="req">必須</span></label> <textarea id="message" name="message" required maxlength="4000"></textarea> </div>
<div aria-hidden="true" style="position:absolute;left:-9999px;"> <input type="text" id="website" name="website" tabindex="-1" autocomplete="off"> </div>
<div class="form__success" id="form-success" style="display:none;"> ✓ お問い合わせを送信しました。 </div>
<button type="submit">送信する</button> </form>
|
続いて、フォームを送信するJavaScriptです。
GAS_ENDPOINTに手順4で控えたURLを入れてください。
contact-form.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| const GAS_ENDPOINT = 'https://script.google.com/macros/s/<あなたのデプロイID>/exec'; const MIN_INTERVAL_MS = 60_000; const STORAGE_KEY = 'blog:contact:last_sent';
const form = document.getElementById('contact-form'); const success = document.getElementById('form-success'); const submitBtn = form.querySelector('button[type=submit]');
form.addEventListener('submit', async (e) => { e.preventDefault();
const name = form.name.value.trim(); const email = form.email.value.trim(); const category = form.category.value; const message = form.message.value.trim();
let err = ''; if (!name) err = 'ニックネームを入力してください。'; else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) err = 'メールアドレスを正しく入力してください。'; else if (!category) err = 'お問い合わせ種別を選択してください。'; else if (!message) err = 'お問い合わせ内容を入力してください。'; if (err) { alert(err); return; }
if (form.website.value) { success.style.display = 'block'; return; }
const last = Number(localStorage.getItem(STORAGE_KEY) || 0); if (Date.now() - last < MIN_INTERVAL_MS) { alert('短時間に複数回の送信はできません。少しお待ちください。'); return; }
const body = new URLSearchParams(); body.set('name', name); body.set('email', email); body.set('category', category); body.set('message', message); body.set('ua', navigator.userAgent || ''); body.set('referrer', document.referrer || '');
submitBtn.disabled = true; try { await fetch(GAS_ENDPOINT, { method: 'POST', mode: 'no-cors', body }); localStorage.setItem(STORAGE_KEY, String(Date.now())); success.style.display = 'block'; form.style.opacity = '0.6'; } catch (e2) { submitBtn.disabled = false; alert('送信に失敗しました。通信環境をご確認ください。'); } });
|
入力チェック→スパム対策→GASへfetchでPOST→送信完了表示、という流れです。
ここで押さえておきたいのがfetchのmode: 'no-cors'です。
GASのWebアプリはCORSヘッダを自前で付与できないため、普通に`fetch`するとブラウザがレスポンスをブロックしてしまいます。
そこで`mode: 'no-cors'`を付けると、レスポンスの中身は読めなくなる代わりに、送信自体は通るようになります。
このとき開発者ツールに`ERR_ABORTED`が出ることがありますが、これはエラーではなく、GAS側の`doPost`はちゃんと最後まで動いているので問題ありません。
なお、ハニーポットや連投防止は無くても動きますが、公開フォームはどうしてもスパムボットに狙われるので、入れておくことを強くお勧めします。
6. 動作確認
最後に動作を確認します。
まずブラウザでGASの/execURLにそのままアクセスし、{"ok":true,...}が返れば疎通はOKです(403が出る場合は手順4のアクセス権かOAuth承認を見直してください)。
次に実際にフォームから送信してみて、次の2つが起きれば成功です(^^
- スプレッドシートに行が増える
- 日時・ニックネーム・メール・種別・内容などが1行追記される
- Gmailに通知が届く
NOTIFY_TOに設定したアドレスに件名付きで届く
メリット・デメリット
この構成の良いところと注意点もまとめておきますね。
- 完全無料・外部サービス不要
- GAS・スプレッドシート・Gmailだけで完結。第三者サービスへの業務委託が発生しない
- データが手元に溜まる
- 回答がスプレッドシートに蓄積され、フィルタやSUMIFSで集計・CSVエクスポートも自在
- 通知と記録の両取り
- スプレッドシートに残しつつGmailにも飛ぶので、見逃さず後から見返せる
- デプロイとOAuth承認に少しクセがある
- 種類を「ウェブアプリ」にする・アクセスを「全員」にする・
doGet実行で承認、ここを忘れると調査が大変
- 送信の成否がJS側で取れない
no-corsのためレスポンスを読めない。確実性を求めるなら別途プロキシ等が必要
- Gmailは1日100通まで
- 大量送信があるとメール枠を使い切るので、上限手前で通知だけ止めるガードを入れておくと安心
正直、最初のデプロイとOAuth承認まわりだけ少しとっつきにくいですが、一度動いてしまえばあとはほったらかしでデータが溜まっていくので、かなりお勧めです。
締め
今回は、外部サービスに頼らずGAS+スプレッドシート+Gmailだけで無料のお問い合わせフォームを作る方法を、当ブログの実装をベースに紹介しました。
Formsubmitのメール転送方式が「とにかく手軽」だとすると、今回の方式は「データを自分の手元に残しつつ、Google公式の仕組みだけで完結させる」方向ですね。
問い合わせを後から見返したり集計したりしたい方には、こちらがぴったりだと思います。
サーバーいらずかつ無料、しかも保管先がGoogleだけで規約的にもクリーンなので、個人開発のサイトやブログのお問い合わせ口としてはかなり優秀です。
ぜひ自分のサイトにも導入してみてください(^^
以上となります。
色々試しましたが、レイアウトも自由自在なので、この方法が良いかな?と思います。
それではお疲れさまでした!