Appearance
Channel Configuration And Credentials
Provider setup is stored in NotificationChannelConfig. The service resolves one active config per channel and optional provider/application/source-system filters before sending.
NotificationChannelConfig
Important fields:
| Field | Purpose |
|---|---|
Name | Human-readable config name. Unique per subdomain by index. |
ChannelType | Email, SMS, PushNotification, or WhatsApp. |
ProviderType | Provider enum name stored as string. |
CredentialsJson | Serialized provider credential value object. |
SenderIdentity | From address, sender id, or equivalent provider sender. |
DisplayName | Display name for user-facing sender. |
ApplicationTarget | Optional target app filter, mainly used by push. |
SourceSystem | Optional source-system config key. |
Subdomain | Tenant config key inherited from MultiTenantEntity. |
IsDefault | Preferred config when multiple configs match. |
EnableRetry | Controls whether retry command is allowed. |
MaxRetryAttempts | Provider retry policy field. |
IsActive | Only active configs are used. |
Resolution Rules
NotificationConfigProvider.GetConfigAsync(...) resolves configs this way:
- Read the current notification context.
- If source system exists:
- match
SourceSystem - require
Subdomain == null
- match
- If source system does not exist:
- match current
Subdomain - require
SourceSystem == null
- match current
- Match
ChannelType. - Match provider type when specified.
- Match application target when specified.
- Prefer
IsDefault == true. - Then order by
Name.
Tenant Configuration
Tenant sends require a subdomain:
json
{
"subdomain": "acme",
"channelType": "Email",
"providerType": "SMTP"
}The client sends X-Subdomain when:
- payload
Subdomainis set .WithSubdomain(...)was used- the current tenant context can resolve a subdomain
Source-System Configuration
System sends use SourceSystem instead of tenant subdomain.
csharp
var payload = NotificationBuilder.Email()
.WithSourceSystem(NotificationSourceSystem.BussinessOwnerAdmin)
.Subject("System alert")
.Body("<p>Something happened.</p>")
.To("ops@example.com")
.Build();For this path, create a channel config with:
SourceSystem = BussinessOwnerAdminSubdomain = null- the target channel type and provider
Provider Credential Shapes
SMTP
json
{
"name": "Main SMTP",
"host": "smtp.example.com",
"port": 587,
"useSsl": true,
"username": "<username>",
"password": "<password>",
"senderIdentity": "no-reply@example.com",
"displayName": "Microtec",
"applicationTarget": null,
"isDefault": true,
"enableRetry": true,
"maxRetryAttempts": 3
}Azure Graph Email
json
{
"name": "Graph Mail",
"tenantId": "<azure-tenant-id>",
"clientId": "<azure-client-id>",
"clientSecret": "<azure-client-secret>",
"senderIdentity": "no-reply@example.com",
"displayName": "Microtec",
"applicationTarget": null,
"isDefault": true,
"enableRetry": true,
"maxRetryAttempts": 3
}FCM
json
{
"name": "ERP FCM",
"projectId": "<firebase-project-id>",
"serviceAccountJson": "{...}",
"webVapidKey": "<optional-web-vapid-key>",
"applicationTarget": "ERP",
"isDefault": true,
"enableRetry": true,
"maxRetryAttempts": 3
}Generic HTTP SMS
json
{
"name": "Default SMS",
"baseUrl": "https://sms-provider.example/send",
"apiToken": "<api-token>",
"apiSecret": "<optional-api-secret>",
"defaultRegionCode": "EG",
"senderIdentity": "Microtec",
"displayName": "Microtec",
"isDefault": true,
"enableRetry": true,
"maxRetryAttempts": 3
}The current Generic HTTP SMS provider posts:
json
{
"recipients": "+201000000000",
"body": "Message body",
"sender": "Microtec"
}with:
text
Authorization: Bearer <api-token>Msegat WhatsApp
json
{
"name": "Msegat WhatsApp",
"baseUrl": "https://communicateapi.t2.sa",
"email": "<msegat-email>",
"password": "<msegat-password>",
"isDefault": true
}The provider stores MsegatWhatsAppCredentials with:
BaseUrlEmailPassword
The direct service command supports baseUrl and defaults it to https://communicateapi.t2.sa. The current shared client request DTO does not expose baseUrl, so client-based setup uses the service default unless the DTO is extended.
Secret Handling
Do not commit real secrets in appsettings files.
Use one of:
- environment variables
- user secrets for local development
- Kubernetes secrets
- Azure Key Vault or another managed secret store
- CI/CD secret injection
Examples in this documentation intentionally use placeholders.
Cache Behavior
Channel config lookups are cached with versioned keys for about one hour. Operationally, when credentials or channel configs change, make sure the command path invalidates cache or restart the service if testing manually and stale config is suspected.
Best Practices
- Keep exactly one default active config per channel, tenant, provider, and application target combination unless there is a deliberate fallback design.
- Use provider enum names exactly as supported by the service.
- Separate tenant configs from source-system configs.
- Store credentials as provider-specific JSON, not loosely shaped arbitrary dictionaries.
- Prefer adding a new provider behind a provider factory instead of branching inside the channel.
- Avoid storing tokens, passwords, or secrets in notification payload
Data.