Skip to content
ExpressとNodemailerを使ってメールを送信してみる記事のサムネイル

ExpressとNodemailerを使ってメールを送信してみる

本記事では、言わずと知れたNode.jsフレームワークのExpressとNodemailerライブラリを使用してGmailでのメール送信を行なってみたいと思います。 今回はメールの送信のみの実装なのでさくっとViteでプロジェクトを作りたいと思います。 自分のWebサイトを作ったけどお問い合わせフォームの実装ができていない方の参考になれば幸いです。

Node.jsバージョン

メール送信機能のプロジェクト作成しますが、動作させるにはNode.jsが必要です。 まずはNode.jsをインストールしましょう。 今回の開発に必要なNode.jsのバージョンは下記です。Node.jsのバージョン管理のツールに私はVoltaを使用しておりますが、お好みのものを使用してください。

  • Node.js 22.11.0
  • npm 10.2.3

Viteでプロジェクトを作成

プロジェクトを作成するディレクトリでターミナルを開き以下のコマンドでViteプロジェクトを作成します。

bash
npm create vite@latest

プロジェクト作成時にいくつか質問されますが、今回私はsample-express-nodemailerというプロジェクト名で作成します。

Project name: ... sample-express-nodemailer
Select a framework: » Vanilla
Select a variant: » TypeScript

Viteのバージョン

今回vite-plugin-nodeパッケージを使用して動作させますが最新のVite6系に対応していない(2024年12月現在)ためViteを5系の最新バージョンに落とします。

bash
npm i -D vite@5

必要なパッケージのインストール

プロジェクトを作成しましたが、まだExpressをインストールしていません。他にも必要なパッケージがあるのでまとめてインストールします。

bash
npm i cors dotenv express nodemailer

今回はTypeScriptを使用するのでtypesファイルと、ViteでNode.jsを動かすためのパッケージもインストールします。

bash
npm i -D @types/cors @types/express @types/node @types/nodemailer vite-plugin-node

不要なファイルの削除

今回は使用しないファイルを最初に削除しておきます。 以下のものを削除してください。

  • srcディレクトリの中身全て
  • publicディレクトリ
  • index.html

vite.config.tsの作成

Viteの設定ファイルであるvite.config.tsファイルをルートディレクトリに作成します。 設定は以下のようにします。

ts
import { defineConfig } from 'vite';
import { VitePluginNode } from 'vite-plugin-node';

export default defineConfig({
  server: {
    port: 3002,
  },
  plugins: [
    ...VitePluginNode({
      adapter: 'express',
      appPath: './src/app.ts',
      exportName: 'viteNodeApp',
      tsCompiler: 'esbuild'
    }),
  ],
  build: {
    minify: true,
  }
});

メール送信機能の実装

ここまでで実装の準備ができました。 これからメールの送信機能を実装していきますがディレクトリを後から分けると長くなってしまうので ディレクトリを分けている状態で進めていきます。 またエントリーファイルから進めたいのですがエラーが出てしまうため最後にエントリーファイルを作成するのをご承知おきください。

.envの作成

環境変数を扱う.envファイルをルートに作成します。

dotenv
MAILER_PORT=465
MAILER_HOST=smtp.gmail.com
MAILER_USER=sample@gmail.com #Gmailのメールアドレス
MAILER_PASS=gmailapplicationpass #アプリパスワードを入力

MAILER_USER、MAILER_PASSはご自身のものを使用してください。 Googleアカウントを持っていない方やアプリパスワードを設定していない方は アカウントの作成とアプリパスワードを設定してから進んでください。

型を定義する

クライアントから送信する値に型を付けます。 srcディレクトリ配下にtypesディレクトリを作成し、そこにsendMailTypes.tsファイルを作成します。

ts
export interface RequestBody {
  name: string;
  message: string;
}

メール送信の処理

ここでNodemailerを使ってメール送信の処理の記述をします。 srcディレクトリの中にserviceディレクトリを作成します。 そこにsendMailService.tsファイルを作成し以下を記述します。

ts
import type { Request } from 'express';
import nodemailer from 'nodemailer';

import type { RequestBody } from '../types/sendMailTypes';

class MailService {
  private transporter;

  constructor() {
    this.transporter = nodemailer.createTransport({
      port: Number(process.env.MAILER_PORT),
      host: process.env.MAILER_HOST,
      auth: {
        user: process.env.MAILER_USER,
        pass: process.env.MAILER_PASS,
      },
      secure: true,
    });
  }

  private isContactRequestBody(body: unknown): body is RequestBody {
    if (typeof body !== 'object' || body === null) return false;
    const { name, message } = body as Record<string, unknown>;

    return typeof name === 'string' && typeof message === 'string';
  }

  async sendContactMail(body: Request): Promise<void> {
    if (!this.isContactRequestBody(body)) {
      throw new Error('Invalid request body');
    }

    const { name, message } = body;

    const textContent = `
      フォームからお問い合わせがありました。
      ======================
      【名前】${name}
      【お問い合わせ内容】${message}
      ======================
    `;

    const toAdminMail = {
      from: process.env.MAILER_USER,
      to: process.env.MAILER_USER,
      subject: `【お問い合わせ】${name}様より`,
      text: textContent,
    };

    await this.transporter.sendMail(toAdminMail);
  }
}

export const sendMailService = new MailService();

ルーターとエンドポイントの設定

Nodemailerでメールの送信処理は実装しました。 今度はルーターの設定とエンドポイントを設定します。 srcディレクトリにroutesディレクトリを作成し、そこにsendMailRoute.tsファイルを作成します。 メソッドをpostで/contactをエンドポイントとします。

ts
import express, { type Request, type Response } from 'express';

import { sendMailService } from '../service/sendMailService';

export const router = express.Router();

router.post('/contact', (req: Request, res: Response) => {
  sendMailService
    .sendContactMail(req.body)
    .then(() => res.status(201).send('ok'))
    .catch(() => res.status(500).send('Internal Server Error'));
});

エントリーファイルの作成

エントリーファイルとなるapp.tsをsrcディレクトリ直下に作成します。

ts
import cors from 'cors';
import { config } from 'dotenv';
import express, { type Application } from 'express';

import { router } from './routes/sendMailRoute';

config();

const app: Application = express();
app.use(cors({}));

//POSTのパラメータを取得
app.use(express.urlencoded({ extended: true }));
app.use(express.json({}));

const port = Number(process.env.PORT) || 3002;
app.listen(port, () => console.log(`Server is running on port ${port}`));
app.use(router);

export const viteNodeApp = app;

これでメール送信の準備ができましたのでクライアント側を作成し、実際に送信してみます。 クライアントはこちらと分けて作成します。

メールフォームの作成

sample-express-nodemailerのプロジェクトと別のところ(私はデスクトップ上に作成します)に index.htmlファイルを作成し以下のHTMLを記述します。 今回はメール送信機能に重きを置いているためフォームに関してはスタイリングなどは一切しません。 またJavaScriptもindex.htmlに直接記述します。

html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>メールの送信</title>
</head>
<body>
  <form>
    <div>
      <label for="name">名前</label>
      <input type="text" name="name" id="name" />
    </div>
    <div>
      <label for="message">お問い合わせ内容</label>
      <textarea name="message" id="message"></textarea>
    </div>
    <button id="submit" type="button">送信する</button>
  </form>

  <script>
    const buttonSubmit = document.querySelector('#submit');
    const inputName = document.querySelector('#name');
    const inputMessage = document.querySelector('#message');

    buttonSubmit.addEventListener('click', () => {
      const formData = {
        name: inputName.value,
        message: inputMessage.value,
      };

      fetch(new Request('http://localhost:3002/contact'), {
        method: 'POST',
        body: JSON.stringify(formData),
        mode: 'cors',
        headers: { 'Content-Type': 'application/json' },
      })
        .then((response) => {
          window.alert('送信できました。');
        })
        .catch((error) => {
          console.error(error);
        });
    });
  </script>
</body>
</html>

ここまでできたら下記コマンドでNode.js側を立ち上げます。

bash
npm run dev

Node.jsが立ち上がったら、実際にindex.htmlをブラウザで開き、フォームの入力をして送信してみます。 Gmailのアドレスにフォームからのメールが来ていれば成功です。

デプロイに関して

当記事のコードをポートフォリオに組み込む際に、クライアントと違うところに置く場合は、以下のように追加で記述が必要です

ts

import cors from 'cors';
import { config } from 'dotenv';
import express, { type Application } from 'express';

import { router } from './routes/sendMailRoute';

config();

const app: Application = express();
app.use(cors({
    origin: 'https://example.com', // アクセス許可するオリジン //
    credentials: true, // レスポンスヘッダーにAccess-Control-Allow-Credentials追加 //
    optionsSuccessStatus: 200, // レスポンスstatusを200に設定 //
  }));

//POSTのパラメータを取得
app.use(express.urlencoded({ extended: true }));
app.use(express.json({}));

const port = Number(process.env.PORT) || 3002;
app.listen(port, () => console.log(`Server is running on port ${port}`));
app.use(router);

export const viteNodeApp = app;
json
"scripts": {
  "dev": "vite",
  "build": "tsc && vite build",
  "preview": "vite preview",
  "start": "node dist/app.js"
}

INFO

クライアント先のリクエスト先も忘れないよう変更してください。

いかがでしたでしょうか。 デプロイに関しては沢山記事があると思いますので解説しませんでした。 ご自身のポートフォリオ等にお問い合わせフォームを組み込むときは調べてみてください。

この記事のサンプルコードはこちら