Dokumentation durchblättern
Module entwickeln
Benachrichtigungen
Wie Schneespur abgeschlossene Einsätze über kanalbasierte Benachrichtigungen meldet und wie ein Modul einen eigenen Kanal wie Telegram oder SMS ergänzt.
Wenn ein Einsatz abgeschlossen ist, soll oft jemand davon erfahren — die Hausverwaltung, der Kunde, die Disposition. Schneespur löst das über ein kanalbasiertes Benachrichtigungssystem: Der Kern kennt nur das Konzept „Kanal”, die konkreten Wege (E-Mail, Telegram, SMS …) liefern Module nach. So lässt sich ein neuer Benachrichtigungsweg hinzufügen, ohne den Kern anzufassen.
Wie eine Benachrichtigung entsteht
Auslöser ist das JobCompleted-Event. Der Listener SendJobCompletedNotification nimmt es
auf und verteilt die Meldung über alle aktiven Kanäle der NotificationChannelRegistry:
JobCompleted event
→ SendJobCompletedNotification listener
→ FilterRegistry applies 'schneespur.job.notification.recipients'
→ NotificationChannelRegistry::dispatch()
→ FilterRegistry applies 'schneespur.job.notification.channels'
→ Each enabled channel's send() method is called
→ Results logged to notification_logs table
Wichtig sind die zwei Filter-Punkte in diesem Ablauf: Bevor versendet wird, dürfen Module die
Empfängerliste (schneespur.job.notification.recipients) und die Auswahl der aktiven Kanäle
(schneespur.job.notification.channels) anpassen. Dadurch greift ein Modul in den Versand ein,
ohne den Listener selbst zu ersetzen. Mehr zu diesem Mechanismus unter
Filter & Hooks und Events.
Der Kern-Kanal: E-Mail
E-Mail ist bereits eingebaut. Der Kern registriert EmailNotificationChannel, der über
Laravels Mail-System versendet — mit dem Mailable JobCompletedMail. Er beachtet die
Benachrichtigungs-Einstellungen pro Kunde (auto_notify_email, notification_email).
Ein eigener Kanal ist also nur dann nötig, wenn ein anderer Weg als E-Mail dazukommen soll.
Einen eigenen Benachrichtigungs-Kanal bauen
Am Beispiel eines Telegram-Kanals: Das Muster ist immer dasselbe — Interface implementieren, in der Registry anmelden, Einstellungen bereitstellen.
1. Das Interface implementieren
Ein Kanal implementiert NotificationChannelInterface. Die zentrale Methode ist send():
Sie bekommt den abgeschlossenen Job, den Benachrichtigungstyp und einen Kontext und ist
dafür verantwortlich, die Meldung tatsächlich zuzustellen. Die Methoden name(), slug()
und isEnabled() beschreiben den Kanal und sagen, ob er einsatzbereit konfiguriert ist.
namespace Schneespur\Module\Telegram\Notification;
use App\Models\Job;
use App\Models\Setting;
use App\Services\Notification\NotificationChannelInterface;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class TelegramChannel implements NotificationChannelInterface
{
public function send(Job $job, string $type, array $context): void
{
$botToken = Setting::get('telegram.bot_token');
$chatId = Setting::get('telegram.chat_id');
$customer = $job->customer;
$object = $job->customerObject;
$driver = $job->user;
$text = sprintf(
"✅ *Einsatz abgeschlossen*\n" .
"Typ: %s\n" .
"Kunde: %s\n" .
"Objekt: %s\n" .
"Fahrer: %s\n" .
"Dauer: %s",
$job->type->label(),
$customer?->name ?? '—',
$object?->name ?? '—',
$driver?->displayName() ?? '—',
$job->durationFormatted()
);
$response = Http::post("https://api.telegram.org/bot{$botToken}/sendMessage", [
'chat_id' => $chatId,
'text' => $text,
'parse_mode' => 'Markdown',
]);
if (!$response->successful()) {
throw new \RuntimeException('Telegram API error: ' . $response->body());
}
}
public function name(): string
{
return 'Telegram';
}
public function slug(): string
{
return 'telegram';
}
public function isEnabled(): bool
{
return !empty(Setting::get('telegram.bot_token'))
&& !empty(Setting::get('telegram.chat_id'));
}
}
isEnabled() ist mehr als Formsache: Über diese Methode entscheidet der Kanal selbst, ob er
am Versand teilnimmt. Solange Bot-Token und Chat-ID fehlen, bleibt der Kanal still — statt mit
einer halb konfigurierten Verbindung zu scheitern. Geworfene Ausnahmen in send() werden vom
System aufgefangen und protokolliert (siehe unten), bringen den Gesamtversand also nicht zu Fall.
2. Im ServiceProvider registrieren
Angemeldet wird der Kanal in der boot()-Phase des Modul-ServiceProviders, über die
NotificationChannelRegistry. Der erste Parameter ist der Slug, der zweite die Klasse:
protected function registerNotificationChannels(): void
{
$registry = app(NotificationChannelRegistry::class);
$registry->register('telegram', TelegramChannel::class);
}
Wo dieser Aufruf im Lebenszyklus des Moduls steht, beschreibt Das ServiceProvider-Muster.
3. Standard-Einstellungen registrieren
Damit Bot-Token und Chat-ID konfigurierbar sind, registriert das Modul sie als Einstellungen.
Sie landen mit dem Modul-Slug als Präfix in der Tabelle settings:
app(ModuleManager::class)->registerSettings('telegram', [
'bot_token' => '',
'chat_id' => '',
]);
4. Eine Einstellungs-Seite bauen
Zuletzt braucht es einen Admin-Controller und eine Blade-View, in der Token und Chat-ID
eingetragen werden. Bewährt hat sich das test-before-save-Muster: den eingegebenen Wert
erst gegen die echte API prüfen und nur speichern, wenn der Test trägt — so fällt eine
falsche Zugangsangabe sofort auf und nicht erst beim nächsten Einsatz.
Der Benachrichtigungs-Kontext
Das $context-Array, das an send() übergeben wird, enthält in der Regel die Empfänger und
je nach Benachrichtigungstyp weitere Angaben:
[
'recipients' => [
['email' => 'customer@example.com', 'name' => 'Customer Name'],
],
// Additional context depending on notification type
]
Filter: Empfänger anpassen
Über den Filter schneespur.job.notification.recipients ändert ein Modul die Empfängerliste,
bevor versendet wird — etwa um eine feste Kopie an die Disposition zu ergänzen. Der dritte
Parameter (100) ist die Priorität, die die Reihenfolge mehrerer Filter bestimmt:
$filters = app(FilterRegistry::class);
$filters->register('schneespur.job.notification.recipients', function (array $recipients, $job): array {
// Add a CC recipient for all jobs
$recipients[] = [
'email' => Setting::get('telegram.cc_email'),
'name' => 'Dispatcher',
];
return $recipients;
}, 100);
Filter: Steuern, welche Kanäle feuern
Spiegelbildlich entscheidet der Filter schneespur.job.notification.channels, welche Kanäle
für einen konkreten Einsatz überhaupt feuern. So lässt sich ein Kanal gezielt für bestimmte
Einsatztypen aus- oder einschalten:
$filters->register('schneespur.job.notification.channels', function (array $channels, $job): array {
// Only use Telegram for urgent job types
if ($job->type === JobType::Kontrolle) {
unset($channels['telegram']);
}
return $channels;
}, 100);
Versand protokollieren
Jeder Versand wird festgehalten — das ist der Grund, warum eine fehlgeschlagene Zustellung
nicht spurlos verschwindet. Das Modell NotificationLog speichert pro Meldung, über welchen
Kanal, an wen und mit welchem Ergebnis versendet wurde:
// Fields:
notifiable_type // e.g. 'App\Models\Job' or 'App\Models\Customer'
notifiable_id // the model ID
channel // 'email', 'telegram', etc.
type // notification type
recipient // where it was sent
status // 'sent', 'failed'
error_message // failure reason (if any)
metadata // JSON additional data
Ein Kanal sollte für seine Zustellungen selbst einen Log-Eintrag anlegen — das ergibt einen nachvollziehbaren Verlauf, wer wann benachrichtigt wurde:
use App\Models\NotificationLog;
NotificationLog::create([
'notifiable_type' => Job::class,
'notifiable_id' => $job->id,
'channel' => 'telegram',
'type' => 'job_completed',
'recipient' => $chatId,
'status' => 'sent',
'metadata' => ['message_id' => $response->json('result.message_id')],
]);
Weitere denkbare Kanäle
Das gezeigte Muster trägt für beliebige Transportwege. Folgende Kanäle lassen sich nach demselben Schema umsetzen — sie unterscheiden sich nur in Transport und benötigter Konfiguration:
| Channel | Transport | Key Config |
|---|---|---|
| Telegram | HTTP POST to Bot API | Bot token, chat ID |
| SMS (Twilio) | Twilio REST API | Account SID, auth token, from number |
| SMS (Vonage) | Vonage REST API | API key, secret, from number |
| Slack | Incoming Webhook | Webhook URL |
| Push (Firebase) | FCM HTTP v1 API | Service account key |
| Microsoft Teams | Incoming Webhook | Webhook URL |
| Twilio/Meta API | Account credentials |
Jeder dieser Kanäle folgt denselben vier Schritten: NotificationChannelInterface
implementieren, in der NotificationChannelRegistry registrieren, Standard-Einstellungen
anmelden und eine Einstellungs-Seite bereitstellen.