feat: resend verification emails to unverified leads (#287)
## Summary - Adds `leads:resend-verification-emails` artisan command that dispatches `VerifyUserLeadEmailNotification` to all leads where `email_verified_at IS NULL` - Supports `--dry-run` flag to preview the count without sending - Follows the same pattern as `leads:retry-failed-jobs` (progress bar, summary table) ## Test plan - [ ] `--dry-run` shows correct count without dispatching - [ ] Command dispatches exactly one notification per unverified lead - [ ] Verified leads are skipped - [ ] Early exit when no unverified leads exist
This commit is contained in:
parent
f408dbe4c8
commit
5b78509588
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\UserLead;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
#[Signature('leads:resend-verification-emails {--dry-run : Show what would happen without dispatching emails}')]
|
||||
#[Description('Resend verification emails to leads that have not yet verified their email address')]
|
||||
class ResendLeadVerificationEmailsCommand extends Command
|
||||
{
|
||||
public function handle(): int
|
||||
{
|
||||
$isDryRun = $this->option('dry-run');
|
||||
|
||||
if ($isDryRun) {
|
||||
$this->warn('DRY RUN — no emails will be dispatched.');
|
||||
$this->newLine();
|
||||
}
|
||||
|
||||
$leads = UserLead::query()->whereNull('email_verified_at')->get();
|
||||
|
||||
if ($leads->isEmpty()) {
|
||||
$this->info('No unverified leads found.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$dispatched = 0;
|
||||
|
||||
$progressBar = $this->output->createProgressBar($leads->count());
|
||||
$progressBar->start();
|
||||
|
||||
foreach ($leads as $lead) {
|
||||
if (! $isDryRun) {
|
||||
$lead->sendEmailVerificationNotification();
|
||||
}
|
||||
|
||||
$dispatched++;
|
||||
$progressBar->advance();
|
||||
}
|
||||
|
||||
$progressBar->finish();
|
||||
$this->newLine(2);
|
||||
|
||||
$this->table(
|
||||
['Action', 'Count'],
|
||||
[
|
||||
['dispatched'.($isDryRun ? ' (dry run)' : ''), $dispatched],
|
||||
]
|
||||
);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
use App\Models\UserLead;
|
||||
use App\Notifications\VerifyUserLeadEmailNotification;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
it('dispatches verification emails only to unverified leads', function () {
|
||||
Notification::fake();
|
||||
|
||||
$unverified = UserLead::factory()->count(3)->create(['email_verified_at' => null]);
|
||||
UserLead::factory()->create(['email_verified_at' => now()]);
|
||||
|
||||
$this->artisan('leads:resend-verification-emails')
|
||||
->assertSuccessful();
|
||||
|
||||
Notification::assertSentTo($unverified, VerifyUserLeadEmailNotification::class);
|
||||
Notification::assertCount(3);
|
||||
});
|
||||
|
||||
it('does not dispatch emails in dry-run mode', function () {
|
||||
Notification::fake();
|
||||
|
||||
UserLead::factory()->count(2)->create(['email_verified_at' => null]);
|
||||
|
||||
$this->artisan('leads:resend-verification-emails', ['--dry-run' => true])
|
||||
->assertSuccessful();
|
||||
|
||||
Notification::assertNothingSent();
|
||||
});
|
||||
|
||||
it('exits early when there are no unverified leads', function () {
|
||||
Notification::fake();
|
||||
|
||||
UserLead::factory()->create(['email_verified_at' => now()]);
|
||||
|
||||
$this->artisan('leads:resend-verification-emails')
|
||||
->expectsOutput('No unverified leads found.')
|
||||
->assertSuccessful();
|
||||
|
||||
Notification::assertNothingSent();
|
||||
});
|
||||
Loading…
Reference in New Issue