🤖

Sendgrid + microCMSで個人のポートフォリトサイトのお問い合わせフォームを改修したお話

2022/01/11に公開

こんにちは、うえむーです。

今回はSendgrid + microCMSで個人のポートフォリトサイトのお問い合わせフォームを改修した事について話をしていきたいと思います。

本題に入る前に、自分のポートフォリオサイトを紹介したいと思います。

リンク先
https://uemu-engineer.com/

「サイト名」はnu-stackというポートフォリオサイトであり、自分の名前とフルスタックを組み合わせた造語です。
フロントエンド・サーバーサイド・インフラなどなんでもできるエンジニアになりたいという気持ちを込めてこのサイトを作成しました。ポートフォリオサイトは去年の11月から着手し今年の6月に完成しました。
上記に添付したgif画像はメインビジュアルでありWebGL(three.js)で作成しております。

以下が現在のサイト全体図です。それぞれのツールを利用してJamstack構成でサイトを立ち上げました。

  • ドメイン・・・「お名前.com」
  • フレームワーク・・・「Next.js」
  • ソースコード管理ツール・・・「GitHub」
  • 静的ホスティングサービス・CDN・・・「Vercel」
  • ヘッドレスCMS・・・「microCMS」
  • メールプラットフォーム・・・「Sendgrid」

今回は、ヘッドレスCMSの「microCMS」とメールプラットフォームの「Sendgrid」をメインにお話ししていきます。

Sendgridとは

SendGridというのは全世界で利用されているメール配信プラットフォームです。クラウドサービスのため会員登録するだけで、トランザクションメール・マーケティングメールをメールサーバー不要で利用できたりメール配信履歴を一元管理できたり一括配信などもできます。

SendGridでフリープランでできる事に関しては以下に記載されているのでこちらからご覧ください。
https://sendgrid.kke.co.jp/functions/

Vercelのドキュメントを確認するとEmailはSendgridなどのメール配信プラットフォームを利用することをお勧めしているみたいです。
https://vercel.com/docs/concepts/solutions/email

今回は、Sendgridというプラットフォームを利用して、トランザクションメール機能追加改修しました。
改修内容は以下になります。

その機能を追加するにはSendgridのAPIを発行する必要があります。

Sendgridを登録・API発行する

まずは、会員登録します。会員登録の手順は以下のようになり、仮登録 + 本登録をして審査が通ったらご利用できます。

会員登録をしたのでAPIを発行します。まず、ダッシュボードをアクセスします。

以下のキャプチャーがダッシュボードのページであり、配信状況の把握・サプレッション管理・メルマガ配信・API発行などができます。今回はAPI発行するのでサイドインのメニュー内のSetting => API Keysをアクセスします。

アクセスしたら、今まで発行したAPI Keyのリストが表示され、新たに発行する場合は赤枠で囲っている「Create API Key」をアクセスします。

アクセスすると以下の様にAPI Keyのラベル名・パーミッションの選択項目が表示されます。

まずは、APIキーの名称を入力します。

次に、パーミッションを選択します。
パーミッションの選択項目は3種類あるみたいです。

  • Full Access・・・すべてのAPIへのアクセス権限が付与されます。
  • Restricted Access・・・アクセス権限をカスタマイズすることができます。
  • Billing Access・・・Billing APIのエンドポイントにアクセスすることができます。

その中からFull Access or Restricted Accessを選択します。
「Billing Access」は選択できないようです。

詳しくは以下のマニュアルを参照していただければと思います。
https://sendgrid.kke.co.jp/docs/User_Manual_JP/Settings/api_keys.html

APIの名称・パーミッションを設定完了したら「Create & View」をクリックします。
すると以下のようにAPIが発行され、実装編に利用するので控えておきます。

microCMSでAPI発行する。

次にmicroCMSでフォーム送信データ蓄積用のAPIを作成します。
API作成手順については、microCMS公式ブログ「microCMSのはじめ方」で分かりやすく紹介されているので参照してください。

https://blog.microcms.io/getting-started/

API名、エンドポイント名は分かりやすいものを設定します。
今回はそれぞれ「お問い合わせ」「contact」と設定して、APIの型はリスト形式を選択します。

続いて、APIスキーマを定義します。
SendGridでの登録時と同様、フィールドIDは以下で統一します。
お名前: name
タイトル: title
メールアドレス: mail
問い合わせ内容: text

お名前、メールアドレス、タイトルはテキストフィールド、問い合わせ内容はテキストエリアで作成します。

これでAPI発行され、実装編に利用するのでX-MICROCMS-API-KEYの値を控えていきます。

実装する

まずはSendgridを活用するために、sendgrid/mailをnpmでインストールしなければなりません。
以下のコマンドでインストールするようにしてください。

npm install --save @sendgrid/mail

インストールした後、package.jsonのsendgrid/mailが追加されていることが確認できます。

 "dependencies": {
...
   "@sendgrid/mail": "^7.4.7",
...
 },

インストール完了後、.envファイルを開き発行したAPIと管理者用アドレスのメールアドレスを入力します。
さらに、microCMSのPOSTAPIKEYも入力します。

NU_POST_API_KEY='XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
NS_GRID_API_KEY='SG.XXXXXXX'
NS_FROM=XXXXXXXXXX@gmail.com

.envファイルを設定した後、next.config.jsファイルを開きenvの設定を追加します。

module.exports = {
 env: {
   NU_POST_API_KEY: process.env.NU_POST_API_KEY,
   NS_GRID_API_KEY: process.env.NS_GRID_API_KEY,
   NS_FROM: process.env.NS_FROM,
 },
...
};

次に、トランザクションメールを送信するためにsend.tsxファイルを作成し以下のように作成します。
NS_GRID_API_KEYは発行したAPIキーを入力し、NS_FROMは宛先を入力します。

ファイル名:api/send.tsx


export default function handler(req, res) {  
    if(req.method === 'POST') {
      const sgMail = require('@sendgrid/mail');
      sgMail.setApiKey(process.env.NS_GRID_API_KEY);
      const msg = {
        to: req.body.email,
        from: process.env.NS_FROM,
        subject: 'お問合せありがとうございました。',
        text: req.body.name + '様<br><br>平素より、●●●●をご利用いただき誠にありがとうございます。<br>今しばらくお待ちくださいますようよろしくお願い申し上げます。<br><br>お問い合わせ内容<br>' + req.body.title  + '<br><br>お問い合わせ詳細<br>' + req.body.body,
        html: req.body.name + '様<br><br>平素より、●●●●をご利用いただき誠にありがとうございます。<br>今しばらくお待ちくださいますようよろしくお願い申し上げます。<br><br>お問い合わせ内容<br>' + req.body.title  + '<br><br>お問い合わせ詳細<br>' + req.body.body,
      };
      (async () => {
        try {
          await sgMail.send(msg);
        } catch (error) {
          console.error(error);
          if (error.response) {
            console.error(error.response.body)
          }
        }
      })();
    }
    res.status(200)
}

次にお問い合わせフォームを作成します。index.tsxファイルを作成して以下のように記載します。
以下のコードはお問合せ内容確認のモーダル表示する機能も含まれています。

ファイル名:index.tsx


/**
 * common
 */
 import React, { useState, useEffect } from "react";
 import axios from "axios";
 import ReactDOM from "react-dom";
 import ReactDOMServer from "react-dom/server";
 import { useRouter } from "next/router";
 import Complete from '../components/complete';
 
 const Contacts = () => {

  // 初期設定
  const router = useRouter()
      , [email, setEmail] = useState("")
      , [name, setName]   = useState("")
      , [title, setTitle] = useState("")
      , [body, setBody]   = useState("")
      , [modalIsOpen,setIsOpen] = React.useState(false);
      function closeModal(){ setIsOpen(false); }

  // お問いあわせ内容確認のモーダルを出力
  const handleSubmit = e => {
    e.preventDefault();
    openModal();
    function openModal() {
      setIsOpen(true);
    }
    function closeModal(){
      setIsOpen(false);
    }
  };

  // お問いあわせ内容確認のDOM生成
  useEffect(() => {
    ReactDOM.render(
      <React.StrictMode>
          <ModalConfirm
              isOpen={modalIsOpen}
              onRequestClose={closeModal}
          />
      </React.StrictMode>,
      document.getElementById("modalArea")
    );
  });

  function ModalConfirm(props) {
    const data = {
      email: email,
      name: name,
      title: title,
      body: body
    };
    if(props.isOpen == true) {      
      return (
        <>
          <div className="c-contactconform">
            <div className="c-contactconform_inner">
              <p className="c-contactconform_txt">入力内容が正しければ「送信する」をクリックしてください。</p>
              <table>
                <tbody>
                  <tr><th>お名前</th><td>{data.name}</td></tr>
                  <tr><th>メールアドレス</th><td>{data.email}</td></tr>
                  <tr><th>お問い合わせの内容</th><td>{data.title}</td></tr>
                  <tr className="borderline"><th>お問い合わせ詳細</th><td>{data.body}</td></tr>
                </tbody>
              </table>
              <div className="c-btn-area">
                <button
                  className="c-btn-primary"
                  type="submit"
                  onClick={FormSubmit}
                >
                  送信する
                </button>
              </div>
            </div>
            <button className="c-btn-close" onClick={closeModal}>close</button> 
          </div>
          <div className="contact__form__bg" onClick={closeModal}></div>
        </>
      )
    } else {
      return null;
    }
  }

  // お問いあわせ内容確認を送信した時のイベント処理
  const FormSubmit = async e => {
    e.preventDefault();
    const datas = { email: email, name: name, title: title, body: body };
    axios({
      method: "post",
      url: "https://XXXXXXXXXXXXXXX.microcms.io/api/v1/contact",
      data: datas,
      headers: {
        "Content-Type": "application/json",
        "X-MICROCMS-API-KEY": process.env.NU_POST_API_KEY
      }
    })
    .then(() => {
      const contact_form = document.getElementsByClassName("c-contactconform");
      const contact_form_inner = document.getElementsByClassName("c-contactconform_inner");
      const complete_cont = ReactDOMServer.renderToStaticMarkup(<Complete />);
      contact_form_inner[0].remove();
      contact_form[0].insertAdjacentHTML(
        'afterbegin', complete_cont
      );
      axios( {
        method: "POST",
        url:'/api/send',
        data: datas,
        headers: {
          "Content-Type": "application/json",
        },
      } ).then(() => {
      })
    })
    .catch(err => {
      console.log(err);
    });
  }
  return (
    <>
    <section id="p-contact">
      <h2 className="c-title">CONTACT</h2>
      <div className="c-contactform__inner">
        <p className="c-contact-txt">
          どんな些細でもいいですので気軽にお問い合わせください。<br/>
          <a href="https://twitter.com/uemuragame5683" target="_blank" rel="noreferrer">Twitter</a>でも受け付けております。
        </p>
        <div className="c-alert-warning"></div>
        <form className="c-contactform">
          <div className="c-contactform__content">
            <label>あなたの名前</label>
            <input
              type="text"
              placeholder="名前を入力してください"
              id="name"
              name="name"
              value={name}
              onChange={e => setName(e.target.value)}
              required
            />
          </div>
          <div className="c-contactform__content">
            <label>メールアドレス</label>
            <input
              type="email"
              placeholder="メールアドレスを入力してください"
              id="email"
              name="email"
              value={email}
              onChange={e => setEmail(e.target.value)}
              required
            />
          </div>
          <div className="c-contactform__content">
            <label>お問い合わせ内容</label>
            <input
              type="text"
              placeholder="タイトルを入力してください"
              id="title"
              name="title"
              value={title}
              onChange={e => setTitle(e.target.value)}
              required
            />
          </div>
          <div className="c-contactform__content">
            <label>お問い合わせ詳細</label>
            <textarea
              placeholder="本文を入力してください"
              name="body"
              value={body}
              onChange={e => setBody(e.target.value)}
              required
            />
          </div>
          <div className="c-btn-area">
            <button className="c-btn-primary" type="submit" onClick={handleSubmit}>送信内容を確認する</button>
          </div>
        </form>
      </div>
      <div id="modalArea"></div>
    </section>
    </>
  )
};
 
 export default Contacts

お問い合わせ完了報告のテンプレートも作成して行きます。

ファイル名:complete.tsx


import * as React from 'react';

export default function Component() {
    return (
      <>
        <div className='c-alert-warning c-contact__complete'>
          <p>
            この度はお問い合わせメールをお送りいただきありがとうございます。<br />
            今しばらくお待ちくださいますようよろしくお願い申し上げます。<br />
            なお、しばらくたっても返信、返答がない場合は、<br />
            お客様によりご入力いただいたメールアドレスに誤りがある場合がございます。<br />
            その際は、お手数ですが再度お問い合わせいただけますと幸いです。
          </p>
        </div>
      </>
    );
}

これをVercelかNetlifyなどのホスティングサービスを利用して、
githubと連携しコミット・デプロイすると以下のような出力結果になりました。

1.完成形

2.トランザクションメール確認

[メールアドレス]に入力したアドレスと、send.tsxで設定した管理者用アドレスに、SendGridで設定したメールが届いていれば成功です。


●● ●●様

平素より、●●●●をご利用いただき誠にありがとうございます。
今しばらくお待ちくださいますようよろしくお願い申し上げます。
なお、しばらくたっても返信、返答がない場合は、
お客様によりご入力いただいたメールアドレスに誤りがある場合がございます。
その際は、お手数ですが再度お問い合わせいただけますと幸いです。

お問い合わせ内容
テストだよ

お問い合わせ詳細
テストだよ


3.フォーム送信データ確認
microCMSの管理画面を開き送信内容が反映されていれば完了です。

お問いあわせ内容確認を送信した時のイベント処理のみのコードは以下になります。
今回はaxiosを利用して実装しております。

FormSubmit関数のイベントハンドラを呼び出すとき、先ずはaxiosでmicroCMSのインスタンスを作成してPOST送信し、成功したらaxiosで先ほど作成したsendgridのAPIのインスタンスを作成してPOST送信するようにしております。


  // お問いあわせ内容確認を送信した時のイベント処理
  const FormSubmit = async e => {
    e.preventDefault();
    const datas = { email: email, name: name, title: title, body: body };
    axios({
      method: "post",
      url: "https://uemura5683.microcms.io/api/v1/contact",
      data: datas,
      headers: {
        "Content-Type": "application/json",
        "X-MICROCMS-API-KEY": process.env.NU_POST_API_KEY
      }
    })
    .then(() => {
      const contact_form = document.getElementsByClassName("c-contactconform");
      const contact_form_inner = document.getElementsByClassName("c-contactconform_inner");
      const complete_cont = ReactDOMServer.renderToStaticMarkup(<Complete />);
      contact_form_inner[0].remove();
      contact_form[0].insertAdjacentHTML(
        'afterbegin', complete_cont
      );
      axios( {
        method: "POST",
        url:'/api/send',
        data: datas,
        headers: {
          "Content-Type": "application/json",
        },
      } ).then(() => {
      })
    })
    .catch(err => {
      console.log(err);
    });
  }

注意事項

SendGridの管理画面で発行したAPIは絶対にGitHubなどのオープンソースで公開しないようにお願いします。公開してしまった場合はSendGridのカスタマーサポートから注意喚起の連絡が届き一時期SendGridが利用できなくなります。
第三者に展開できないようにGithubでprivateモードにするか、Vercelなどの静的ホスティングサービスでenvの設定をするようにお願いします。

まとめ

ヘッドレスCMSのmicroCMSとメール配信サービスのSendGridを利用すればサーバー不要でトランザクションメールの送信、管理画面でメール送信内容を蓄積することができるのでぜひ試して見てください。

参考記事

https://www.boel.co.jp/tips/vol119/

https://sendgrid.kke.co.jp/blog/?p=14220

https://ji23-dev.com/blogs/next-use-microcms-contact

Discussion