Lineでスケジューリングしたメールマガジンを送信する方法

## 概要
既存の「メールアドレス宛にスケジュール配信するメールマガジン機能」に加え、Lineにも同様のスケジュール配信を行う仕組みを実装したいという要望は多いです。

この記事では、以下の要件を満たす手順を紹介します。

- メールマガジン用に保存しているテーブル（例：`MailMagazine`）を使い、Line用の送信スケジュールテーブルを新設する
- スケジュールされた時間 (`scheduledAt`) にLineメッセージを送信
- 送信ステータス管理（例：`SCHEDULED` → `SENT` など）

---

## 前提
1. **メールアドレス宛にスケジュール配信する機能** がすでに存在する
2. **メールマガジンの内容を保存するテーブル** (例: `MailMagazine`) がすでに存在する

ここからは、Line向けのスケジュール配信を追加するための流れを説明していきます。

---

## テーブル構成

### 1. `LineMessageSchedule` テーブルの作成

新たに以下のようなテーブルを作成します。  
※ 実際のORMやマイグレーションツールなどの記述に合わせて修正してください。

```sql
CREATE TABLE LineMessageSchedule (
  id INT AUTO_INCREMENT PRIMARY KEY,
  mailMagazineId INT NOT NULL,     -- メールマガジンテーブルとのリレーション
  lineUserId VARCHAR(255) NOT NULL, 
  message TEXT NOT NULL,           -- 送信するメッセージ本文
  scheduledAt DATETIME NOT NULL,   -- スケジュール時刻
  status VARCHAR(50) NOT NULL      -- SCHEDULED / SENT など
);
```

#### 主な項目
- **mailMagazineId** : どのメールマガジンに紐づいているかを示すID  
- **lineUserId** : 送信先のLineユーザーID  
- **message** : 実際に送信する内容（FlexMessageなど）  
- **scheduledAt** : 送信予定日時  
- **status** : ステータス (例：`SCHEDULED` = 送信待ち、`SENT` = 送信済み など)

---

## 実装フロー

### 1. 配信先ユーザーの取得と振り分け

メールマガジン送信先として登録されているグループ情報（`deliveryGroup`）から、Lineユーザーとメールユーザーを振り分けます。

```ts
// 配信先グループを取得
const deliveryGroupsWithUser =
  await this.deliveryGroupService.findAllForMailMagazine({
    deliveryGroupIds:
      deliveryGroupIds ||
      mailMagazine.deliveryGroups.map(
        ({ deliveryGroupId }) => deliveryGroupId,
      ),
  });

// 配信先グループ内で LineUser とメール User に分割
const lineUsers = deliveryGroupsWithUser.filter((user) => user.lineUserId);
const emailUsers = deliveryGroupsWithUser.filter((user) => !user.lineUserId);
```

ここで `lineUserId` を持っているかどうかで Lineかメールかを区別しています。

### 2. Line用メッセージスケジュールの登録

Line向けのスケジュール情報を、先ほど作成した `LineMessageSchedule` テーブルに作成します。  
（ここでは Prisma の `createMany` を利用しています。）

```ts
await this.prisma.lineMessageSchedule.createMany({
  data: await Promise.all(
    lineUsers.map(async (user) => {
      return {
        mailMagazineId: createdMailMagazine.mailMagazineId,
        lineUserId: user.lineUserId,
        // 送信内容を作成 (FlexMessageなど)
        message: this.lineService.createFlexMessage(
          subject,
          body,
        ),
        scheduledAt: deliveryAt,
        status: 'SCHEDULED',
      };
    }),
  ),
});
```

- `lineUserId` : 配信対象ユーザー（Lineユーザー）  
- `message` : 送信するLineメッセージ（FlexMessageなどを生成）  
- `scheduledAt` : ユーザーが指定した配信日時  

### 3. 定期実行 (Cron) での送信処理

スケジュールが登録されたLineメッセージを定期的にチェックし、送信予定時刻になったら実際にLineへpushメッセージを送ります。  
以下の例では、Node.js の `cron` あるいは NestJS の `@Cron` を使用し、毎分実行として設定しています。

```ts
@Cron('* * * * *') // 毎分実行
async sendScheduledLineMessages() {
  const now = new Date();
  
  // ステータスが SCHEDULED で、scheduledAt <= 現在時刻 のメッセージを取得
  const schedules = await this.prisma.lineMessageSchedule.findMany({
    where: {
      scheduledAt: {
        lte: now,
      },
      status: 'SCHEDULED',
    },
    include: {
      mailMagazine: true, // 必要に応じてメールマガジンの情報も取得
    },
  });

  // 取得したスケジュールを順次送信
  for (const schedule of schedules) {
    try {
      // 送信処理 (Push Message)
      await this.lineClient.pushMessage({
        to: schedule.lineUserId,
        messages: [schedule.message],
      });

      // ステータスを SENT に更新 (例)
      await this.prisma.lineMessageSchedule.update({
        where: { id: schedule.id },
        data: { status: 'SENT' },
      });
    } catch (error) {
      console.error('Line message send error:', error);
      // エラー時のリトライやステータス管理は適宜実装
    }
  }
}
```

1. **スケジュール一覧取得** : `scheduledAt` が現在時刻以前、かつステータスが `SCHEDULED` のレコードを取得  
2. **実際の送信処理** : `lineClient.pushMessage` などでLineに送信  
3. **ステータス更新** : 送信成功後 `SENT` などに更新し、再度送信されないようにする  

---

## まとめ

以上のように、既存のメールマガジンのスケジュール配信機能に加えて、Lineにも同様の仕組みを組み込むことができます。基本的な流れは以下です。

1. **Line用のスケジュールテーブル** (`LineMessageSchedule`) を新設  
2. **配信先ユーザー** をメール・Lineで振り分ける  
3. **スケジュール情報** を登録し、cronなどで定期的に送信を実行  


参考になれば幸いです！質問等あればコメントでお待ちしています。