維持費永久0円のお問い合わせフォームを作る（自動返信機能つき）

## 1. 背景
ランニングコスト削減のためGithubPagesにデプロイしている自作サイトに、お問い合わせフォームを構築しようと思い調べていたところ、既存のサービスを利用すると維持費が発生する上に、件数に制約があることが分かりました。GoogleFormをはめ込むことで問い合わせフォームの代わりにすることもできますが、サイトのデザインの統一感が損なわれてしまいます。そこで、GoogleFormに直接POSTすることでサイトのデザインも独自に設定できることが分かったので、実装方法を紹介します。合わせて、自動返信の設定方法についても紹介します。

- **作成したお問い合わせフォーム**
https://bitcraft.work/contact/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760542/d4eaf667-6e6e-49ea-98c5-758f7592445f.png)


## 2. アーキテクチャ
![アーキテクチャ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760542/d3b7e26c-5e7f-4970-86fe-739b9e88c605.png)

主な流れは、ユーザーがサイト内の「送信」ボタンを押すとGoogleFormに回答が送信され、予め連携しているスプレッドシートにも回答が記録されます。GASで作成したスクリプトのトリガーを5分間隔で設定し、スプレッドシートに新しいデータがあれば、ユーザーが入力したメールアドレス宛に自動返信を行います。また、管理者にもフォームの内容を送信します。
上記の図ではGithubPagesを使用する前提で表していますが、どのサーバーを利用しても基本的には実装できます。

## 3. GoogleFormを作成する

- GoogleFormの新規作成はここから
https://docs.google.com/forms/u/0/

- 例）お問い合わせフォーム用に作成したGoogleForm
https://docs.google.com/forms/d/e/1FAIpQLScULh85kAiW2OKRvFMOIu7fpu6gw_PESrDMd7qlSXmJG2yWsg/viewform?usp=dialog

後から、GoogleFormをHTMLに置き換えるのでできる限りシンプルな作りにするとスムーズに実装できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760542/0e80dece-14c4-441c-a1f0-8f780dd4a7bb.png)

## 4. HTML・JS(一部)を実装する

先程作成したGoogleFormと同じ内容のFormをHTMLとCSSによって実装します。以下にはGoogleForm内での要素とHTMLで実装する際のコードも合わせて紹介します。

### 4.1. 各種回答形式の実装方法の紹介 （ここは読み飛ばしてOK）

- **短文回答（名前）**
inputタグの最後にある`required/`は回答を必須にする場合に記述してください。

    ```html
    <div class="form-group">
        <label class='title'>お名前 <span style="color: red;">*</span></label>
        <input type="text" id="name" name="entry.〇〇〇〇〇" class="form-control" value="" placeholder=" 例）山田太郎" required/>
    </div>
    ```

- **ラジオボタン（問い合わせ種別）**
必要に応じて`label`タグを増やすことで、ラジオボタンの要素を増やすことができます。

    ```html
    <div class="form-group">
        <label label class='title'>お問い合わせ種別 <span style="color: red;">*</span></label>
        <label class="check_label">
            <input class="radio form-check-input" name="entry.〇〇〇〇〇" type="radio" value="システム開発のご相談">
            <span class="radio-icon"></span>システム開発のご相談
        </label>
        <label class="check_label">
            <input class="radio form-check-input" name="entry.〇〇〇〇〇" type="radio" value="Webサイト制作のご依頼">
            <span class="radio-icon"></span>Webサイト制作のご依頼
        </label>
        <label class="check_label">
            <input class="radio form-check-input" name="entry.〇〇〇〇〇" type="radio" value="UI・UXデザインのご相談">
            <span class="radio-icon"></span>UI・UXデザインのご相談
        </label>
    </div>
    ```

- **長文回答（相談内容）**
    ```html
    <div class="form-group">
        <label label class='title'>ご相談内容 <span style="color: red;">*</span></label>
        <textarea class="form-control" name="entry.〇〇〇〇〇"></textarea>
    </div>
    ```

- **チェックボックス（ポリシー同意）**
ポリシーの同意確認としてチェックボックスを利用する場合、`a`タグの`href="△△△△"`を任意のページに設定することで、`プライバシーポリシー`の文字がハイパーリンクになります。

    ```html
    <div class="form-group">
        <label label class='title'>個人情報の取得について <span style="color: red;">*</span></label>
        <label class="check_label">
            <input class="checkbox form-check-input" name="entry.〇〇〇〇〇" type="checkbox" value="プライバシーポリシーに同意します。" required/>
            <span class="checkbox-icon"></span>
            <a href="△△△△" target="_blank">プライバシーポリシー</a>に同意します。
        </label>
    </div>
    ```

### 4.2. HTML全体
以下のHTMLに、必要に応じてCSSを設定してください。各要素の`name="entry.〇〇〇〇〇"`は3で作成したGoogleForm内の`name`の値に置き換えます。（5で方法を説明）

```html
<form id="contact-form">
    <div class="form-group">
        <label class='title'>お名前 <span style="color: red;">*</span></label>
        <input type="text" id="name" name="entry.〇〇〇〇〇" class="form-control" value="" placeholder=" 例）山田太郎" required/>
    </div>
    <div class="form-group">
        <label class='title'>メールアドレス <span style="color: red;">*</span></label>
        <input type="text" id="email" name="entry.〇〇〇〇〇" class="form-control" value="" placeholder=" 例）example@mail.com" required/>
    </div>
    <div class="form-group">
        <label label class='title'>お問い合わせ種別 <span style="color: red;">*</span></label>
        <label class="check_label">
            <input class="radio form-check-input" name="entry.〇〇〇〇〇" type="radio" value="システム開発のご相談">
            <span class="radio-icon"></span>システム開発のご相談
        </label>
        <label class="check_label">
            <input class="radio form-check-input" name="entry.〇〇〇〇〇" type="radio" value="Webサイト制作のご依頼">
            <span class="radio-icon"></span>Webサイト制作のご依頼
        </label>
        <label class="check_label">
            <input class="radio form-check-input" name="entry.〇〇〇〇〇" type="radio" value="UI・UXデザインのご相談">
            <span class="radio-icon"></span>UI・UXデザインのご相談
        </label>
    </div>
    <div class="form-group">
        <label label class='title'>ご相談内容 <span style="color: red;">*</span></label>
        <textarea class="form-control" name="entry.〇〇〇〇〇"></textarea>
    </div>
    <div class="form-group">
        <label label class='title'>個人情報の取得について <span style="color: red;">*</span></label>
        <label class="check_label">
            <input class="checkbox form-check-input" name="entry.〇〇〇〇〇" type="checkbox" value="プライバシーポリシーに同意します。" required/>
            <span class="checkbox-icon"></span>
            <a href="△△△△" target="_blank">プライバシーポリシー</a>に同意します。
        </label>
    </div>
</form>
```

### 4.3. 画面を遷移させないためのJSを実装する

フォームの回答の送信先は`form`タグの中に`aciton`を設定することで実装できますが、この方法だと、回答送信後にGoogleFormの送信完了ページに遷移してしまいます。そこで、以下の様にJavaScriptの`fetch("□□□□□")`で送信先を設定することで、ページの遷移を防ぐことが可能になります。（5で方法を説明）

```JS
const form = document.getElementById("contact-form");

  form.addEventListener("submit", function(e) {
    e.preventDefault(); // ページ遷移を防ぐ

    const formData = new FormData(form);

    fetch("□□□□□", {
      method: "POST",
      mode: "no-cors", // これが大事
      body: formData
    })
    .catch(() => {
      alert("送信に失敗しました。再度お試しください。");
    });
  });
```

## 5. HTMLとGoogleFormの連携
次に、3で作成したGoogleFormと4.で作成したHTMLとを紐づけます。

### 5.1. 回答の送信先リンクを取得

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760542/8adba818-8783-44a3-8147-480cabc1d0d3.png)

GoogleFormの回答リンクにアクセスして「F12キー」を押して開発者モードを開いて下さい。次に右上の「×印」横のメニューを開き、「検索」をクリックします。（Ctrl＋Shift＋Fでも開けます）画面の下部に検索タブが開いたら以下の様に入力して検索します。

```html
form action
```

検索結果をクリックして、`action="https://docs.google.com/forms/u/0/d/e/‥‥"`の部分をコピーして、以下の様に、「4.3.」のJavaScript内の`fetch("□□□□□")`の値を置き換えます。

```JS
// 省略

fetch("https://docs.google.com/forms/u/0/d/e/‥‥", {
      method: "POST",
      mode: "no-cors",
      body: formData
    })
    
// 省略
```

### 5.2. 質問ごとの識別子をFormを送信して確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760542/39731a42-5bd3-484f-9317-7e4d19d1e9ad.png)

開発者モードを開いたまま、作成したGoogleFormの全ての項目に適当な回答を入力して送信します。回答を送信出来たら、開発者モード上部の「ネットワーク」タブをクリックし、「FormResponse」を選択します。次に、右側のタブから「ペイロード」を選択すると、`entry.1234567890`の様な値が表示されるので、回答の内容を基に、4で作成したフォームの対応する質問の`name="entry.〇〇〇〇〇"`と値を置き換えます。

```html
<form id="contact-form" action="https://docs.google.com/forms/u/0/d/e/‥‥">
    <div class="form-group">
        <label class='title'>お名前 <span style="color: red;">*</span></label>
        <input type="text" id="name" name="entry.1234567890" class="form-control" value="" placeholder=" 例）山田太郎" required/>
    </div>
    <div class="form-group">
        <label class='title'>メールアドレス <span style="color: red;">*</span></label>
        <input type="text" id="email" name="entry.0987654321" class="form-control" value="" placeholder=" 例）example@mail.com" required/>
    </div>
<!-- 以下に続く -->
```

これで、HMTLの入力をGoogleFormに送信できるようになりました。

## 6. 自動返信と回答通知機能の設定

### 6.1. スプレッドシートの連携

最後に、自動返信のシステムを実装します。2で説明した様に、スプレッドシートを5分おきに探索して、新しい回答があればメールを送信するシステムであるため、GoogleFormとスプレッドシートを紐づけておく必要があります。以下の画像の様に、「回答＞スプレッドシートにリンク」をクリックして連携します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760542/96b9e5a3-963c-4a25-aa53-165b4607bf9c.png)

次に、上の操作で自動的にスプレッドシートが開くので、開いたスプレッドシートのURLから`スプレッドシートのID`を取得します。（メモ帳などに記録すると良いと思います。）

```
https://docs.google.com/spreadsheets/d/ この部分がスプレッドシートのID /edit?
```

### 6.2. AppScriptの記述

以下の画像の様に右上のメニューから「App Script」を開いてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760542/01e9d862-767b-4262-af7c-957afe2b14b5.png)

AppScriptが開けたら、エディター内に下記のコードをコピペで張り付けてください。次に、エディタ上で`sheetID`の値を上の「6.1.」で確認したスプレッドシートのIDに置き換えてください。`sheetName`は連携時にデフォルトで「フォームの回答１」となりますが、シート名に変更を加えた場合、こちらも置き換えてください。

次に、メールの内容に必要な情報を`responses["質問文"]`の形式でスプレッドシートから取得します。質問文に間違いがあると正しく参照できないため、GoogleForm内からコピして張り付けることをお勧めします。
※ 下の方にある`sendEmails`関数内で取得しています。

また、ユーザー返信内容`bodyToUser`と確認メールの返信先`ownerEmail`も必要に応じて修正してください。特に、`ownerEmail`が間違っていると回答通知が届かないので、回答通知を送信したいメールアドレスに置き換えてさい。

※ `ownerEmail`はユーザーの自動返信先のメールアドレスとは異なります。フォームの作成者が回答を確認するためのアドレスになります。ユーザーのメールアドレスは`responses["メールアドレス"]`によりFormの回答から取得しています。

```JavaScript
function checkNewResponses() {
  const sheetId = 'ここにスプレッドシートのIDを張り付け'; // 例: '1AbCDefGHiJklmnOpQRstuVwXYZ1234567890'
  const sheetName = 'フォームの回答 1'; // 実際のシート名に合わせてください

  const sheet = SpreadsheetApp.openById(sheetId).getSheetByName(sheetName);

  if (!sheet) {
    throw new Error(`"${sheetName}" という名前のシートが見つかりませんでした。`);
  }
  
  const lastRow = sheet.getLastRow();
  const properties = PropertiesService.getScriptProperties();
  const lastProcessedRow = Number(properties.getProperty("lastProcessedRow") || 1); // ヘッダーは1行目

  if (lastRow <= lastProcessedRow) {
    return; // 新しい回答がない場合は終了
  }

  for (let i = lastProcessedRow + 1; i <= lastRow; i++) {
    const row = sheet.getRange(i, 1, 1, sheet.getLastColumn()).getValues()[0];
    const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];

    const response = {};
    headers.forEach((header, index) => {
      response[header] = row[index];
    });

    sendEmails(response); // メール処理
  }
  
  // 最終処理済み行を保存
  properties.setProperty("lastProcessedRow", lastRow);
}

function sendEmails(responses) {
  const emailAddress = responses["メールアドレス"];
  const incName = responses["ご所属"];
  const userName = responses["お名前"] || "お客様";
  const subjectToUser = "【株式会社〇〇】お問い合わせありがとうございます";
  const bodyToUser = `${incName}\n${userName} 様\n\nこの度はお問い合わせいただき、誠にありがとうございます。担当の〇〇です。\n\nお問い合わせの内容を確認のうえ、追ってご連絡させていただきます。\n今後ともどうぞよろしくお願いいたします。\n\n株式会社〇〇 代表  山田 太郎`;

  const subjectToOwner = "【株式会社〇〇】新しいお問い合わせがあります";
  const bodyToOwner = `以下の内容でお問い合わせがありました。\n\n` + Object.entries(responses).map(([key, value]) => `${key}: ${value}`).join("\n");
  
  const ownerEmail = "mail@mail.com"; // ←管理者のメールアドレスに変更
  
  MailApp.sendEmail(emailAddress, subjectToUser, bodyToUser);
  MailApp.sendEmail(ownerEmail, subjectToOwner, bodyToOwner);
}

```

### 6.3. トリガーの設定

最後に、上の「6.2.」で作成したAppScriptを5分おきで実行するトリガーを設定します。以下の画像の様に画面左端のメニューから「トリガー」を選択し、右下の「＋トリガーを追加」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760542/12190a58-d06a-44ee-a1db-6925a96ae7b6.png)

次に、以下の画像の様に「イベントのソースを選択」を「時間主導型」に、「時間ベースのトリガーのタイプを選択」を「分ベースのタイマー」、「時間の間隔を選択（分）」を「5分おき」に設定し、右下の「保存」をクリックしてください。

※ 5分よりも短い間隔でトリガーを設定すると1日の実行時間制限を超えてしまう可能性があるため、5分以上の間隔で設定してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3760542/08471979-be6a-42cd-9495-b506f82668d3.png)

これで、すべての設定が完了しました。

## 7. まとめ

今回は、維持費永久0円のお問い合わせフォームの作成方法について紹介しました。現在、働き方が多様化しフリーランスという選択肢も浸透してきています。Webサイトのクオリティはクライアントからの信頼に繋がります。しかし、クオリティの高いWebサイトを作ろうと思うと、毎月発生するランニングコストでお財布を圧迫してしまいます。リーズナブルにクオリティの高いWebサイトを作成したいという方のお役に立てたらと思います。