Twilio SendGridとNode.jsを使用してメール問い合わせフォームを構築

March 21, 2022
執筆者
Phil Nash
Twilion
レビュー担当者

How to build an email contact form with SendGrid and Node JP

この記事はPhil Nashこちらで公開した記事(英語)を日本語化したものです。

Webサイトにメールアドレスを公開すると、スクレーピングされ、スパムに使用されるおそれがあります。この問題を回避し、同時に自身のWebサイトからメールで問い合わせを受け取れるようにする方法の一つとして、問い合わせフォームを構築することがあります。

本稿では、Twilio SendGridベースの開発プロジェクトの一例として、ウェブサイト上で問い合わせフォームを構築し、メールアドレスを公開することなく問い合わせのメールを受け取れるようにする方法を説明します。

プロジェクトはTwilio Functionsも使って構築しますが、本稿でご紹介するコードはあらゆるNode.js環境での使用に対応しています。

APIによるメール送信との違い

ユーザーがお問合せフォームを送信した際は、お問合せフォームを入力した人のメールアドレスを送信元とするメールがウェブサイト運営者に送信されることがよくあります。しかし、メール送信に関する信頼性を維持するため、Twilio SendGridは単一送信元として検証されたアドレス、または認証済みのドメインからの送信しか許可しません。

そのため、提供されたメールアドレスをfromアドレスとして使用する代わりに、reply-toアドレスに設定できます。この方法であれば、Twilio SendGridは認証済みメールアドレスからメールを送信する一方で、受信者側で [Reply](返信)ボタンが押されると、提供されたメールアドレスに返信メールが送られます。

この方法の他のメリットは、メール送信プロセスを管理できる点です。一例として、問い合わせフォームからのメールを(その他のメールとの対比で)簡単にフィルタリングできるように、メールの件名に[contactform]タグを加える操作なども可能です。また、フォーム上で追加情報を収集しメール本文に含めることもできます。

ここまで、Twilio SendGridによる問い合わせフォーム構築について考察してきました。それでは構築を始めましょう。

問い合わせフォームの構築

構築するフォームは次のようなイメージで動作します。

tsg-form-jp-1

構築には以下が必要となります。

必要なもの

プロジェクトの開始

問い合わせフォームのサーバー側の構築にはTwilio Functionsを使用します。Twilio Functionsを利用するメリットは、プロジェクトを(自前のインフラではなく)Twilioのインフラでホストできることです。

Twilio CLIをインストールしたらターミナルで次のコマンドを実行し、サーバーレスツールキットをインストールします。

twilio plugins:install @twilio-labs/plugin-serverless

このプラグインにより、プロジェクトを作成、実行、展開できるようになります。次のコマンドで新しいプロジェクトを作成します。

twilio serverless:init contact-form --empty

--emptyフラグは、サンプル系のファイルなしでプロジェクトを生成することを意味します。作成したばかりのプロジェクトをテキストエディタで開きます。先ずはメール送信に必要なコードを完成させる部分にフォーカスします。その後、問い合わせフォームのユーザー体験部分を構築していきます。

環境設定

メール送信に必要な環境変数をセットアップします。

ドメイン認証は、送信元検証において一般的な方法です。単一送信元検証はすぐに始められますが、メール送信を商用環境で開始する前にドメイン認証を完了する必要があります。DNSシステム管理へのアクセスが困難な場合は、単一送信元検証で開発を開始し、DNSへのアクセスを確保してからドメイン認証を有効化するという方法もあります。

.envファイルを開き、以下のように等式の右側にあなたの環境に応じた値を入力します。

SENDGRID_API_KEY=<SG.YOUR_API_KEY>
TO_EMAIL_ADDRESS=<YOUR_TO_ADDRESS>
FROM_EMAIL_ADDRESS=<YOUR_FROM_ADDRESS>

それでは、メールを送信するためのコードを記述していきましょう。

メールの送信

Twilio SendGridでメールを送信するには、まずSendGrid Nodeヘルパーライブラリをインストールします。プロジェクトディレクトリで、次のコマンドを実行します。

npm install @sendgrid/mail

ここで、functionsディレクトリにsend-email.jsというファイルを作成し、開きます。先ずは、インストールしたヘルパーライブラリをrequireします。

const sg = require("@sendgrid/mail");

このファイルはTwilio Functionsとして機能するため、次のように3つの引数(context、event、callback)を受け取るhandler関数を作成する必要があります。

exports.handler = async function(context, event, callback) {
}

いよいよメールを送信するためのコードです。まずsend-email.jsに戻り、APIキーでSendGridライブラリーを設定する必要があります。contextオブジェクトには、.envファイルからのすべての環境変数が含まれます。次に、tofromsubjecttextのプロパティを持つオブジェクトを構築します。

exports.handler = async function(context, event, callback) {
  sg.setApiKey(context.SENDGRID_API_KEY);
  const msg = {
    to: context.TO_EMAIL_ADDRESS,
    from: { email: context.FROM_EMAIL_ADDRESS, name: "Your contact form" },
    subject: "New email",
    text: "This is a brand new email.",
  };
}

メールを送信するには、このオブジェクトをsgオブジェクトのsend関数に渡します。これは非同期操作です。そのため結果をawaitし、すべてのものをtry/catchブロックにまとめます。

exports.handler = async function(context, event, callback) {
  sg.setApiKey(context.SENDGRID_API_KEY);
  const msg = {
    to: context.TO_EMAIL_ADDRESS,
    from: { email: context.FROM_EMAIL_ADDRESS, name: "Your contact form" },
    subject: "New email",
    text: "This is a brand new email.",
  };
  try {
    await sg.send(msg);
    return callback(null, "Email sent!");
  } catch (error) {
    console.error(error);
    return callback(error);
  }
}

メール送信に必要なコードは上記スニペットのようになります。ターミナルで次のコマンドを実行し、アプリケーションを開始します。

npm start

次にURL http://localhost:3000/send-email を開きます。コードが実行されます。[Email sent!](メールが送信されました)というメッセージが表示されたら受信ボックスを確認します。メールが到着しているはずです。

エラーが発生した場合は、コンソールに詳細情報がないか確認します。また、コードは正常に実行されるのにメールを受信できない場合は、SendGrid管理コンソールの [Email Activity](メールアクティビティ)のページで詳細を確認してください。

問い合わせフォームの作成

現時点では、このコードはURLをリクエストする度に同じメールを送信するという作りに留まっています。問い合わせフォームを作成するには、件名、メールのコンテンツ、送信者のメールアドレスなどの入力を受け取るフロントエンドが必要です。次に、このユーザー入力を取得し、既存のコードを変更して、提供された情報要素を基に、提供されたメールアドレスを送信するメールのreply-toアドレスに設定して、メールを送信する必要があります。

まず、assetsディレクトリでindex.htmlというファイルを作成し、次のコードを追加するところから始めます。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Contact form</title>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <main>
      <h1>Contact Form</h1>
      <form action="/send-email" method="POST" id="contact-form">
        <div class="form-field">
          <label for="email">Your email address</label>
          <input
            type="email"
            inputmode="email"
            name="from"
            id="from"
            required
          />
        </div>
        <div class="form-field">
          <label for="subject">Subject</label>
          <input type="text" name="subject" id="subject" required />
        </div>
        <div class="form-field">
          <label for="content">Your message</label>
          <textarea name="content" id="content" required></textarea>
        </div>
        <div class="actions">
          <button type="submit" id="submit-button" class="button-primary">
            Send
          </button>
        </div>
      </form>
      <p id="status" class="status" hidden></p>
    </main>
  </body>
</html>

これはHTMLページで、メールアドレスと件名の入力フィールド、そしてメールの本文部分のテキスト領域を含みます。フォームを送信すると、/send-email関数に対するPOSTリクエストが行われます。

見やすくなるように少しスタイルを整えましょう。assetsディレクトリにstyle.cssというファイルを作成し、次のコードを追加します。

* {
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
        Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  padding: 10px;
}

main {
  max-width: 800px;
  width: 100%;
  margin: 0 auto;
}

form label {
  display: block;
}

form input,
form textarea {
  width: 100%;
  font-size: 16px;
}

form button {
  font-size: 24px;
}

.form-field {
  margin-bottom: 1em;
}

.status {
  color: #fff;
  padding: 0.6rem;
  border-radius: 4px;
}

.status.success {
  background-color: rgb(20, 176, 83);
}

.status.error {
  background-color: rgb(214, 31, 31);
}

これでページが少し見やすくなるはずです。

次に、フォームを通じて送信された情報要素を基にメールを送信するようにします。関数を修正する必要がありますので、functions/send-email.jsを再度開きましょう。

フォームからの情報要素がeventオブジェクトを通じて関数に渡されます。次のようにコードを更新して、メールオブジェクトを作成します。

  const msg = {
    to: context.TO_EMAIL_ADDRESS,
    from: { email: context.FROM_EMAIL_ADDRESS, name: "Your contact form" },
    replyTo: event.from,
    subject: `[contactform] ${event.subject}`,
    text: `New email from ${event.from}.\n\n${event.content}`,
  };

この時点では、まだ認証(検証)済みアドレスから自分のメールアドレスに送信している状況(上記スニペットの行2/3の部分)ですが、一方で、他のプロパティは以下のように更新されています。

  • replyToフィールドを追加しました。ユーザーから提供されたメールアドレスが設定されます。
  • 件名は、フォームで提供された件名に[contactform]タグを追加したものになります。このタグにより、受信ボックスでこれらフォーム経由のメールをフィルタリングできるようになります。
  • メールの本文部分は、誰がフォームを送信したのか分かるようにメールアドレスを含めるとともに、フォームのテキストコンテンツを含むようになりました。

また、この関数から戻る際の動作を更新する必要もあります。現時点では文字列またはエラーオブジェクトを返しているだけです。これではユーザーフレンドリーとは言えません。代わりに、インターフェイスで使用できるJSONオブジェクトを戻すようにするとともに、成功・失敗が表示されるようにしましょう。メールを送信する前に、次のようにリターンオブジェクトを作成します。

  const response = new Twilio.Response();
  response.appendHeader("Content-Type", "application/json");
  try {
    await sg.send(msg);

メッセージが正常に送信されたら、成功を示すステータスコード200とJSONオブジェクトを返します。

  try {
    await sg.send(msg);
    response.setStatusCode(200);
    response.setBody({ success: true });
    return callback(null, response);
  }

エラーが発生した場合は、ステータスコード400で失敗を知らせるとともに、APIからエラーを送り返します。

hl_lines="3 4 5 6 7  8 9 10 11"
  } catch (error) {
    console.error(error);
    let { message } = error;
    if (error.response) {
      console.error(error.response.body);
      message = error.response.body.errors[0];  
    }

    response.setStatusCode(400);
    response.setBody({ success: false, error: message }); 
    return callback(null, errorResponse(response, message));
  }

この時点で、関数全体は次のようになります。

exports.handler = async function(context, event, callback) {
  sg.setApiKey(context.SENDGRID_API_KEY);
  const msg = {
    to: context.TO_EMAIL_ADDRESS,
    from: { email: context.FROM_EMAIL_ADDRESS, name: "Your contact form" },
    replyTo: event.from,
    subject: `[contactform] ${event.subject}`,
    text: `New email from ${event.from}.\n\n${event.content}`,
  };
  try {
    await sg.send(msg);
    response.setStatusCode(200);
    response.setBody({ success: true });
    return callback(null, response);
  } catch (error) {
    console.error(error);
    let { message } = error;
    if (error.response) {
      console.error(error.response.body);
      message = error.response.body.errors[0];  
    }

    response.setStatusCode(400);
    response.setBody({ success: false, error: message }); 
    return callback(null, errorResponse(response, message));
  }
}

締めくくりとして、関数からのJSONリターンオブジェクトを参照するために、フォームにJavaScriptコードを追加しましょう。

assets/index.htmlを再度開きます。</main>タグの後に新しい要素<script></script>を追加します。

script要素の中で、フロントエンドJavaScriptの記述を開始します。まず、ページで操作する要素の参照を取得します。

const form = document.getElementById("contact-form");
const fromInput = document.getElementById("from");
const subjectInput = document.getElementById("subject");
const contentInput = document.getElementById("content");
const sendButton = document.getElementById("submit-button");
const status = document.getElementById("status");

メール送信のステータス表示に役立つ関数を、以下のように追加します。

function setStatus(message, klass) {
  status.textContent = message;
  status.classList.add(klass);
  status.removeAttribute("hidden");
}
function hideStatus() {
  status.setAttribute("hidden", "hidden");
  status.className = "status";
  status.textContent = "";
}

上記の関数は、フォームの下の段落部分を表示したり隠したりします。これにより、フォームを通じたメール送信の結果に基づき、成功または失敗のメッセージを表示できます。

最後に、フォーム送信の処理が必要になります。これを行うには、フォームの「submit」イベントをリッスンし、フォームフィールドから情報要素を収集し、その後fetch APIを使用して自分の関数に情報提供します。かなり長いコードになるため、各部の役割を説明するコメントを追加しています。

// Listen for the submit event of the form and handle it [フォーム上のsubmitイベントを監視&処理]
form.addEventListener("submit", async (event) => {
  // Stop the form from actually submitting [フォームの送信処理を停止]
  event.preventDefault();
  // Disable the submit button to avoid double submissions [Sendボタンをグレーアウトし、submitイベントの重複発生を防止]
  sendButton.setAttribute("disabled", "disabled");
  // Hide any status message as we are now dealing with a new submission [以前のステータスメッセージをクリアし、新規submitイベントの処理開始に備える]
  hideStatus();
  try {
    // Submit the data to our function, using the action and method from the form element itself. [フォームのscript要素から情報要素をメール送信用のコードに渡す]
    const response = await fetch(form.getAttribute("action"), {
      method: form.getAttribute("method"),
      // Collect the data from the form inputs and serialize the data as JSON [フォーム入力から情報要素を取り出し、JSON形式にシリアライズする]
      body: JSON.stringify({
        from: fromInput.value,
        subject: subjectInput.value,
        content: contentInput.value,
      }),
      // Set the content type of our request to application/json so the function knows how to parse the data [コンテンツタイプをapplication/jsonにセット]
      headers: {
        "Content-Type": "application/json",
      },
    });
    if (response.ok) {
      // If the response was a success, clear the form inputs and set a success message in the status [レスポンスが成功であれば、フォーム内容をクリアし、ステータスとして成功を表示]
      fromInput.value = "";
      subjectInput.value = "";
      contentInput.value = "";
      setStatus("Message sent successfully. Thank you!", "success");
      // After 5 seconds, hide the success message. [ステータス表示部の成功の記載を5秒後に消去]
      setTimeout(hideStatus, 5000);
    } else {
      // If the request returns an error response, read the error message from the response and set it as a failure message in the status [レスポンスが失敗であれば、エラー情報を読み出し、ステータスとしてエラー情報を表示]
      const data = await response.json();
      console.log(data);
      setStatus(data.error, "error");
    }
    // The request is over, so enable the submit button again [リクエスト処理が一旦終了なので、Sendボタンのグレーアウトを解除]
    sendButton.removeAttribute("disabled");
  } catch (error) {
    // If the request failed, set a generic error message in the status [リクエスト処理が失敗に終わったので、ステータスとして失敗の旨を表示]
    setStatus(
      "There was an error and the message could not be sent. Sorry.",
      "error"
    );
    console.log(error);
    // The request is over, so enable the submit button again [リクエスト処理が一旦終了なので、Sendボタンのグレーアウトを解除]
    sendButton.removeAttribute("disabled");
  }
});
);

JavaScriptコードは以上です。ここまでの流れの中でサーバーを停止していた場合は、npm startで再度開始し、localhost:3000/index.htmlでページを開き、メールのアドレス、件名、本文内容を入力します。フォームを送信すると成功メッセージが表示されると思います。

tsg-form-jp-2

次に受信ボックスを確認します。問い合わせフォームからメールを受信しているはずです。また、メールは自分のドメインから送られていますが、返信すると、フォームで情報提供していたメールアドレスが返信先となることを確認してください。

SendGridによる問い合わせフォーム

本稿では、Twilio SendGrid EmailとTwilio Functionsを活用し、メール問い合わせフォームを構築する方法を解説いたしました。

Twilio Functionsなら、実際にnpm run deployコマンドを使用して、アプリケーションをTwilioインフラストラクチャにデプロイし、任意のユーザに使っていただけます。

この例のコードの完全版は、GitHub上のTwilio Labs「function-templates」リポジトリで確認いただけます。また、このアプリケーションはTwilio Code-Exchangeからもデプロイできます。

メール問い合わせフォームは、Twilio SendGridを使用した構築のスタート地点に過ぎません。他にも、Mars Roverからメールで画像を送信(英語ブログ)したり、Inbound Parse Webhookでメールを受信(英語ブログ)したり、インタラクティブAMPメールを送信(英語ブログ)したりすることができます。

読者の皆さんが構築したアプリケーションのアイデアなど、メールまたはTwitterでぜひお知らせください!Twilio SendGrid Email でコミュニケーションの未来を構築しましょう!