exists && array_diff(array_keys($settings->getDirty()), ['failure_count']) !== []) { $settings->failure_count = 0; } }); } /** * The reason must not contain a provider response body. It is shared with the * team, the team admins, and the instance admins, so it is redacted once more. */ public function recordDeliveryFailure(string $reason): void { $reason = remove_iip($reason); $this->increment('failure_count'); Log::warning('Notification delivery failed', [ 'channel' => $this->notificationChannelName(), 'team_id' => $this->team_id, 'failure_count' => $this->failure_count, 'reason' => $reason, ]); if ($this->failure_count < self::DELIVERY_FAILURE_THRESHOLD || ! $this->isEnabled()) { return; } $this->disableNotificationChannel(); $this->save(); $channel = $this->notificationChannelName(); auditLog('notifications.'.Str::lower($channel).'.auto_disabled', [ 'team_id' => $this->team_id, 'reason' => $reason, ], 'warning'); send_internal_notification(sprintf( "%s notifications disabled for team %s after %d failed deliveries.\nReason: %s", $channel, $this->team_id, self::DELIVERY_FAILURE_THRESHOLD, $reason )); $this->sendChannelDisabledEmail($reason); $this->notifyOtherChannels($reason); } public function recordDeliverySuccess(): void { if ($this->failure_count > 0) { $this->failure_count = 0; $this->save(); } } /** * Tell the team through its other enabled channels. The disabled channel is * not in the list anymore, and the "general" event never uses email. The * root team already gets the internal notification on the same channels. */ private function notifyOtherChannels(string $reason): void { if ($this->team_id === 0 || ! $this->team) { return; } $channel = $this->notificationChannelName(); $this->team->notify(new GeneralNotification( "{$channel} notifications were disabled after ".self::DELIVERY_FAILURE_THRESHOLD." failed deliveries in a row.\nReason: {$reason}\nPlease fix the settings and enable {$channel} notifications again: ".base_url().'/notifications/'.Str::lower($channel), success: false, )); } /** * Tell the team admins through the instance email settings, because the * team's own channel does not work. */ private function sendChannelDisabledEmail(string $reason): void { if (! is_transactional_emails_enabled() || ! $this->team) { return; } try { $channel = $this->notificationChannelName(); $mail = new MailMessage; $mail->subject("Coolify: {$channel} notifications were disabled"); $mail->view('emails.notification-channel-disabled', [ 'channel' => $channel, 'failures' => self::DELIVERY_FAILURE_THRESHOLD, 'reason' => $reason, 'url' => base_url().'/notifications/'.Str::lower($channel), ]); $admins = $this->team->members()->wherePivotIn('role', ['admin', 'owner'])->get(['users.id', 'users.email']); foreach ($admins as $admin) { send_user_an_email($mail, $admin->email, asNotification: true); } } catch (Throwable $e) { Log::warning('Failed to send notification channel disabled email: '.$e->getMessage()); } } }