invitations()->where('email', $email)->first(); $invitation = $existing ?? $room->invitations()->create([ 'owner_ref' => $room->owner_ref, 'email' => $email, 'user_ref' => $invite['user_ref'] ?? User::where('email', $email)->value('public_id'), 'display_name' => $invite['name'] ?? null, 'role' => $invite['role'] ?? 'participant', 'status' => 'pending', 'sent_at' => now(), ]); if ($existing) { $invitation->update(['sent_at' => now()]); } $this->sendInvitationEmail($room, $invitation, $host); if ($user = User::where('email', $email)->first()) { $user->notify(new MeetingInvitationNotification($room, $host)); } } } public function sendInvitationEmail(Room $room, Invitation $invitation, User $host): void { $this->email->send( $invitation->email, 'Invitation: '.$room->title, $this->invitationBody($room, $host, $invitation), ); } public function meetingStarted(Session $session, User $host): void { AuditLogger::record( $session->owner_ref, 'notification.meeting_started', $session->room->organization_id, $host->ownerRef(), Session::class, $session->id, ); } public function recordingReady(Recording $recording): void { $host = User::where('public_id', $recording->session->room->host_user_ref)->first(); if ($host) { $host->notify(new RecordingReadyNotification($recording)); } if ($host?->email) { $this->email->send( $host->email, 'Recording ready: '.$recording->session->room->title, 'Your meeting recording is ready to view at '.route('meet.recordings.show', $recording), ); } } public function aiSummaryReady(\App\Models\AiSummary $summary): void { $host = User::where('public_id', $summary->session->room->host_user_ref)->first(); if ($host) { $host->notify(new AiSummaryReadyNotification($summary)); } } public function sendReminder(Room $room, User $host): void { $this->email->send( $host->email, 'Reminder: '.$room->title.' starts soon', 'Your meeting starts at '.$room->scheduled_at?->timezone($room->timezone)->format('M j, Y g:i A T')."\n\nJoin: ".$room->joinUrl(), ); $host->notify(new MeetingReminderNotification($room)); foreach ($room->invitations()->where('status', 'accepted')->get() as $invitation) { if (! $invitation->email) { continue; } $this->email->send( $invitation->email, 'Reminder: '.$room->title.' starts soon', 'You are invited to a meeting starting at '.$room->scheduled_at?->timezone($room->timezone)->format('M j, Y g:i A T')."\n\nJoin: ".$room->joinUrl(), ); } } protected function invitationBody(Room $room, User $host, ?Invitation $invitation = null): string { $when = $room->scheduled_at ? $room->scheduled_at->timezone($room->timezone)->format('M j, Y g:i A T') : 'Instant meeting'; $lines = [ $host->name.' invited you to a Ladill Meet session.', '', 'Meeting: '.$room->title, 'When: '.$when, 'Join: '.$room->joinUrl(), $room->passcode ? 'Passcode: '.$room->passcode : '', ]; if ($invitation?->rsvp_token) { $lines[] = ''; $lines[] = 'RSVP: '.$invitation->rsvpUrl(); } return implode("\n", array_filter($lines, fn ($l) => $l !== '')); } }