dpl/app/Console/Commands/ReconcileMail.php

60 lines
3.1 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Jobs\SendMailMessageJob;
use App\Models\MailMessage;
use App\Services\MailFlow\DeliveryEvents;
use App\Services\MailFlow\Settings;
use Illuminate\Console\Command;
class ReconcileMail extends Command
{
protected $signature = 'mail:reconcile';
protected $description = 'Recover persisted mail tasks and delivery callbacks without cross-provider duplicates';
public function handle(DeliveryEvents $events): int
{
$enabled = Settings::get('enabled');
if (!$enabled && !\Illuminate\Support\Facades\Schema::hasTable('v2_mail_messages')) { return self::SUCCESS; }
if ($enabled) {
MailMessage::where('direction', 'event')->where('status', 'pending')->orderBy('id')->limit(50)->get()
->each(function ($event) use ($events) {
try { $events->apply($event, true); } catch (\Throwable $e) { /* retain for next poll */ }
});
if (Settings::get('resend_enabled') && Settings::get('resend_key')) {
$messages = MailMessage::where('direction', 'out')->where('channel', 'resend')
->whereNotNull('provider_id')->whereIn('status', ['submitted', 'delayed'])
->where('created_at', '>', now()->subDays(7))
->orderBy('updated_at')->orderBy('id')->limit(10)->get();
foreach ($messages as $message) {
try { $events->refreshStatus($message); }
catch (\Throwable $e) { break; } // Preserve status and retry next cycle, including rate limits.
if (!app()->runningUnitTests()) { usleep(600000); }
}
}
}
MailMessage::where('direction', 'out')->whereNotNull('payload')->where('updated_at', '<', now()->subMinutes(2))
->when(!$enabled, fn ($q) => $q->whereIn('purpose', ['service_expired', 'traffic_reminder', 'expiry_reminder', 'automatic_broadcast']))
->where(function ($query) {
$query->where('status', 'pending')->orWhere('status', 'sending')
->orWhere(fn ($q) => $q->where('status', 'unknown')->where('channel', 'resend'));
})->orderBy('id')->limit(100)->get()->each(function ($message) {
if ($message->attempts >= 6 && $message->status === 'unknown') {
$message->update(['payload' => null]);
return;
}
if ($message->attempts >= 6 && $message->status === 'pending') {
$message->update(['status' => 'failed', 'error_code' => 'mail_retry_exhausted', 'payload' => null]);
return;
}
SendMailMessageJob::dispatch($message->id);
});
MailMessage::where('direction', 'in')->where('created_at', '<', now()->subDays(30))->update(['payload' => null]);
// Verification/login content must not remain indefinitely in encrypted storage.
MailMessage::whereNotNull('payload')->where('direction', 'out')->where('first_attempt_at', '<', now()->subHours(23))
->update(['payload' => null]);
return self::SUCCESS;
}
}