Initial commit
This commit is contained in:
181
app/Console/Commands/RetryFailedMailings.php
Normal file
181
app/Console/Commands/RetryFailedMailings.php
Normal file
@@ -0,0 +1,181 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user