Files
ismysiteup.online/MULTI_CHANNEL_NOTIFICATIONS_GUIDE.md
T
2026-01-24 12:16:10 +02:00

16 KiB
Raw Blame History

Multi-Channel Notifications Implementation Guide

This guide provides a complete implementation for multi-channel monitoring alerts in your Laravel application.

Overview

The notification system supports the following channels:

  • Email (built-in Laravel)
  • Database (built-in Laravel)
  • Slack (via webhook)
  • Telegram (via Bot API)
  • Discord (via webhook)
  • Microsoft Teams (via webhook)
  • Google Chat (via webhook)
  • Custom Webhook (generic HTTP POST)
  • SMS (via Twilio/Vonage - requires additional setup)
  • Opsgenie (via API)
  • PagerDuty (via Events API)

Installation Complete

The following packages have been installed:

composer require laravel/slack-notification-channel
composer require laravel-notification-channels/telegram
composer require laravel-notification-channels/discord

Database Schema

Migration created: 2026_01_18_123539_add_notification_channels_to_users_table.php

Fields added to users table:

  • notification_channels (JSON) - Stores enabled channels per alert type
  • slack_webhook_url - Slack incoming webhook URL
  • telegram_chat_id - Telegram bot chat ID
  • discord_webhook_url - Discord webhook URL
  • teams_webhook_url - Microsoft Teams webhook URL
  • google_chat_webhook_url - Google Chat webhook URL
  • webhook_url - Custom webhook endpoint
  • sms_phone_number - Phone number for SMS alerts
  • opsgenie_api_key - Opsgenie API key
  • pagerduty_integration_key - PagerDuty integration key

Alert Types

The system supports these alert types:

  • uptime - Site down/up alerts
  • performance - Performance threshold alerts
  • ssl - SSL certificate expiration
  • dns - DNS record changes
  • broken_links - Broken link detection
  • domain_expiry - Domain expiration warnings
  • sitemap - Sitemap validation issues
  • health_check - Health endpoint failures
  • tcp_port - TCP port monitoring

Base Notification Class

Create app/Notifications/MonitorAlert.php:

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\Discord\DiscordChannel;
use NotificationChannels\Discord\DiscordMessage;
use NotificationChannels\Telegram\TelegramChannel;
use NotificationChannels\Telegram\TelegramMessage;

abstract class MonitorAlert extends Notification implements ShouldQueue
{
    use Queueable;

    public string $alertType;
    public string $siteName;
    public string $message;
    public array $data;
    public string $severity; // info, warning, critical

    public function __construct(string $alertType, string $siteName, string $message, array $data = [], string $severity = 'warning')
    {
        $this->alertType = $alertType;
        $this->siteName = $siteName;
        $this->message = $message;
        $this->data = $data;
        $this->severity = $severity;
    }

    /**
     * Get the notification's delivery channels.
     */
    public function via($notifiable): array
    {
        $enabledChannels = $notifiable->getNotificationChannelsFor($this->alertType);

        $channelMap = [
            'email' => 'mail',
            'database' => 'database',
            'slack' => 'slack',
            'telegram' => TelegramChannel::class,
            'discord' => DiscordChannel::class,
        ];

        $channels = [];
        foreach ($enabledChannels as $channel) {
            if (isset($channelMap[$channel])) {
                $channels[] = $channelMap[$channel];
            }
        }

        return $channels ?: ['database'];
    }

    /**
     * Get the mail representation of the notification.
     */
    public function toMail($notifiable): MailMessage
    {
        $color = $this->getColorForSeverity();

        return (new MailMessage)
            ->subject("[{$this->getSeverityLabel()}] {$this->siteName} - {$this->getAlertTypeLabel()}")
            ->greeting("Alert: {$this->siteName}")
            ->line($this->message)
            ->when(!empty($this->data), function ($mail) {
                foreach ($this->data as $key => $value) {
                    $mail->line("**{$key}:** {$value}");
                }
            })
            ->action('View Dashboard', url('/dashboard'))
            ->line('Timestamp: ' . now()->format('Y-m-d H:i:s T'));
    }

    /**
     * Get the Slack representation of the notification.
     */
    public function toSlack($notifiable): SlackMessage
    {
        $color = $this->getColorForSeverity();

        $message = (new SlackMessage)
            ->from('Site Monitor', ':robot_face:')
            ->to('#monitoring')
            ->attachment(function ($attachment) use ($color) {
                $attachment->title($this->siteName, url('/dashboard'))
                    ->content($this->message)
                    ->color($color)
                    ->timestamp(now());

                foreach ($this->data as $key => $value) {
                    $attachment->field($key, $value, true);
                }
            });

        return $message;
    }

    /**
     * Get the Telegram representation of the notification.
     */
    public function toTelegram($notifiable): TelegramMessage
    {
        $emoji = $this->getEmojiForSeverity();

        $text = "{$emoji} **{$this->getSeverityLabel()} Alert**\n\n";
        $text .= "**Site:** {$this->siteName}\n";
        $text .= "**Type:** {$this->getAlertTypeLabel()}\n\n";
        $text .= "{$this->message}\n\n";

        if (!empty($this->data)) {
            $text .= "**Details:**\n";
            foreach ($this->data as $key => $value) {
                $text .= "• {$key}: {$value}\n";
            }
        }

        $text .= "\n_" . now()->format('Y-m-d H:i:s T') . "_";

        return TelegramMessage::create()
            ->to($notifiable->telegram_chat_id)
            ->content($text)
            ->button('View Dashboard', url('/dashboard'));
    }

    /**
     * Get the Discord representation of the notification.
     */
    public function toDiscord($notifiable): DiscordMessage
    {
        $color = $this->getDiscordColorForSeverity();

        return DiscordMessage::create()
            ->embed(function ($embed) use ($color) {
                $embed->title("{$this->siteName} - {$this->getAlertTypeLabel()}")
                    ->description($this->message)
                    ->color($color)
                    ->timestamp(now());

                foreach ($this->data as $key => $value) {
                    $embed->field($key, $value, true);
                }

                $embed->footer('Site Monitoring Alert');
            });
    }

    /**
     * Get the array representation of the notification (database).
     */
    public function toArray($notifiable): array
    {
        return [
            'alert_type' => $this->alertType,
            'site_name' => $this->siteName,
            'message' => $this->message,
            'severity' => $this->severity,
            'data' => $this->data,
            'timestamp' => now()->toDateTimeString(),
        ];
    }

    /**
     * Get color based on severity
     */
    protected function getColorForSeverity(): string
    {
        return match ($this->severity) {
            'critical' => 'danger',
            'warning' => 'warning',
            default => 'info',
        };
    }

    /**
     * Get Discord color based on severity
     */
    protected function getDiscordColorForSeverity(): int
    {
        return match ($this->severity) {
            'critical' => 0xFF0000, // Red
            'warning' => 0xFFA500,  // Orange
            default => 0x0099FF,    // Blue
        };
    }

    /**
     * Get emoji based on severity
     */
    protected function getEmojiForSeverity(): string
    {
        return match ($this->severity) {
            'critical' => '🚨',
            'warning' => '⚠️',
            default => 'ℹ️',
        };
    }

    /**
     * Get severity label
     */
    protected function getSeverityLabel(): string
    {
        return strtoupper($this->severity);
    }

    /**
     * Get alert type label
     */
    protected function getAlertTypeLabel(): string
    {
        return str_replace('_', ' ', ucwords($this->alertType));
    }
}

Specific Notification Classes

1. UptimeAlert

Create app/Notifications/UptimeAlert.php:

<?php

namespace App\Notifications;

class UptimeAlert extends MonitorAlert
{
    public function __construct(string $siteName, bool $isDown, int $statusCode, string $url, float $responseTime = null)
    {
        $message = $isDown
            ? "Your site is DOWN (HTTP {$statusCode})"
            : "Your site is back ONLINE";

        $data = [
            'Status Code' => $statusCode,
            'URL' => $url,
        ];

        if ($responseTime) {
            $data['Response Time'] = round($responseTime, 2) . 'ms';
        }

        parent::__construct(
            'uptime',
            $siteName,
            $message,
            $data,
            $isDown ? 'critical' : 'info'
        );
    }
}

2. SslExpiryAlert

Create app/Notifications/SslExpiryAlert.php:

<?php

namespace App\Notifications;

use Carbon\Carbon;

class SslExpiryAlert extends MonitorAlert
{
    public function __construct(string $siteName, Carbon $expiresAt, int $daysRemaining)
    {
        $message = $daysRemaining <= 0
            ? "SSL certificate has EXPIRED"
            : "SSL certificate expires in {$daysRemaining} days";

        parent::__construct(
            'ssl',
            $siteName,
            $message,
            [
                'Expires On' => $expiresAt->format('Y-m-d H:i:s'),
                'Days Remaining' => $daysRemaining,
            ],
            $daysRemaining <= 7 ? 'critical' : 'warning'
        );
    }
}

3. DomainExpiryAlert

Create app/Notifications/DomainExpiryAlert.php:

<?php

namespace App\Notifications;

use Carbon\Carbon;

class DomainExpiryAlert extends MonitorAlert
{
    public function __construct(string $siteName, string $domain, Carbon $expiresAt, int $daysRemaining)
    {
        $message = $daysRemaining <= 0
            ? "Domain '{$domain}' has EXPIRED"
            : "Domain '{$domain}' expires in {$daysRemaining} days";

        parent::__construct(
            'domain_expiry',
            $siteName,
            $message,
            [
                'Domain' => $domain,
                'Expires On' => $expiresAt->format('Y-m-d'),
                'Days Remaining' => $daysRemaining,
            ],
            $daysRemaining <= 7 ? 'critical' : 'warning'
        );
    }
}

4. SitemapAlert

Create app/Notifications/SitemapAlert.php:

<?php

namespace App\Notifications;

class SitemapAlert extends MonitorAlert
{
    public function __construct(string $siteName, string $sitemapUrl, string $error)
    {
        parent::__construct(
            'sitemap',
            $siteName,
            'Sitemap validation failed',
            [
                'Sitemap URL' => $sitemapUrl,
                'Error' => $error,
            ],
            'warning'
        );
    }
}

5. PerformanceAlert

Create app/Notifications/PerformanceAlert.php:

<?php

namespace App\Notifications;

class PerformanceAlert extends MonitorAlert
{
    public function __construct(string $siteName, float $responseTime, float $threshold)
    {
        parent::__construct(
            'performance',
            $siteName,
            "Response time exceeded threshold",
            [
                'Response Time' => round($responseTime, 2) . 'ms',
                'Threshold' => round($threshold, 2) . 'ms',
                'Difference' => '+' . round($responseTime - $threshold, 2) . 'ms',
            ],
            'warning'
        );
    }
}

Integration with Existing Jobs

Update your monitoring jobs to send notifications. Example for CheckSitemap:

// In app/Jobs/CheckSitemap.php
use App\Notifications\SitemapAlert;

protected function sendAlert(string $message): void
{
    // Send notification using the new system
    $this->sitemap->site->user->notify(
        new SitemapAlert(
            $this->sitemap->site->name,
            $this->sitemap->url,
            $this->sitemap->last_error
        )
    );

    // Keep existing Notification model creation for backwards compatibility
    Notification::create([
        'user_id' => $this->sitemap->site->user_id,
        'type' => 'sitemap_invalid',
        'message' => "{$message}: {$this->sitemap->url}",
        'data' => json_encode([
            'sitemap_id' => $this->sitemap->id,
            'site_id' => $this->sitemap->site_id,
            'url' => $this->sitemap->url,
            'error' => $this->sitemap->last_error,
        ]),
        'read_at' => null,
    ]);
}

Configuration Files

Services Configuration

Add to config/services.php:

'telegram-bot-api' => [
    'token' => env('TELEGRAM_BOT_TOKEN'),
],

'twilio' => [
    'sid' => env('TWILIO_SID'),
    'token' => env('TWILIO_TOKEN'),
    'from' => env('TWILIO_FROM'),
],

'opsgenie' => [
    'api_key' => env('OPSGENIE_API_KEY'),
    'api_url' => 'https://api.opsgenie.com',
],

'pagerduty' => [
    'api_url' => 'https://events.pagerduty.com/v2/enqueue',
],

Environment Variables

Add to .env:

# Telegram Bot
TELEGRAM_BOT_TOKEN=your_bot_token_here

# Twilio SMS
TWILIO_SID=your_account_sid
TWILIO_TOKEN=your_auth_token
TWILIO_FROM=+1234567890

# Opsgenie
OPSGENIE_API_KEY=your_api_key

# Global defaults (optional)
DEFAULT_SLACK_CHANNEL=#monitoring
DEFAULT_NOTIFICATION_QUEUE=notifications

Notification Settings Controller

The controller and views for managing notification settings will be created separately to allow users to:

  • Configure which channels to use for each alert type
  • Enter webhook URLs and API keys
  • Test notification delivery
  • View notification history

Channel Setup Guides

Slack

  1. Create an Incoming Webhook in your Slack workspace
  2. Copy the webhook URL
  3. Paste in notification settings

Telegram

  1. Create a bot via @BotFather
  2. Get your bot token
  3. Start a chat with your bot
  4. Get your chat ID (use @userinfobot)
  5. Enter chat ID in settings

Discord

  1. Edit channel settings
  2. Go to Integrations > Webhooks
  3. Create webhook
  4. Copy webhook URL

Microsoft Teams

  1. Add "Incoming Webhook" connector to channel
  2. Configure and create
  3. Copy webhook URL

Google Chat

  1. Create a webhook in Google Chat space
  2. Copy webhook URL

Testing Notifications

Create a test command:

php artisan make:command TestNotifications
<?php

namespace App\Console\Commands;

use App\Notifications\UptimeAlert;
use App\Models\User;
use Illuminate\Console\Command;

class TestNotifications extends Command
{
    protected $signature = 'notifications:test {user}';
    protected $description = 'Test notification delivery for a user';

    public function handle()
    {
        $user = User::findOrFail($this->argument('user'));

        $user->notify(new UptimeAlert(
            'Test Site',
            false, // Not down
            200,
            'https://example.com',
            150.5
        ));

        $this->info('Test notification sent!');
    }
}

Best Practices

  1. Queue Notifications: All notifications implement ShouldQueue
  2. Rate Limiting: Implement rate limiting to prevent notification spam
  3. Retry Logic: Configure queue retry attempts
  4. Monitoring: Log notification failures
  5. Testing: Test each channel before production use
  6. Security: Encrypt sensitive webhook URLs and API keys
  7. User Preferences: Respect user notification preferences
  8. Batching: Consider batching similar alerts

Next Steps

  1. Run migration: php artisan migrate
  2. Create notification settings UI
  3. Integrate with existing monitoring jobs
  4. Test each notification channel
  5. Configure queue worker for notifications
  6. Set up monitoring for the notification system itself

Troubleshooting

Notifications not sending

  • Check queue worker is running
  • Verify webhook URLs are correct
  • Check logs: storage/logs/laravel.log
  • Test with notifications:test command

Channel-specific issues

  • Slack: Verify webhook URL format
  • Telegram: Ensure bot token is valid and chat ID is correct
  • Discord: Check webhook hasn't been deleted
  • Email: Verify mail configuration

Future Enhancements

  • Notification grouping (digest emails)
  • Quiet hours configuration
  • Escalation policies
  • Notification templates
  • Webhook retry logic
  • Notification analytics
  • Mobile push notifications
  • In-app notification center