copy()->addMinutes($windowMinutes); $interviews = Interview::where('status', 'scheduled') ->whereNull('reminder_sent_at') ->whereNotNull('scheduled_at') ->where('scheduled_at', '>=', $now->subMinutes(5)) // Within 5m grace ->where('scheduled_at', '<=', $targetWindow) ->get(); $processedCount = 0; foreach ($interviews as $interview) { // 1. Candidate reminder email if (! empty($interview->candidate_email)) { try { Mail::to($interview->candidate_email) ->queue(new InterviewReminderMail($interview, 'candidate', $interview->candidate_name)); } catch (\Throwable $e) { Log::error('Failed to send 15m reminder to candidate: '.$e->getMessage(), [ 'interview_id' => $interview->id, ]); } } // 2. Panelists reminder emails $assignedUsers = $interview->assignedUsers(); foreach ($assignedUsers as $interviewer) { if (! empty($interviewer->email)) { try { Mail::to($interviewer->email) ->queue(new InterviewReminderMail($interview, 'interviewer', $interviewer->name)); } catch (\Throwable $e) { Log::error('Failed to send 15m reminder to interviewer: '.$e->getMessage(), [ 'interview_id' => $interview->id, 'interviewer_id' => $interviewer->id, ]); } } } $interview->update(['reminder_sent_at' => now()]); $processedCount++; } return $processedCount; } }