Skip to content

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:

FieldPurpose
NameHuman-readable config name. Unique per subdomain by index.
ChannelTypeEmail, SMS, PushNotification, or WhatsApp.
ProviderTypeProvider enum name stored as string.
CredentialsJsonSerialized provider credential value object.
SenderIdentityFrom address, sender id, or equivalent provider sender.
DisplayNameDisplay name for user-facing sender.
ApplicationTargetOptional target app filter, mainly used by push.
SourceSystemOptional source-system config key.
SubdomainTenant config key inherited from MultiTenantEntity.
IsDefaultPreferred config when multiple configs match.
EnableRetryControls whether retry command is allowed.
MaxRetryAttemptsProvider retry policy field.
IsActiveOnly active configs are used.

Resolution Rules

NotificationConfigProvider.GetConfigAsync(...) resolves configs this way:

  1. Read the current notification context.
  2. If source system exists:
    • match SourceSystem
    • require Subdomain == null
  3. If source system does not exist:
    • match current Subdomain
    • require SourceSystem == null
  4. Match ChannelType.
  5. Match provider type when specified.
  6. Match application target when specified.
  7. Prefer IsDefault == true.
  8. 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 Subdomain is 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 = BussinessOwnerAdmin
  • Subdomain = 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:

  • BaseUrl
  • Email
  • Password

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.

Internal Documentation — Microtec