
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プロジェクトを作成します。
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系の最新バージョンに落とします。
npm i -D vite@5
必要なパッケージのインストール
プロジェクトを作成しましたが、まだExpressをインストールしていません。他にも必要なパッケージがあるのでまとめてインストールします。
npm i cors dotenv express nodemailer
今回はTypeScriptを使用するのでtypesファイルと、ViteでNode.jsを動かすためのパッケージもインストールします。
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ファイルをルートディレクトリに作成します。 設定は以下のようにします。
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ファイルをルートに作成します。
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ファイルを作成します。
export interface RequestBody {
name: string;
message: string;
}
メール送信の処理
ここでNodemailerを使ってメール送信の処理の記述をします。 srcディレクトリの中にserviceディレクトリを作成します。 そこにsendMailService.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をエンドポイントとします。
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ディレクトリ直下に作成します。
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に直接記述します。
<!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側を立ち上げます。
npm run dev
Node.jsが立ち上がったら、実際にindex.htmlをブラウザで開き、フォームの入力をして送信してみます。 Gmailのアドレスにフォームからのメールが来ていれば成功です。
デプロイに関して
当記事のコードをポートフォリオに組み込む際に、クライアントと違うところに置く場合は、以下のように追加で記述が必要です
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;
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"start": "node dist/app.js"
}
INFO
クライアント先のリクエスト先も忘れないよう変更してください。
いかがでしたでしょうか。 デプロイに関しては沢山記事があると思いますので解説しませんでした。 ご自身のポートフォリオ等にお問い合わせフォームを組み込むときは調べてみてください。
この記事のサンプルコードはこちら