Skip to content

SMS, Push & Real-Time — WhatsApp, FCM, SignalR

Granit.Notifications supports a wide range of delivery channels beyond email. Each channel is registered independently and resolved via Keyed Services. For provider configuration options, see the configuration reference.

ProviderPackageKeyed Service key
Azure Communication ServicesGranit.Notifications.AzureCommunicationServices"AzureCommunicationServices"
AWS SNSGranit.Notifications.AwsSns"AwsSns"
TwilioGranit.Notifications.Twilio"Twilio"
BrevoGranit.Notifications.Brevo"Brevo"
context.Services.AddGranitNotificationsSms(opts => opts.Provider = "Brevo");
ProviderPackageKeyed Service key
BrevoGranit.Notifications.Brevo"Brevo"
TwilioGranit.Notifications.Twilio"Twilio"
context.Services.AddGranitNotificationsWhatsApp(opts => opts.Provider = "Brevo");

WhatsApp Business templates are pre-approved per language on Meta’s side. The channel resolves the language per recipient through a cascade: context.Culturerecipient.PreferredCulture → the Granit.Localization.PreferredCulture setting (User → Tenant → Global via ISettingProvider, soft dependency — skipped when Settings is not wired) → WhatsAppChannelOptions.DefaultLanguage (default "fr").

ProviderPackageKeyed Service key
Firebase Cloud Messaging (FCM HTTP v1)Granit.Notifications.GoogleFcm"GoogleFcm"
Azure Notification HubsGranit.Notifications.AzureNotificationHubs"AzureNotificationHubs"
AWS SNS Platform ApplicationGranit.Notifications.AwsSns"AwsSns"
context.Services.AddGranitNotificationsMobilePush(opts => opts.Provider = "GoogleFcm");
context.Services.AddGranitNotificationsGoogleFcm();

FCM authentication uses the OAuth2 bearer flow of the HTTP v1 API: the configured service account key (Notifications:GoogleFcm:ServiceAccountJson) mints access tokens with the firebase.messaging scope. Tokens are cached with a 5-minute refresh margin and minted single-flight; a 401 invalidates the cache and retries once.

Real-time notifications delivered via SignalR hub with optional Redis backplane for multi-instance deployments.

context.Services.AddGranitNotificationsSignalR(
context.Configuration.GetConnectionString("Redis")!);

Map the hub in your application:

context.App.MapHub<NotificationHub>("/hubs/notifications");

W3C Web Push (VAPID, RFC 8030/8291/8292) — no dependency on FCM or US cloud providers, European sovereignty by design.

context.Services.AddGranitNotificationsPush();

Native .NET 10 SSE channel with authentication, per-user connection limits, and bounded message buffers.

context.Services.AddGranitNotificationsSse();

The SSE channel requires authentication (.RequireAuthorization() on the route group) and enforces per-user connection limits to prevent resource exhaustion. When the limit is reached, the endpoint returns 429 Too Many Requests. Message buffers use bounded channels with DropOldest policy.

SSE connections live on a single pod. Add the Redis backplane so a notification published on any replica reaches users connected to the others:

context.Services.AddGranitNotificationsSseRedisBackplane();

Granit.Notifications.Sse.StackExchangeRedis reuses the shared IConnectionMultiplexer (from Granit.Caching.StackExchangeRedis) over a pub/sub channel. Malformed messages are logged, counted (granit.notifications.sse.backplane.message_dropped), and skipped — they never kill the subscriber loop. Running multi-replica without a backplane logs a one-shot warning outside Development, because delivery would silently be partial.

Zulip Bot API chat integration for team notifications.

context.Services.AddGranitNotificationsZulip();