Netlifyには簡単に問い合わせフォームを導入できる機能がありますが、便利に導入できる反面、カスタマイズ性が乏しい、無料プランは月に10MBしかファイルを添付できない、自動返信メール機能がない、などの無視できない制約があります。

しかし、Netlify Functionsの機能を使うことで、上記のデメリットがない使い勝手の良い問い合わせフォームを作ることができます。

以下、添付ファイルありの問い合わせフォームの作り方をご紹介します。

Netlify Functionsフォルダにエンドポイントを作る

フォームが送信された時に発火する特別なイベント「submission-created」があり、このイベントを使うのが正攻法なのですが、実行環境で動作が変わったり、特有の「所作」が面倒なので、オリジナルのエンドポイントを作ります。

例として、functionsフォルダ内に「contact-form」フォルダを作り、その中にcontact-form.tsを作成します。階層は以下の通りです。

netlify
└── functions
    └── contact-form
        └── contact-form.ts

この場合、エンドポイントは.netlify/functions/contact-formになり、ここにデータをpostすることになります。ファイルの中身は後述します。

サイト側のフォーム部分

Netlify Formと違い、専用の属性やタグをフォーム部分に追加する必要はありません。通常のFormと同じ構成で大丈夫です。

私は、非同期の処理が好きなので、送信ボタン押下時にaxiosでpost送信するようにしています。構成は以下の通りです。

return axios("/.netlify/functions/contact-form", {
  method: "post",
  data: this.formData
})
  .then(() => {
    // 成功した時の処理
  })
  .catch(error => {
    // 失敗した時の処理
  })
  }

このコードで重要なポイントは、FormData(multipart/form-data)で送っていないことです。画像をBase64エンコードした上で他のフィールドと同じく文字列でpostしています。

busboyで画像データのパースが上手くいかなかったのが理由です。

メール送信のイベントを作る

先ほど作成したcontact-form.tsファイルに、postされたデータを受け取ってメールを送信するイベントの処理を記述します。

postされたデータは第1引数のevetの中のbodyに格納されていて、それをパースして使います。メールの送信にはNodemailerを使います。

サンプルコードは以下の通りです。

// contact-form.ts

import { Handler } from "@netlify/functions";
import { createTransport } from "nodemailer";

interface formFields {
  name: string;
  email: string;
  message: string;
  attachment: {
    name: string;
    url: string;
  };
}

export const handler: Handler = async event => {
  const { name, email, attachment }: formFields = JSON.parse(event.body!);
  const transporter = createTransport({
    host: "mail.example.com",
    port: 465,
    secure: true,
    auth: {
      user: process.env.MAIL_AUTH_USER,
      pass: process.env.MAIL_AUTH_PASS
    }
  });
  const from = '"Shinobi Works" <no-reply@shinobiworks.com>';

  try {
    // 問い合わせの通知メールを送信
    await transporter.sendMail({
      from,
      to: "administer@example.com",
      replyTo: email,
      subject: "問い合わせがありました",
      attachments: attachment && [
        { filename: attachment.name, path: attachment.url }
      ],
      text: `<名前>\n${name}\n<問い合わせ内容>\n${message}`
    });
  } catch (error) {
    console.log(`メール送信に失敗しました`);
    throw error;
  }

  // 自動返信メール
  await transporter.sendMail({
    from,
    to: email,
    subject: "【Shinobi Works】お問い合わせいただきありがとうございます",
    text: `${name}様\nこの度は...(略)`
  });

  return {
    statusCode: 200,
    body: "Successed to send an email"
  };
};

Base64エンコードされた画像はそのままpathに指定することができます。その他のコードについてはNodemailderの使い方の記事をご覧ください。