Files
timebank-cc-public/app/Console/Commands/RetryFailedMailings.php
Ronald Huynen 2547717edb Initial commit
2026-03-23 21:37:59 +01:00

182 lines
6.1 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Jobs\SendBulkMailJob;
use App\Models\Mailing;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
class RetryFailedMailings extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'mailings:retry-failed
{--mailing-id= : Specific mailing ID to retry}
{--hours= : Retry mailings failed within this many hours (default from config)}
{--dry-run : Show what would be retried without actually retrying}
{--force : Force retry even if within normal retry window}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Retry failed email mailings that are outside their normal retry window';
/**
* Execute the console command.
*/
public function handle()
{
$mailingId = $this->option('mailing-id');
$hours = $this->option('hours') ?: timebank_config('bulk_mail.abandon_after_hours', 72);
$dryRun = $this->option('dry-run');
$force = $this->option('force');
$this->info("Looking for failed mailings" . ($mailingId ? " (ID: {$mailingId})" : " from the last {$hours} hours") . "...");
// Build query for mailings with failures
$query = Mailing::where('status', 'sent')
->where('failed_count', '>', 0);
if ($mailingId) {
$query->where('id', $mailingId);
} else {
$query->where('sent_at', '>=', now()->subHours($hours));
}
$failedMailings = $query->get();
if ($failedMailings->isEmpty()) {
$this->info('No failed mailings found to retry.');
return 0;
}
$this->info("Found {$failedMailings->count()} mailings with failures:");
foreach ($failedMailings as $mailing) {
$this->line("- Mailing #{$mailing->id}: {$mailing->title}");
$this->line(" Failed: {$mailing->failed_count}, Sent: {$mailing->sent_count}, Total: {$mailing->recipients_count}");
$this->line(" Sent at: {$mailing->sent_at}");
}
if ($dryRun) {
$this->info("\n[DRY RUN] Would retry the above mailings. Use --force to actually retry.");
return 0;
}
if (!$force && !$this->confirm('Do you want to retry these failed mailings?')) {
$this->info('Operation cancelled.');
return 0;
}
$totalRetried = 0;
foreach ($failedMailings as $mailing) {
$this->info("\nRetrying mailing #{$mailing->id}: {$mailing->title}");
$retriedCount = $this->retryFailedMailing($mailing, $force);
$totalRetried += $retriedCount;
if ($retriedCount > 0) {
$this->info("Queued {$retriedCount} retry jobs for mailing #{$mailing->id}");
} else {
$this->warn("No recipients to retry for mailing #{$mailing->id}");
}
}
$this->info("\nCompleted! Total retry jobs queued: {$totalRetried}");
return 0;
}
/**
* Retry a specific failed mailing
*/
protected function retryFailedMailing(Mailing $mailing, bool $force = false): int
{
// Check if mailing is still within automatic retry window
$abandonAfterHours = timebank_config('bulk_mail.abandon_after_hours', 72);
$retryWindowExpired = $mailing->sent_at->addHours($abandonAfterHours)->isPast();
if (!$force && !$retryWindowExpired) {
$this->warn("Mailing #{$mailing->id} is still within automatic retry window. Use --force to override.");
return 0;
}
// Get all recipients and group by locale
$recipientsByLocale = $this->getRecipientsGroupedByLocale($mailing);
if (empty($recipientsByLocale)) {
return 0;
}
$jobsQueued = 0;
// Dispatch retry jobs for each locale
foreach ($recipientsByLocale as $locale => $recipients) {
if (!empty($recipients)) {
$contentBlocks = $mailing->getContentBlocksForLocale($locale);
SendBulkMailJob::dispatch($mailing, $locale, $contentBlocks, collect($recipients))
->onQueue('emails');
$jobsQueued++;
}
}
// Reset failure count to allow fresh tracking
if ($jobsQueued > 0) {
$mailing->update([
'failed_count' => 0,
'status' => 'sending' // Reset to sending status
]);
}
return $jobsQueued;
}
/**
* Get recipients grouped by locale for retry
* Note: This is a simplified approach - in a production system you might want to track
* individual recipient failures more precisely
*/
protected function getRecipientsGroupedByLocale(Mailing $mailing): array
{
// Get all potential recipients again
$allRecipients = $mailing->getRecipientsQuery()->get();
if ($allRecipients->isEmpty()) {
return [];
}
// Group by language preference
$recipientsByLocale = [];
foreach ($allRecipients as $recipient) {
$locale = $recipient->lang_preference ?? timebank_config('base_language', 'en');
// Only include locales that have content blocks
$availableLocales = $mailing->getAvailablePostLocales();
if (in_array($locale, $availableLocales)) {
$recipientsByLocale[$locale][] = $recipient;
} else {
// Check if fallback is enabled
if (timebank_config('bulk_mail.use_fallback_locale', true)) {
$fallbackLocale = timebank_config('bulk_mail.fallback_locale', 'en');
if (in_array($fallbackLocale, $availableLocales)) {
$recipientsByLocale[$fallbackLocale][] = $recipient;
}
}
// If fallback is disabled or fallback locale not available, skip this recipient
}
}
return $recipientsByLocale;
}
}