0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Docker を用いた node-cron & nodemailer を使ったメール送信バッチシステム

Last updated at Posted at 2025-02-05

node-cronとnodemailerを利用して

  • DBに登録されたメール送信リクエストを定期的にチェック
  • 未送信のものを5件ずつメールサーバーを介して送信
  • 送信済みフラグを立てる

というシステムを作ってゆく
また、Dockerを用いた開発環境も紹介する

全体像

  1. DB

    • テーブル(mail_queue)に以下の列がある想定
      • to_address(送信先アドレス)
      • subject (件名)
      • body (本文)
      • from_address(送信元アドレス)
      • smtp_host(SMTPサーバー、固定)
      • smtp_id(SMTP認証ユーザー名、固定)
      • smtp_pw(SMTP認証パスワード、固定)
      • smtp_port(SMTPサーバーポート、固定)
      • sent_flag(送信済みフラグ:0=未送信、1=送信済み)
  2. Node.jsアプリケーション

    • node-cronを使って、一定時間ごとにmail_queueテーブルをチェック
    • nodemailerを使って5件ずつメールを送信
    • 送信成功したらsent_flagを1に更新
  3. Docker構成

    • Node.jsアプリ(コンテナ)
    • DB(コンテナ)
    • 必要に応じてメールサーバー(SMTPサーバー)を立てる

ディレクトリ構成

my-mail-system/
├─ docker-compose.yml
├─ backend/
│  ├─ Dockerfile
│  ├─ package.json
│  ├─ package-lock.json
│  └─ src/
│     ├─ index.js
│     └─ db.js (DB接続設定など)
└─ .env 

Node.jsアプリの実装例

1.パッケージインストール

bash
# backend ディレクトリで
npm init -y
npm install node-cron nodemailer pg  # PostgreSQLの場合

2.DB接続設定(db.jsと.envファイル)

環境変数を読み込むためにdotenvパッケージをインストールします

bash
npm install dotenv
.envファイル
DB_HOST=db
DB_USER=user
DB_PASSWORD=pass
DB_NAME=maildb
DB_PORT=5432

プロジェクトのルートディレクトリに.envファイルを作成して、接続情報を記述します。

db.js
const { Pool } = require('pg');
require('dotenv').config(); // .envファイルの読み込み

const pool = new Pool({
  host: process.env.DB_HOST,       // ホスト名
  user: process.env.DB_USER,       // ユーザー名
  password: process.env.DB_PASSWORD, // パスワード
  database: process.env.DB_NAME,   // データベース名
  port: process.env.DB_PORT,       // ポート番号
});

module.exports = pool;

注意点

  1. .envをGitで管理しない
    .envファイルには機密情報が含まれるため、必ず.gitignoreに追加して下さい
  2. 環境変数をDocker Composeで定義する場合
    Docker環境を使用している場合、docker-compose.ymlで環境変数を定義できます
    docker-compose.yml
    services:
      db:
        image: postgres:15
        environment:
          POSTGRES_DB: maildb
          POSTGRES_USER: user
          POSTGRES_PASSWORD: pass
        ports:
          - "5432:5432"
    
  3. 本番環境では安全な方法で管理
    本番環境では、.envファイルではなく、AWS Secrets ManagerやGoogle Cloud Secret Managerなどを使って管理するのがおすすめです。

3.メインロジック(index.js)

index.js
const cron = require('node-cron');
const nodemailer = require('nodemailer');
const pool = require('./db'); // PostgreSQL接続設定

const main = async () => {
  console.log('Worker started');

  // 毎分実行 (例: "* * * * *" -> 1分ごと)
  cron.schedule('* * * * *', async () => {
    console.log('=== Cron job triggered ===');
    try {
      // 1) 未送信のレコードを最大5件取得する
      const res = await pool.query(
        'SELECT id, to_address, subject, body, from_address, smtp_host, smtp_id, smtp_pw, smtp_port FROM mail_queue WHERE sent_flag = $1 LIMIT 5',
        [0]
      );
      const rows = res.rows; // PostgreSQLでは`rows`にデータが格納される

      if (!rows.length) {
        console.log('No unsent mails.');
        return;
      }

      // 2) 取得したレコード分だけメール送信
      for (const mail of rows) {
        const transporter = nodemailer.createTransport({
          host: mail.smtp_host,
          port: mail.smtp_port,
          secure: false, // 587などTLSを使うなら false
          auth: {
            user: mail.smtp_id,
            pass: mail.smtp_pw,
          },
        });

        const mailOptions = {
          from: mail.from_address,
          to: mail.to_address,
          subject: mail.subject,
          text: mail.body,
        };

        try {
          await transporter.sendMail(mailOptions);
          console.log(`Mail sent to: ${mail.to_address}`);

          // 3) 送信成功したらフラグを立てる
          await pool.query(
            'UPDATE mail_queue SET sent_flag = $1 WHERE id = $2',
            [1, mail.id]
          );
        } catch (err) {
          console.error('Error sending mail:', err);
        }
      }
    } catch (err) {
      console.error('Error in cron job:', err);
    }
  });
};

main();

上記は簡易的な例です。実際にはエラーハンドリングやリトライ制御、HTMLメールや添付ファイル対応などが必要に応じて追加されます。

Docker構成

1.docker-compose.yml

docker-compose.yml
version: '3.8'
services:
  db:
    image: postgres:15
    container_name: my-mail-db
    environment:
      - POSTGRES_DB=maildb
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - DB_HOST=db
    ports:
      - "5432:5432"
    volumes:
      - db_data:/var/lib/postgresql/data

  backend:
    build: ./backend
    container_name: my-mail-backend
    depends_on:
      - db
    volumes:
      - ./backend/src:/usr/src/app/src
    ports:
      - "3000:3000"
    env_file:
      - .env

volumes:
  db_data:

docker-compose.yml で .env を明示的に読み込むようにしないとDocker環境では.envが読み込まれないようです

  • dbサービス
    • postgreSQL 15.0 イメージを使用
    • POSTGRES_DB=maildbなど環境変数で初期DBを作成
  • backendサービス
    • ./backendディレクトリにあるDockerfileからビルド
    • depends_onでdbが起動してから起動するようにしている
    • volumesでローカルのソースコードをコンテナ内にマウント(ホットリロードできるように設定)
    • commandでNode.jsアプリ(index.js)を起動

2.Dockerfileの例(backend用)

dockerfile
# backend/Dockerfile
FROM node:18

WORKDIR /usr/src/app

# package.json と package-lock.json をコピー
COPY package*.json ./

# npm install 実行
RUN npm install

# ソースコードをコピー
COPY . .

# ホットリロード(開発用)でなければエントリポイントはCMDでもOK
# CMD ["node", "src/index.js"]

CMD がコメントアウトされているため、コンテナ起動後、起動時に常駐するプロセスが実行されないため何も実行されずに exit 0(正常終了)でコンテナが停止します。開発時はコメントアウトをはずし忘れないこと

3.コンテナ起動

bash
docker-compose up -d --build
  • --buildオプションでDockerfuleに従って再ビルド
  • 立ち上がったら、backendコンテナ内でnode src/index.jsが実行され、node-cronが動き始める

4.DBテーブル作成(PostgrSQL)

sql
CREATE TABLE mail_queue (
  id SERIAL PRIMARY KEY,
  to_address VARCHAR(255) NOT NULL,
  subject VARCHAR(255) NOT NULL,
  body TEXT NOT NULL,
  from_address VARCHAR(255) NOT NULL,
  smtp_host VARCHAR(255) NOT NULL,
  smtp_id VARCHAR(255) NOT NULL,
  smtp_pw VARCHAR(255) NOT NULL,
  smtp_port INTEGER NOT NULL,
  sent_flag INTEGER NOT NULL DEFAULT 0
);

docker-compose で起動後、PostgreSQLコンテナに入って上記クエリを実行したり、環境変数等で初回起動時にマイグレーションを走らせる方法もあります

実行イメージ

  1. docker-compose up -d --buildでコンテナ起動
  2. dbコンテナ(postageSQL)の初期化後、SQLでテーブル作成
  3. mail_queueテーブルに(to_address, subject, body, from_address, smtp_host, smtp_id, smtp_pw, smtp_port, sent_flag=0) をINSERT
  4. backendコンテナ内のnode-cronが毎分未送信レコードをチェック→nodemailerで送信→成功したらsent_flag=1に更新
  5. ログはdocker-compose logs -f backendなどで確認可能

補足(SMTPサーバー)

  • 実際に運用する場合は、信頼できるSMTPサーバーを利用します
  • テスト環境用に一時的にmailhogのようなテスト用SMTPサーバーをdocker-composeで起動して受信確認するケースもあります
    例:docker-compose.ymlにMailhogを追加する場合
yaml
  mailhog:
    image: mailhog/mailhog
    container_name: my-mailhog
    ports:
      - "8025:8025"  # Web UI
  • Mailhogを立ち上げると、SMTP:1025,Web UI: 8025が提供され、ブラウザから http://localhost:8025 で受信メールを簡単に確認できます
  • Node.js側のSMTP設定をhost=mailhog,port=1025にすると、実際のメールの代わりにMailhogのUIで受信を確認できます

まとめ

  • node-cron で一定間隔ごとにDBをチェックして、未送信メールがあればnodemailerで送信→送信済みフラグを更新
  • Docker + docker-composeでNode.jsとDBをコンテナ化し、必要に応じてテスト用SMTPサーバーも起動する
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?