Communication — Codebase flow
Maps the send pipeline to workers & files in Tux.
Workers
| Worker | Role |
|---|---|
Tux.Workers.Notification.Scheduler | Scans NotificationScheduler (and MessagingJob) Ready; evaluates Notification Settings; pushes work to Generator. |
Tux.Workers.Notification.Generator | Renders template, resolves recipients; creates UserMessage and/or OutgoingMessage. |
Tux.Workers.Notification.Distributor | "Claims" then sends for real through provider (SendGrid/Twilio). |
| Webhook & Tracker | Receives provider callbacks, updates OutgoingMessage state. |
Worker Automation Scheduler | Scans AutomationScheduler (reminder/report), pushes delivery work to NotificationScheduler. |
Generator: route by notification type
Generator uses the type to select a render handler. Example id-only handlers (read EventData as entity id):
// Trong Generator
var id = JsonConvert.DeserializeObject<int>((string)job.EventData);
// route: IncidentSubmitted -> HandleNotificationAsync -> HandleIncidentSubmittedAsync
// -> NotificationService.SendIncidentSubmittedNotification(...)Some sample handlers: HandleAccountNewRegistration_EmployeeAsync, HandleStaffProfileUpdateAsync, HandleIncidentSubmittedAsync. Each handler resolves recipients (from NotificationSetting subscribers + data in the job), renders template, and creates OutgoingMessage.
Distributor: claim before send
// Pseudo: cập nhật có điều kiện
// UPDATE OutgoingMessage SET Status = Sending
// WHERE PartitionKey=@pk AND RowKey=@rk AND Status IN (Created, RetryReady)
// Nếu 0 dòng bị đổi → tin đã/đang gửi → thoát, KHÔNG gọi providerAfter successful send → SentToProvider + store provider message id. Webhook advances to Delivered/Opened.
Campaign
MessagingJob (SQL) coordinates campaign/report payload; MessageJobBlobName points to the messaging-job container (name/path only — API authorizes before exposing content). Worker Notification Scheduler queries both NotificationScheduler and MessagingJob. Retry reuses compatible blob payload; incompatible payload → stop sending.
UserMessage (in-app)
Generator creates UserMessage when template requires display in app. Keys:
Customer: PartitionKey = user:{User.Id}
Staff: PartitionKey = employee:{Employee.Guid}
RowKey = {SourceReverseTicks}_{NotificationJob.RowKey}SourceReverseTicks comes from the stable source timestamp (creation/schedule basis of NotificationJob), not write-time — otherwise Generator retry creates duplicate rows. Parent inboxes group by recipient partition.
What Notification Settings control
Scheduler reads BU config:
NotificationOption— catalog of notification types (new types must be seeded here).NotificationSetting— enable/disable + subscriber by BU.
Types not seeded or with no subscriber → handler skips gracefully (no send).
DeliveryDispatchResult
Each dispatch result is stored in AutomationScheduler.DispatchResultJson: delivery path + coordinates (notification/scheduler/messaging-job/user-message/outgoing-message) + SkippedReasonCode/ConflictReasonCode/Summary. Compact, merged on retry, never deleted.
Important tests
- Direct reminder retry → one
NotificationJob; Generator retry → oneOutgoingMessageper recipient/method; duplicate Distributor message → does not call provider a second time. - UI-only reminder →
UserMessage, noOutgoingMessage; UserMessage retry with stable source ticks → one row. - Missing scheduler recreated through dispatch guard; hash mismatch → stop send.
- Storage adapters preserve keys supplied by caller.
- Report/campaign retry reuses compatible
MessagingJob + Blob.
Next: Common cases.