argument('userId'); if ($userId < 1) { $this->error('The user ID must be a positive integer.'); return self::FAILURE; } $user = User::query()->find($userId); if (! $user) { $this->error("User {$userId} was not found."); return self::FAILURE; } $email = strtolower(trim((string) $user->email)); if ($email === '') { $this->error("User {$userId} does not have an email address."); return self::FAILURE; } if ($user->email_verified_at !== null && ! $this->option('force')) { $this->error("User {$userId} already has a verified email address. Use --force to send anyway."); return self::FAILURE; } $token = $this->tokenService->createForUser($userId); $event = EmailSendEvent::query()->create([ 'type' => 'verify_email', 'email' => $email, 'ip' => null, 'user_id' => $userId, 'status' => $this->option('now') ? 'pending' : 'queued', 'reason' => null, 'created_at' => now(), ]); if ($this->option('now')) { return $this->sendNow($user, $event, $token); } SendVerificationEmailJob::dispatch( emailEventId: (int) $event->id, email: $email, token: $token, userId: $userId, ip: null, ); $this->markVerificationEmailSent($user); $this->info("Queued verification email for user {$userId} <{$email}>."); return self::SUCCESS; } private function sendNow(User $user, EmailSendEvent $event, string $token): int { if (! $this->acquireGlobalSendSlot()) { $this->updateEvent($event, 'blocked', 'rate_limited'); $this->error('The global verification email rate limit is currently exhausted. Try again in a minute.'); return self::FAILURE; } if ($this->quotaService->isExceeded()) { $this->updateEvent($event, 'blocked', 'quota'); $this->error('The monthly registration email quota is exceeded.'); return self::FAILURE; } try { Mail::to($user->email)->send(new RegistrationVerificationMail($token)); } catch (\Throwable $exception) { $this->updateEvent($event, 'failed', 'send_error'); $this->error('Failed to send the verification email: ' . $exception->getMessage()); return self::FAILURE; } $this->quotaService->incrementSentCount(); $this->updateEvent($event, 'sent', null); $this->markVerificationEmailSent($user); $email = strtolower(trim((string) $user->email)); $this->info("Sent verification email to user {$user->id} <{$email}>."); return self::SUCCESS; } private function acquireGlobalSendSlot(): bool { $key = 'registration:verification-email:global'; $maxPerMinute = max(1, (int) config('registration.email_global_send_per_minute', 30)); return RateLimiter::attempt($key, $maxPerMinute, static fn () => true, 60); } private function updateEvent(EmailSendEvent $event, string $status, ?string $reason): void { EmailSendEvent::query() ->whereKey($event->getKey()) ->update([ 'status' => $status, 'reason' => $reason, ]); } private function markVerificationEmailSent(User $user): void { $now = now(); $windowStartedAt = $user->verification_send_window_started_at; if (! $windowStartedAt || $windowStartedAt->lt($now->copy()->subDay())) { $user->verification_send_window_started_at = $now; $user->verification_send_count_24h = 1; } else { $user->verification_send_count_24h = ((int) $user->verification_send_count_24h) + 1; } $user->last_verification_sent_at = $now; $user->save(); } }