「バッチ処理」で経過日数を「毎朝4時」にメールで送る方法【Notification + バッチ処理を組むやり方】

# やりたいこと
・Laravel 9（PHP 8）で、毎日朝4時に「ポートフォリオの作成日数」をユーザーの登録メール宛に送信したい。

・ユーザーごとに「未完了のポートフォリオ」の経過日数を計算して通知する。



# 環境
Laravel 9
PHP 8
Mailpit



# 実装手順
## ① Artisanコマンドを作成
```bash
php artisan make:command SendDevDaysNotification
```
作成されるファイル：
```
app/Console/Commands/SendDevDaysNotification.php
```



## ② Markdown対応の通知クラスを作成
```bash
php artisan make:notification DevDaysNotification --markdown=emails.devdays
```
作成されるファイル：
```bash
# 通知（Notification）クラス本体
app/Notifications/DevDaysNotification.php
# メールのMarkdownテンプレート
resources/views/emails/devdays.blade.php
```



## ③コマンドに処理を書く
```app/Console/Commands/SendDevDaysNotification.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Models\User;
use App\Notifications\DevDaysNotification;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;

class SendDevDaysNotification extends Command
{
    protected $signature = 'notify:send-devdays';
    protected $description = '毎朝、DevDaysの経過日数を通知';

    public function handle()
    {
        $users = User::with(['counts' => function ($q) {
            $q->where('is_completed', false)->latest('started_at');
        }])->get();

        foreach ($users as $user) {
            $count = $user->counts->first();
            if ($count) {
                $days = Carbon::parse($count->started_at)->diffInDays(now());
                $user->notify(new DevDaysNotification($user->name, $days));
                Log::info("通知を送信: {$user->email}（{$days}日）");
            }
        }

        $this->info('通知の送信が完了しました。');
    }
}
```



## ④Notificationの中身を書く
```app/Notifications/DevDaysNotification.php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;

class DevDaysNotification extends Notification
{
    use Queueable;

    protected $name;
    protected $days;

    public function __construct($name, $days)
    {
        $this->name = $name;
        $this->days = $days;
    }

    public function via($notifiable)
    {
        return ['mail'];
    }

    public function toMail($notifiable)
    {
        return (new MailMessage)
            ->subject('【DevDays】現在の経過日数のお知らせ')
            ->markdown('emails.devdays', [
                'name' => $this->name,
                'days' => $this->days,
            ]);
    }
}
```



## ⑤Markdownメールテンプレートを書く
```resources/views/emails/devdays.blade.php

@component('mail::message')
# DevDays 経過日数のご案内

{{ $name ?? 'お客様' }} さん、こんにちは！

現在の DevDays は開始から **{{ $days }}日** 経過しました。

引き続きがんばっていきましょう！

@endcomponent
```



## ⑥スケジュール登録（毎朝4時）
```app/Console/Kernel.php

protected function schedule(Schedule $schedule)
{
    $schedule->command('notify:send-devdays')->dailyAt('04:00');
}
```



## ⑦通知のキュー処理対応（任意）
### ▶️ キューなし（同期処理）の場合
そのまま使えます。
$user->notify(...) 実行時に、即座にメールが送られます。
キュー設定やワーカー起動も 不要 です。
ただし、通知対象が多いとコマンド実行が重くなります。


### ▶️ キューあり（非同期処理）の場合
大量ユーザーに通知を送る場合は、キューを使うことで高速かつ安全に送れます。

#### ⑦-① .env をキュー用に変更
```.env
QUEUE_CONNECTION=database
```

##### 補足
開発中は .env を
```.env
# QUEUE_CONNECTION=database
QUEUE_CONNECTION=sync
```
に戻すと、即時送信されるのでデバッグしやすくなります。


#### ⑦-② ジョブテーブルを作成・マイグレート
```bash
php artisan queue:table
php artisan migrate
```
jobs テーブルが生成され、通知がここに一旦蓄積されます。



#### ⑦-③ queue:work を起動（メールを送信する実行プロセス）
```bash
php artisan queue:work
```
上記コマンドを起動しておくことで、jobs テーブルにたまった通知が順番に処理されます。



## ⑧テスト実行（手動）
```bash
pwd
# /Applications/MAMP/htdocs/Laravel(任意フォルダ)/dev_days(任意プロジェクト名)

mailpit
# http://localhost:8025/ にログイン
```

```bash
php artisan notify:send-devdays
```


### mailpit 設定方法
https://qiita.com/honaki/items/2906571f60a95c5b4f37#mailpit-%E3%81%AE%E5%B0%8E%E5%85%A5%E3%81%A8%E8%A8%AD%E5%AE%9A%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83



## ⑨スケジュール確認用テスト（1分ごとに実行）
```app/Console/Kernel.php

protected function schedule(Schedule $schedule)
{
    // $schedule->command('notify:send-devdays')->dailyAt('04:00');
    // スケジュールを一時的に変更：
    $schedule->command('notify:send-devdays')->everyMinute();
}
```
実行コマンド：
```bash
php artisan schedule:run
```



## ⑩本番環境(エックスサーバー)：cron + queue:work
### ① Laravelの .env を queue対応にしておく
```.env
QUEUE_CONNECTION=database
```


### ② php artisan queue:table と php artisan migrate を 本番でも実行しておく
```bash
php artisan queue:table
php artisan migrate
```
※ jobs テーブルが本番DBに作られていないとキューは動きません。



### ③ cronの登録（Xserverのサーバーパネル → cron設定）
以下を1分ごとに登録：
```bash
* * * * * cd /home/akkun1114/akkun1114.com/your_project && /usr/bin/php8.1 artisan queue:work --stop-when-empty >> /dev/null 2>&1
```

#### 補足：
/home/akkun1114/akkun1114.com/your_project ← Laravelプロジェクトのルート

/usr/bin/php8.1 ← 実行するPHPのパス（Xserverのサーバーパネルから確認可能）



### ④ スケジュール実行のための schedule:run も別途必要（Xserverのサーバーパネル → cron設定）
実は queue:work だけではスケジュール登録された Artisanコマンド（notify:send-devdays）が動きません。

なので、もう1本、以下のcronも登録する：
```bash
* * * * * cd /home/akkun1114/akkun1114.com/your_project && /usr/bin/php8.1 artisan schedule:run >> /dev/null 2>&1
```


### どうなるか？
・毎分cronで queue:work を1回だけ実行
・jobs テーブルにキューがあれば1つずつ処理して終わる
・処理がなくなればすぐ終了する
・Supervisor とは違い、常駐しない（共有サーバー向けの運用）



### まとめ：エックスサーバーでのキュー対応はこうする
| 項目              | 対応方法                               |
| --------------- | ---------------------------------- |
| Supervisor      | ❌ 使用不可                             |
| systemd         | ❌ 使用不可                             |
| queue\:work を常駐 | ❌ 不可                               |
| 対応策             | ✅ cron + `--stop-when-empty` を毎分実行 |




## おまけ
### .env の設定サンプル
```.env
MAIL_MAILER=smtp
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="DevDays"

QUEUE_CONNECTION=database
```



# 関連
https://qiita.com/honaki/items/b76a5f3db695eb043828