Appearance
Notification Service
The Notification service is the centralized messaging hub for Microtec ERP. It delivers email, SMS, and push notifications on behalf of all other microservices, decoupling message delivery logic from business logic across the platform.
Overview
| Property | Value |
|---|---|
| Service | Notification.Apis |
| CAE placement | Private CAE |
| Gateway route prefix | /notification-apis/ |
| Source | Platforms/Src/InfrastructureServices/Notification/ |
| Channels | Email, SMS, Push (web + mobile) |
| Template dependency | Template.Apis (renders message bodies) |
| Auth realm | microtec (Keycloak) |
Architecture
Channel Types
| Channel | Enum Value | Delivery Mechanism | Use Cases |
|---|---|---|---|
NotificationChannel.Email | SMTP / SendGrid | Invoices, reports, alerts | |
| SMS | NotificationChannel.Sms | Twilio / local SMS gateway | OTPs, payment reminders |
| Push (Web) | NotificationChannel.WebPush | Firebase Cloud Messaging (FCM) | Real-time in-app alerts |
| Push (Mobile) | NotificationChannel.MobilePush | FCM (Android) + APNs (iOS) | Mobile app alerts |
| In-App | NotificationChannel.InApp | Stored in DB, polled by frontend | Workflow approvals, system messages |
Domain Model
API Endpoints
| Method | Route | Operation |
|---|---|---|
POST | /Notifications/Send | Send an immediate notification |
POST | /Notifications/SendBulk | Send to multiple recipients |
POST | /Notifications/Schedule | Schedule for future delivery |
GET | /Notifications/{Id} | Get notification status |
GET | /Notifications/InApp | Get in-app notifications for current user |
POST | /Notifications/InApp/{Id}/Read | Mark in-app notification as read |
POST | /DeviceTokens/Register | Register FCM/APNs device token |
DELETE | /DeviceTokens/{Id} | Unregister device token |
INotificationPublicApi
All ERP services call the Notification service through this typed client:
csharp
// Defined in Microtec.PublicApi.AppsPortal
public interface INotificationPublicApi
{
/// <summary>
/// Send a notification using a pre-defined template.
/// The Notification service calls Template.Apis to render the body.
/// </summary>
Task SendAsync(
SendNotificationRequest request,
CancellationToken ct = default);
/// <summary>
/// Send the same notification to multiple recipients.
/// </summary>
Task SendBulkAsync(
SendBulkNotificationRequest request,
CancellationToken ct = default);
/// <summary>
/// Schedule a notification for future delivery.
/// </summary>
Task ScheduleAsync(
ScheduleNotificationRequest request,
CancellationToken ct = default);
}SendNotificationRequest
csharp
public record SendNotificationRequest(
int TenantId,
int RecipientUserId,
string RecipientEmail,
string? RecipientPhone,
NotificationChannel Channel,
string TemplateCode,
Dictionary<string, string> TemplateData,
string Language = "ar");Usage Examples
Sending an Invoice Email
csharp
// Inside PostInvoiceCommandHandler
public async Task<ApiResponse> Handle(PostInvoiceCommand request, CancellationToken ct)
{
var invoice = await unitOfWork.Repository<Invoice>().GetByIdAsync(request.Id);
invoice.Post();
await unitOfWork.SaveChangesAsync(ct);
// Notify customer via email
await notificationApi.SendAsync(new SendNotificationRequest(
TenantId: tenantProvider.TenantId,
RecipientUserId: invoice.CustomerId,
RecipientEmail: invoice.CustomerEmail,
RecipientPhone: null,
Channel: NotificationChannel.Email,
TemplateCode: "INVOICE_POSTED",
TemplateData: new()
{
["InvoiceNumber"] = invoice.Number,
["InvoiceDate"] = invoice.InvoiceDate.ToString("yyyy-MM-dd"),
["TotalAmount"] = invoice.TotalAfterTax.ToString("N2"),
["CustomerName"] = invoice.CustomerName
},
Language: "ar"
), ct);
return ApiResponse.Success();
}Sending a Payroll Completion SMS
csharp
await notificationApi.SendAsync(new SendNotificationRequest(
TenantId: tenantProvider.TenantId,
RecipientUserId: employee.Id,
RecipientEmail: string.Empty,
RecipientPhone: employee.MobileNumber,
Channel: NotificationChannel.Sms,
TemplateCode: "PAYROLL_SALARY_CREDITED",
TemplateData: new()
{
["EmployeeName"] = employee.NameAr,
["Amount"] = payrollLine.NetAmount.ToString("N2"),
["Month"] = payrollRun.PeriodMonth.ToString("MMMM yyyy")
},
Language: "ar"
), ct);Bulk Push Notification
csharp
await notificationApi.SendBulkAsync(new SendBulkNotificationRequest(
TenantId: tenantProvider.TenantId,
RecipientUserIds: approverIds,
Channel: NotificationChannel.WebPush,
TemplateCode: "WORKFLOW_APPROVAL_REQUIRED",
TemplateData: new()
{
["EntityType"] = "PurchaseOrder",
["EntityNumber"] = purchaseOrder.Number,
["Amount"] = purchaseOrder.Total.ToString("N2")
},
Language: "ar"
), ct);Template Resolution
When a notification request arrives, the Notification service calls Template.Apis to render the message body:
Notification Statuses
| Status | Meaning |
|---|---|
Pending | Request received, queued for delivery |
Rendering | Calling Template.Apis to render body |
Sending | Submitted to channel provider |
Sent | Provider acknowledged delivery |
Delivered | Confirmed delivery (email open tracking / SMS DLR) |
Failed | Delivery failed after retry exhaustion |
Retry Policy
Failed notifications are retried with exponential back-off:
| Attempt | Delay |
|---|---|
| 1 (initial) | Immediate |
| 2 | 30 seconds |
| 3 | 5 minutes |
| 4 (final) | 30 minutes |
After 4 failed attempts, the status is set to Failed and an in-app alert is generated for the platform admin.
Device Token Registration
Mobile and web apps register their push tokens at login:
http
POST /notification-apis/DeviceTokens/Register
Authorization: Bearer <token>
{
"token": "fcm-or-apns-token-here",
"platform": "Android"
}Platforms: Android, iOS, Web.
Configuration
json
// appsettings.json (Notification.Apis)
{
"Email": {
"Provider": "SendGrid",
"SendGrid": {
"ApiKey": "<keyvault-ref>",
"SenderEmail": "noreply@onlinemicrotec.com.sa",
"SenderName": "Microtec ERP"
}
},
"Sms": {
"Provider": "Twilio",
"Twilio": {
"AccountSid": "<keyvault-ref>",
"AuthToken": "<keyvault-ref>",
"FromNumber": "+9665XXXXXXXX"
}
},
"Push": {
"Fcm": {
"ServerKey": "<keyvault-ref>"
}
},
"Services": {
"Template": "https://mic-erp-be-template.internal.<env>.azurecontainerapps.io"
},
"Keycloak": {
"Authority": "https://<keycloak-host>/realms/microtec",
"Audience": "account"
}
}Provider Selection
The Email:Provider key selects the active email provider. Switching between SendGrid and Smtp does not require code changes — only config. Set to Smtp for local development using a local SMTP relay like MailHog.
Deployment
The Notification service is listed in the pipeline service config:
Devops/azure/config/container-backend/services-config.json → "notification"