Skip to content

Notifications Configuration — Options & Health Checks

This page documents all configuration options, health checks, and OpenTelemetry instrumentation for the notification engine and its providers.

{
"Notifications": {
"MaxParallelDeliveries": 8
}
}
PropertyDefaultDescription
MaxParallelDeliveries8Max concurrent delivery messages (Wolverine queue parallelism)
{
"Notifications": {
"Email": {
"Provider": "Smtp",
"SenderAddress": "noreply@clinic.example.com",
"SenderName": "Clinic Portal"
}
}
}
{
"Notifications": {
"Smtp": {
"Host": "smtp.example.com",
"Port": 587,
"UseSsl": true,
"Username": "user",
"Password": "vault:secret/data/smtp#password",
"TimeoutSeconds": 30
}
}
}
{
"Notifications": {
"Brevo": {
"ApiKey": "vault:secret/data/brevo#api-key",
"DefaultSenderEmail": "noreply@clinic.example.com",
"DefaultSenderName": "Clinic Portal",
"DefaultSmsSenderId": "CLINIC",
"BaseUrl": "https://api.brevo.com/v3",
"TimeoutSeconds": 30
}
}
}
{
"Notifications": {
"SendGrid": {
"ApiKey": "vault:secret/data/sendgrid#api-key",
"DefaultSenderEmail": "noreply@clinic.example.com",
"DefaultSenderName": "Clinic Portal",
"SandboxMode": false,
"TimeoutSeconds": 30
}
}
}
{
"Notifications": {
"Twilio": {
"AccountSid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"AuthToken": "vault:secret/data/twilio#auth-token",
"DefaultSmsFrom": "+15551234567",
"DefaultWhatsAppFrom": "whatsapp:+14155238886",
"MessagingServiceSid": null,
"TimeoutSeconds": 30
}
}
}
{
"Notifications": {
"Sms": {
"Provider": "Brevo",
"SenderId": "CLINIC"
}
}
}
{
"Notifications": {
"WhatsApp": {
"Provider": "Twilio",
"DefaultLanguage": "fr"
}
}
}
PropertyDefaultDescription
ProviderKeyed Service name of the WhatsApp sender ("Twilio", "Brevo")
DefaultLanguage"fr"Terminal fallback for the template language

The template language is resolved per recipient through a cascade: context.Culturerecipient.PreferredCulture → the Granit.Localization.PreferredCulture setting (User → Tenant → Global via ISettingProvider, soft dependency) → DefaultLanguage.

{
"Notifications": {
"MobilePush": {
"Provider": "GoogleFcm"
},
"GoogleFcm": {
"ProjectId": "my-firebase-project",
"ServiceAccountJson": "vault:secret/data/fcm#service-account",
"BaseAddress": "https://fcm.googleapis.com/",
"TimeoutSeconds": 30
}
}
}

ServiceAccountJson is the Google service account key consumed to mint OAuth2 bearer tokens for the FCM HTTP v1 API (scope https://www.googleapis.com/auth/firebase.messaging). Tokens are cached with a 5-minute refresh margin and minted single-flight — concurrent sends never trigger parallel token requests; a 401 invalidates the cache and retries once.

Azure Communication Services (Email + SMS)

Section titled “Azure Communication Services (Email + SMS)”

One package, two capabilities. Each capability is registered only when its configuration section exists — an SMS-only host never wires (or validates) the email sender, and vice versa.

{
"Notifications": {
"AzureCommunicationServices": {
"Email": {
"ConnectionString": "endpoint=...;accesskey=...",
"SenderAddress": "noreply@mydomain.com",
"TimeoutSeconds": 120
},
"Sms": {
"ConnectionString": "endpoint=...;accesskey=...",
"FromPhoneNumber": "+15551234567",
"TimeoutSeconds": 30
}
}
}
}

One package, two capabilities, same SNS SDK. Each capability is registered only when its configuration section exists.

{
"Notifications": {
"AwsSns": {
"Sms": {
"Region": "eu-west-1",
"SenderId": "MYAPP",
"SmsType": "Transactional",
"TimeoutSeconds": 30
},
"MobilePush": {
"Region": "eu-west-1",
"PlatformApplicationArn": "arn:aws:sns:eu-west-1:123456789:app/GCM/my-app",
"TimeoutSeconds": 30
}
}
}
}

Notifications:AwsSns:Sms:

PropertyDefaultDescription
RegionAWS region (required)
SenderIdnullAlphanumeric sender ID (1-11 chars, region-dependent)
SmsType"Transactional""Transactional" or "Promotional"
OriginationNumbernullE.164 originating number
AccessKeyIdnullAWS access key (uses default credential chain when null)
SecretAccessKeynullAWS secret key (required if AccessKeyId set)

Notifications:AwsSns:MobilePush:

PropertyDefaultDescription
RegionAWS region (required)
PlatformApplicationArnSNS Platform Application ARN (required)
AccessKeyIdnullAWS access key (uses default credential chain when null)
SecretAccessKeynullAWS secret key (required if AccessKeyId set)
{
"Notifications": {
"AzureNotificationHubs": {
"ConnectionString": "Endpoint=sb://...",
"HubName": "my-notification-hub",
"TimeoutSeconds": 30
}
}
}
{
"Notifications": {
"SignalR": {
"RedisConnectionString": "redis:6379"
}
}
}
{
"Notifications": {
"WebPush": {
"VapidSubject": "mailto:admin@clinic.example.com",
"VapidPublicKey": "BFx...",
"VapidPrivateKey": "vault:secret/data/webpush#private-key"
}
}
}
{
"Notifications": {
"Sse": {
"HeartbeatIntervalSeconds": 30,
"MaxConnectionsPerUser": 10,
"MaxBufferSize": 100
}
}
}
OptionDefaultDescription
HeartbeatIntervalSeconds30Keep-alive interval through proxies
MaxConnectionsPerUser10Concurrent SSE connections per user (covers multiple tabs/devices)
MaxBufferSize100Messages buffered per connection before oldest are dropped

SSE connections are per-pod: without a backplane, a notification published on pod A never reaches a user connected to pod B. Add Granit.Notifications.Sse.StackExchangeRedis to fan deliveries out over Redis pub/sub (reuses the shared IConnectionMultiplexer from Granit.Caching.StackExchangeRedis):

builder.Services.AddGranitNotificationsSseRedisBackplane();
{
"Notifications": {
"Sse": {
"StackExchangeRedis": {
"ChannelName": "granit-notifications-sse"
}
}
}
}
OptionDefaultDescription
ChannelName"granit-notifications-sse"Redis pub/sub channel carrying the SSE envelopes

Malformed backplane messages are logged, counted (granit.notifications.sse.backplane.message_dropped), and skipped — a poison message never kills the subscriber loop. Outside the Development environment, running the SSE channel without a backplane logs a one-shot warning at the first delivery, because multi-replica delivery would silently be partial.

{
"Notifications": {
"Zulip": {
"DefaultStream": "alerts",
"DefaultTopic": "system",
"Bot": {
"BaseUrl": "https://zulip.example.com",
"BotEmail": "notification-bot@zulip.example.com",
"ApiKey": "vault:secret/data/zulip#api-key",
"TimeoutSeconds": 30
}
}
}
}

Notification channel providers register opt-in health checks:

builder.Services.AddHealthChecks()
.AddGranitSmtpHealthCheck()
.AddGranitAwsSesHealthCheck()
.AddGranitBrevoHealthCheck()
.AddGranitAcsEmailHealthCheck()
.AddGranitAcsSmsHealthCheck()
.AddGranitAzureNotificationHubsHealthCheck()
.AddGranitAwsSnsSmsHealthCheck()
.AddGranitAwsSnsMobilePushHealthCheck()
.AddGranitGoogleFcmHealthCheck()
.AddGranitScalewayEmailHealthCheck()
.AddGranitSendGridHealthCheck()
.AddGranitTwilioHealthCheck()
.AddGranitZulipHealthCheck();
ProviderExtensionProbeTags
SMTPAddGranitSmtpHealthCheck()EHLO handshake via MailKitreadiness
SESAddGranitAwsSesHealthCheck()GetAccount() — Degraded if sending pausedreadiness, startup
BrevoAddGranitBrevoHealthCheck()GET /accountreadiness
ACS EmailAddGranitAcsEmailHealthCheck()Non-mutating config/credential validation (never sends)readiness
ACS SMSAddGranitAcsSmsHealthCheck()Non-mutating config/credential validation (never sends)readiness
Azure Notification HubsAddGranitAzureNotificationHubsHealthCheck()Hub description retrievalreadiness
SNS SMSAddGranitAwsSnsSmsHealthCheck()SNS API connectivity checkreadiness, startup
SNS Mobile PushAddGranitAwsSnsMobilePushHealthCheck()SNS Platform Application checkreadiness, startup
Google FCMAddGranitGoogleFcmHealthCheck()Config validation (ProjectId + parseable service account)readiness, startup
Scaleway TEMAddGranitScalewayEmailHealthCheck()GET /emails?page_size=1readiness, startup
SendGridAddGranitSendGridHealthCheck()GET /scopesreadiness, startup
TwilioAddGranitTwilioHealthCheck()GET /Accounts/{sid}.jsonreadiness, startup
ZulipAddGranitZulipHealthCheck()GET /api/v1/users/me (Bot auth)readiness

All checks sanitize error messages — credentials, hostnames, and API keys are never exposed in the health check response. Every check enforces a 10-second defensive timeout via .WaitAsync() to prevent blocking Kubernetes probe cycles.

All fan-out and delivery operations are traced via ActivitySource:

Activity nameDescription
notifications.fanoutFan-out of a NotificationTrigger into delivery commands
notifications.deliverDelivery of a single DeliverNotificationCommand via a channel

Tags: notifications.type, notifications.channel, notifications.delivery_id, notifications.notification_id, notifications.recipient_count, notifications.delivery_count, notifications.success.