HTMLメール配信サービスを作ってみた

こちらは、[ゆるWeb勉強会@札幌 Advent Calendar 2021](https://adventar.org/calendars/6270) の2日目の記事です。

## はじめに

ちょっと会社でDBからデータを取得しメールで配信するシステムを開発することになりました。

配信するメールは、プレーンテキストのメールではなく、テーブルで情報をまとめたHTMLメールを配信するシステムです。

HTMLメールを配信するシステムをどのように組めばいいかよくわからなかい状態で、HTMLを構築すればいいんだよね、バックエンドでHTMLを構築するならSSRだよねということで、自分の中で短絡的にReactか？ということで、Next.jsを使用して作っています。

この辺「いや、そうじゃないだろう」というツッコミをいただければと思い記事にしました。

一応ソースは[こちら](https://github.com/shun/mail-publisher-example)にあります。

## 環境構築

まずはNext.jsの環境を作成します。
プロジェクト名は `mail-publish-example` とします。

```bash
$ yarn create next-app mail-publisher-example --typescript
```

とりあえずここで起動することを確認します。

```bash
$ cd mail-publish-example
$ yarn dev
```

http://localhost:3000 にアクセスすることで
Next.js の初期画面が起動することを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/65929/2fe4120e-1df5-9816-0fc2-32aa7cdb9447.png)

## APIを作ってみる

システムの構成としてはweb apiが呼ばれるとデータを取得しメール本文を生成して配信します。

というところでAPIを作ってみます。

### apiの構成

generator で作成したプロジェクトのソースは `pages` ディレクトリ以下に格納されています。

```
pages
├── _app.tsx
├── api
│   └── hello.ts
└── index.tsx
```

api を実装するには `pages/api` 以下に作成したいapi名のファイルを格納します。

今回は `sendmail` というapiを作りたいと思います。

### apiの実装

まずは `sendmail.ts` ファイルをapiディレクトリ以下に作成します。

```
pages
├── _app.tsx
├── api
│   ├── hello.ts
│   └── sendmail.ts
└── index.tsx
```

`hello.ts` を参考にapiを実装します。
ほぼそのまんまのコードですが、新規に作ったものが動くのに意味があると思うので、リネームではなく新規に作成しました。

```typescript
import type { NextApiRequest, NextApiResponse } from 'next'

type Data = {
  status: string
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
  console.log("Hello sendmail api !");
  res.status(200).json({ status: 'OK' })
}
```

ここで新規に作成したAPIが動くかどうかを確認します。
`yarn dev` で動かして、curl でPOSTしてみます。

ちゃんとコンソール上にも `Hello sendmail api !` が出力され、レスポンスも返ってきたことが確認できました。

```
$ curl -XPOST http://localhost:3000/api/sendmail
{"status":"OK"}
```

ここからreactを使ってHTMLの本文を作成していきます。

### メール本文を作成

メール本文のコンポーネントは `MailBody.tsx` とします。

```
pages
├── _app.tsx
├── api
│   ├── hello.ts
│   ├── MailBody.tsx
│   └── sendmail.ts
└── index.tsx
```

メールの本文として、果物の名称の配列を受け取ると、リストとしてレンダリングするとします。

```typescript:MailBody.tsx
export const MailBody = (fruits: string[]) => {
  return (
    <>
      <ul>
      {
        fruits.map((fruit: string, index: number) => (
          <li key={ index }>{ fruit }</li>
        ))
      }
      </ul>
    </>
  )
};
```

メール本文のコンポーネントができたらレンダリングを行います。
レンダリングは `rederToString` 関数で行います。
`sendmail.ts` は以下のようになります。

```typescript:sendmail.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { renderToString } from "react-dom/server";
import { MailBody } from "./MailBody";

type Data = {
  status: string
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {

  const fruits = [
    "apple",
    "banana",
    "orange",
  ];
  const body = renderToString(MailBody(fruits));
  console.log("body", body);
  res.status(200).json({ status: 'OK' })
}
```

実行すると、以下のような文字列がbody変数に格納されているはずです。

```
<ul data-reactroot=""><li>apple</li><li>banana</li><li>orange</li></ul>
```

これで、apiがコールされるとHTMLが動的に構築することができました。

ここまでできると、あとはnodemailerにわたすだけでメールが送信できます。

## メールを送信する。

### nodemail をインストールする

以下のコマンドを実行し、nodemailerを追加します。

```bash
$ yarn add nodemailer
```

メールサーバの情報を作成します。

```typescript
const transporter = createTransport({
  host: "MAIL SERVER ADDRESS",
  port: 465,
  secure: true,
  auth: {
    user: "ACCOUNT NAME",
    pass: "PASSWORD",
  }
});
```

メールサーバの情報を作成しましたら、以下で送信します。

```typescript
export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {

  const fruits = [
    "apple",
    "banana",
    "orange",
  ];
  const body = renderToString(MailBody(fruits));
  try {
    transporter.sendMail({
      from: "hogehoge@examile.com",
      to: "hogehoge2@example.com",
      subject: "mail example",
      html: body,
    });

    res.status(200).json({ status: 'OK' })
  } catch (error) {
    console.error("fail to send mail.", error);
    res.status(500).json({ status: 'NG' })
  }
}
```

メールサーバの設定が正しく行えていれば、  `to` で設定した宛先にメールが届いているはずです。

よくわからないけど、とりあえずHTMLメールを動的に構築して配信するところまでやったので良しとはしていますが、普通が知りたい。。。

ここまでお付き合いしていただきありがとうございます。