feat(cli): app:resend-verification command
Resend the verification link to one user (--user=<id|email>) or every unverified user (--all-unverified), with a --dry-run preview. Uses User::sendEmailVerificationNotification() which is already Throwable- guarded, so SMTP hiccups during a bulk flush log instead of aborting. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
1cefc6ad2d
commit
76584651a6
1 file changed
+74
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resend the email-verification mail to one user or to every unverified user.
|
||||||
|
*
|
||||||
|
* Useful after an SMTP outage: the account was created and the user was told
|
||||||
|
* "we sent you a link", but the mail never left because Gmail 535'd us. Once
|
||||||
|
* the mail config is fixed, this flushes the backlog.
|
||||||
|
*/
|
||||||
|
class ResendVerificationCommand extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'app:resend-verification
|
||||||
|
{--user= : A specific user id OR email to resend to}
|
||||||
|
{--all-unverified : Resend to every user with a null email_verified_at}
|
||||||
|
{--dry-run : Print who would be mailed; do not actually send}';
|
||||||
|
|
||||||
|
protected $description = 'Resend the email-verification link to one user or all unverified users';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
$one = (string) $this->option('user');
|
||||||
|
$all = (bool) $this->option('all-unverified');
|
||||||
|
$dryRun = (bool) $this->option('dry-run');
|
||||||
|
|
||||||
|
if ($one === '' && ! $all) {
|
||||||
|
$this->error('Pass either --user=<id-or-email> or --all-unverified.');
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($one !== '') {
|
||||||
|
$query = ctype_digit($one)
|
||||||
|
? User::where('id', (int) $one)
|
||||||
|
: User::where('email', $one);
|
||||||
|
$users = $query->get();
|
||||||
|
} else {
|
||||||
|
$users = User::whereNull('email_verified_at')->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($users->isEmpty()) {
|
||||||
|
$this->warn('No matching users.');
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sent = 0;
|
||||||
|
$skipped = 0;
|
||||||
|
foreach ($users as $user) {
|
||||||
|
if ($user->hasVerifiedEmail()) {
|
||||||
|
$skipped++;
|
||||||
|
$this->line(" skip #{$user->id} <{$user->email}> — already verified");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($dryRun) {
|
||||||
|
$this->line(" would #{$user->id} <{$user->email}>");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// sendEmailVerificationNotification is already SafeNotifier-wrapped
|
||||||
|
// and will log instead of throwing on SMTP errors.
|
||||||
|
$user->sendEmailVerificationNotification();
|
||||||
|
$sent++;
|
||||||
|
$this->line(" sent #{$user->id} <{$user->email}>");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info($dryRun
|
||||||
|
? "Dry run complete — {$users->count()} candidates, {$skipped} already verified."
|
||||||
|
: "Done — {$sent} sent, {$skipped} skipped.");
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user