Back to ER Diagram
Notification Logic

Notification System Logic

Comprehensive event-driven notification system with 107 configurable trigger codes across all modules. Supports admin-level toggle, user preferences, email via Brevo, real-time via SignalR, and optional SMS.

5 Tables Involved
107 Trigger Codes
Schema v3.0
14 Modules Covered

System Overview

The notification system is event-driven, sending both in-app notifications and emails based on configurable triggers. Users can customize their notification preferences.

System Event
config.notification_triggers
config.notification_templates
config.user_preferences
config.notifications

Tables Involved

config.notification_templates

  • Email/SMS template definitions with HTML and plain text versions
  • Variables stored in JSONB for dynamic content replacement
  • Example: PR_APPROVED template with {{pr_number}}, {{approver_name}}

config.notification_triggers

  • Maps system events to notification templates
  • Defines recipient type (CREATOR, APPROVER, ROLE, SPECIFIC_USER)
  • Can be enabled/disabled per trigger

config.notifications

  • In-app notification records for each user
  • Links to entity (entity_type, entity_id) for navigation
  • Tracks read status with is_read flag

config.email_logs

  • Logs all sent emails for audit and troubleshooting
  • Tracks delivery status (SENT, FAILED, BOUNCED)
  • Stores error messages for failed deliveries

config.user_preferences

  • User-specific notification preferences
  • Keys like: email_pr_approved, email_po_pending, inapp_rfq_submitted
  • Users can opt-out of specific notification types

Notification Flow Steps

1

Event Occurs

A system event occurs (e.g., PR approved, PO created, vendor registration). The event is captured with entity details.

public async Task RaiseEventAsync(string eventCode, string entityType, long entityId, object data)
{
    await _notificationService.ProcessEventAsync(new NotificationEvent
    {
        EventCode = eventCode,          // e.g., "PR_APPROVED"
        EntityType = entityType,        // e.g., "PR"
        EntityId = entityId,            // e.g., 12345
        EventData = data,               // Additional context data
        OccurredAt = DateTime.UtcNow
    });
}
2

Find Applicable Triggers

Look up which triggers are configured for this event and are enabled.

SELECT nt.*, t.template_code, t.subject, t.body_html, t.variables
FROM config.notification_triggers nt
JOIN config.notification_templates t ON nt.template_id = t.id
WHERE nt.event_code = 'PR_APPROVED'
  AND nt.module_type = 'PR'
  AND nt.is_enabled = true
  AND t.is_active = true;
3

Resolve Recipients

Determine who should receive the notification based on recipient_type.

private async Task<List<User>> ResolveRecipientsAsync(string recipientType, NotificationEvent evt)
{
    return recipientType switch
    {
        "CREATOR" => await GetEntityCreator(evt.EntityType, evt.EntityId),
        "APPROVER" => await GetNextApprover(evt.EntityType, evt.EntityId),
        "ROLE" => await GetUsersByRole(evt.RoleId),
        "ALL_APPROVERS" => await GetAllPendingApprovers(evt.EntityType, evt.EntityId),
        "VENDOR" => await GetVendorContacts(evt.VendorId),
        _ => new List<User>()
    };
}
4

Check User Preferences

Verify if each recipient has opted to receive this type of notification.

SELECT preference_value
FROM config.user_preferences
WHERE user_id = @userId
  AND preference_key = 'email_pr_approved';

-- Default to 'true' if no preference is set
-- Only skip if explicitly set to 'false'
5

Render Template

Replace template variables with actual values from the event data.

private string RenderTemplate(string template, Dictionary<string, string> variables)
{
    foreach (var kvp in variables)
    {
        template = template.Replace("{{" + kvp.Key + "}}", kvp.Value);
    }
    return template;
}

// Variables example:
// {{pr_number}} -> "PR-2026-00123"
// {{approver_name}} -> "John Smith"
// {{amount}} -> "$15,000.00"
6

Create In-App Notification

Insert a notification record for the user's notification bell.

INSERT INTO config.notifications
    (user_id, title, message, notification_type, entity_type, entity_id, is_read, created_at)
VALUES
    (@userId, 'PR Approved', 'Your PR #PR-2026-00123 has been approved',
     'APPROVAL', 'PR', 12345, false, NOW());
7

Send Email & Log

Send the email via SMTP/SendGrid and log the result.

// Send email
var result = await _emailService.SendAsync(recipient.Email, subject, bodyHtml);

// Log result
INSERT INTO config.email_logs
    (template_id, to_email, subject, status, error_message, sent_at)
VALUES
    (@templateId, @email, @subject,
     result.Success ? 'SENT' : 'FAILED',
     result.ErrorMessage, NOW());

Complete Event Code Reference

Comprehensive list of all 107 notification trigger codes across the ReqVise Unified Platform. Admin can enable/disable any trigger. Users can customize preferences except for critical notifications.

Authentication Module (6 triggers)

Event CodeDescriptionRecipientsChannelDefault
AUTH_LOGIN_SUCCESSUser logged inUserIn-AppDisabled
AUTH_LOGIN_FAILEDFailed login attemptUser, AdminEmailEnabled
AUTH_ACCOUNT_LOCKEDAccount locked after failed attemptsUser, AdminEmailEnabled
AUTH_PASSWORD_RESET_REQUESTEDPassword reset initiatedUserEmailEnabled
AUTH_PASSWORD_CHANGEDPassword successfully changedUserEmailEnabled
AUTH_SESSION_EXPIREDSession timeoutUserIn-AppDisabled

CRM Module (7 triggers)

Event CodeDescriptionRecipientsChannelDefault
CRM_LEAD_RECEIVEDNew lead synced from CRMSales ManagerEmail In-AppEnabled
CRM_LEAD_QUALIFIEDLead marked as qualifiedSales ManagerIn-AppEnabled
CRM_LEAD_NURTURELead moved to nurtureSales ManagerIn-AppDisabled
CRM_QUOTE_CREATEDNew quote createdCustomerEmailEnabled
CRM_QUOTE_REVISEDQuote revisedCustomerEmailEnabled
CRM_DEAL_WONDeal marked as wonSales Mgr, Project MgrEmail In-AppEnabled
CRM_DEAL_LOSTDeal marked as lostSales ManagerEmailEnabled

Project Module (9 triggers)

Event CodeDescriptionRecipientsChannelDefault
PRJ_CREATEDProject createdProject Manager, TeamEmail In-AppEnabled
PRJ_SCHEDULE_UPDATEDSchedule modifiedTeam MembersIn-AppEnabled
PRJ_MILESTONE_APPROACHINGMilestone due in 7 daysProject ManagerEmailEnabled
PRJ_MILESTONE_OVERDUEMilestone past dueProject Mgr, EscalationEmailEnabled
PRJ_PROGRESS_UPDATEDActivity progress changedProject ManagerIn-AppDisabled
PRJ_BUDGET_WARNINGBudget at 80%Project Mgr, FinanceEmailEnabled
PRJ_BUDGET_EXCEEDEDBudget exceeded 100%Project Mgr, DirectorEmailEnabled
PRJ_TEAM_ASSIGNEDUser added to projectUserEmail In-AppEnabled
PRJ_CLOSEDProject closedTeam, StakeholdersEmailEnabled

Engineering Module (15 triggers)

Event CodeDescriptionRecipientsChannelDefault
ENG_TDS_ALLOCATEDTDS allocated to vendorVendorEmailEnabled
ENG_TDS_DUE_REMINDERTDS due in 3 daysVendorEmailEnabled
ENG_TDS_OVERDUETDS past due dateVendor, Eng LeadEmailEnabled
ENG_TDS_SUBMITTEDVendor submitted TDSReviewerEmail In-AppEnabled
ENG_TDS_APPROVEDTDS approvedVendor, CreatorEmailEnabled
ENG_TDS_REJECTEDTDS rejectedVendorEmailEnabled
ENG_TDS_REVISION_ALLOWEDRevision allowed by reviewerVendorEmailEnabled
ENG_DRAWING_ALLOCATEDDrawing allocated to vendorVendorEmailEnabled
ENG_DRAWING_DUE_REMINDERDrawing due in 3 daysVendorEmailEnabled
ENG_DRAWING_OVERDUEDrawing past due dateVendor, Eng LeadEmailEnabled
ENG_DRAWING_SUBMITTEDVendor submitted drawingReviewerEmail In-AppEnabled
ENG_DRAWING_APPROVEDDrawing approvedVendor, CreatorEmailEnabled
ENG_DRAWING_REJECTEDDrawing rejectedVendorEmailEnabled
ENG_TRANSMITTAL_SENTTransmittal sent to recipientRecipientEmailEnabled
ENG_TRANSMITTAL_ACKNOWLEDGEDTransmittal acknowledgedSenderIn-AppEnabled

Purchase Request Module (8 triggers)

Event CodeDescriptionRecipientsChannelDefault
PR_CREATEDPR createdCreatorIn-AppDisabled
PR_SUBMITTEDPR submitted for approvalNext ApproverEmail In-AppEnabled
PR_PENDING_APPROVALPR awaiting your approvalApproverEmailEnabled
PR_APPROVED_LEVELPR approved at current levelCreator, Next ApproverIn-AppEnabled
PR_FULLY_APPROVEDPR fully approvedCreatorEmail In-AppEnabled
PR_REJECTEDPR rejectedCreatorEmail In-AppEnabled
PR_REVISION_REQUESTEDPR needs revisionCreatorEmailEnabled
PR_SHORT_CLOSEDPR short closedCreatorEmailEnabled

Stock Module (2 triggers)

Event CodeDescriptionRecipientsChannelDefault
STK_CREATEDStock created from PRStock ManagerIn-AppEnabled
STK_LOW_INVENTORYStock below thresholdProcurementEmailEnabled

RFQ Module (10 triggers)

Event CodeDescriptionRecipientsChannelDefault
RFQ_CREATEDRFQ createdCreatorIn-AppDisabled
RFQ_SUBMITTEDRFQ submitted for approvalApproverEmail In-AppEnabled
RFQ_APPROVEDRFQ approvedCreatorEmail In-AppEnabled
RFQ_PUBLISHEDRFQ published to vendorsInvited VendorsEmailEnabled
RFQ_BID_RECEIVEDVendor submitted bidRFQ CreatorEmail In-AppEnabled
RFQ_BID_DEADLINE_REMINDERBid deadline in 24 hoursVendors (no bid yet)EmailEnabled
RFQ_BID_DEADLINE_PASSEDBid deadline reachedRFQ CreatorIn-AppEnabled
RFQ_EVALUATION_COMPLETEBid evaluation completedApproversIn-AppEnabled
RFQ_SHORTLISTEDVendor shortlistedVendorEmailEnabled
RFQ_NOT_SHORTLISTEDVendor not shortlistedVendorEmailEnabled

Reverse Auction Module (10 triggers)

Event CodeDescriptionRecipientsChannelDefault
RA_CREATEDAuction createdCreatorIn-AppDisabled
RA_INVITATION_SENTAuction invitationShortlisted VendorsEmailEnabled
RA_STARTING_SOONAuction starts in 1 hourParticipantsEmailEnabled
RA_STARTEDAuction startedParticipantsEmail In-AppEnabled
RA_NEW_BIDNew bid receivedProcurement TeamIn-AppEnabled
RA_OUTBIDYour bid is no longer lowestVendorReal-timeEnabled
RA_EXTENDEDAuction time extendedParticipantsReal-timeEnabled
RA_ENDING_SOONAuction ends in 5 minutesParticipantsReal-timeEnabled
RA_ENDEDAuction endedParticipantsEmailEnabled
RA_WONYou won the auctionWinning VendorEmailEnabled

Purchase Order Module (10 triggers)

Event CodeDescriptionRecipientsChannelDefault
PO_CREATEDPO createdCreatorIn-AppDisabled
PO_SUBMITTEDPO submitted for approvalApproverEmail In-AppEnabled
PO_PENDING_APPROVALPO awaiting your approvalApproverEmailEnabled
PO_APPROVEDPO fully approvedCreatorEmail In-AppEnabled
PO_REJECTEDPO rejectedCreatorEmail In-AppEnabled
PO_RELEASEDPO released to vendorVendorEmailEnabled
PO_ACKNOWLEDGEDPO acknowledged by vendorCreatorIn-AppEnabled
PO_NOT_ACKNOWLEDGEDPO not acknowledged in 48hCreator, VendorEmailEnabled
PO_AMENDMENT_CREATEDPO amendment initiatedVendor, ApproversEmailEnabled
PO_AMENDMENT_APPROVEDPO amendment approvedVendor, CreatorEmailEnabled

Vendor Portal (9 triggers)

Event CodeDescriptionRecipientsChannelDefault
VND_REGISTRATION_RECEIVEDNew vendor registrationProcurement AdminEmailEnabled
VND_REGISTRATION_APPROVEDVendor approvedVendorEmailEnabled
VND_REGISTRATION_REJECTEDVendor rejectedVendorEmailEnabled
VND_COMPLIANCE_DUECompliance document expiringVendorEmailEnabled
VND_COMPLIANCE_EXPIREDCompliance document expiredVendor, AdminEmailEnabled
VND_COMPLIANCE_SUBMITTEDCompliance uploadedAdminIn-AppEnabled
VND_COMPLIANCE_APPROVEDCompliance approvedVendorEmailEnabled
VND_COMPLIANCE_REJECTEDCompliance rejectedVendorEmailEnabled
VND_PROFILE_INCOMPLETEProfile incomplete warningVendorEmailEnabled

Vendor Drawings Submission (6 triggers)

Event CodeDescriptionRecipientsChannelDefault
VDW_SUBMISSION_REQUIREDDrawings required for POVendorEmailEnabled
VDW_SUBMITTEDVendor submitted drawingsEngineering ReviewerEmail In-AppEnabled
VDW_APPROVEDDrawings approvedVendorEmailEnabled
VDW_REVISE_RESUBMITDrawings need revisionVendorEmailEnabled
VDW_REJECTEDDrawings rejectedVendorEmailEnabled
VDW_REVIEW_OVERDUEReview overdue (7 days)Reviewer, ManagerEmailEnabled

Delivery & Payment (15 triggers)

Event CodeDescriptionRecipientsChannelDefault
ASN_CREATEDASN created by vendorReceiving TeamEmailEnabled
ASN_SHIPMENT_DISPATCHEDShipment dispatchedReceiving TeamEmailEnabled
ASN_DELIVERY_DATEExpected delivery todayReceiving TeamEmailEnabled
GRN_CREATEDGRN createdVendor, ProcurementIn-AppEnabled
GRN_QC_PASSEDQC passedVendorIn-AppEnabled
GRN_QC_FAILEDQC failedVendor, ProcurementEmailEnabled
GRN_PARTIAL_RECEIPTPartial goods receivedVendor, ProcurementEmailEnabled
INV_SUBMITTEDInvoice submitted by vendorFinanceEmail In-AppEnabled
INV_THREE_WAY_MATCH_OK3-way match successfulFinanceIn-AppEnabled
INV_THREE_WAY_MISMATCH3-way match discrepancyFinance, ProcurementEmailEnabled
INV_APPROVEDInvoice approvedVendorEmailEnabled
INV_REJECTEDInvoice rejectedVendorEmailEnabled
INV_PAYMENT_DUEInvoice due in 3 daysFinanceEmailEnabled
INV_PAYMENT_OVERDUEInvoice overdueFinance, VendorEmailEnabled
PAY_PROCESSEDPayment processedVendorEmailEnabled

Analytics & Reporting (5 triggers)

Event CodeDescriptionRecipientsChannelDefault
RPT_SCHEDULED_READYScheduled report generatedSubscribersEmailEnabled
RPT_EXPORT_COMPLETELarge export completeRequesterIn-AppEnabled
KPI_THRESHOLD_BREACHKPI threshold exceededDashboard OwnerEmailEnabled
CRM_SYNC_COMPLETECRM sync completedAdminIn-AppDisabled
CRM_SYNC_FAILEDCRM sync failedAdminEmailEnabled

System Notifications (5 triggers)

Event CodeDescriptionRecipientsChannelDefault
SYS_MAINTENANCE_SCHEDULEDMaintenance scheduledAll UsersEmailEnabled
SYS_MAINTENANCE_STARTINGMaintenance starting in 1hAll UsersIn-AppEnabled
SYS_MAINTENANCE_COMPLETEMaintenance completeAll UsersIn-AppEnabled
SYS_NEW_VERSIONNew version deployedAll UsersIn-AppDisabled
SYS_BACKUP_FAILEDBackup job failedAdminEmailEnabled

Critical Notifications (Cannot be disabled by users)

AUTH_ACCOUNT_LOCKED, AUTH_PASSWORD_RESET_REQUESTED, AUTH_PASSWORD_CHANGED, VND_COMPLIANCE_EXPIRED, PO_RELEASED, INV_REJECTED, SYS_BACKUP_FAILED

Template Example

Example email template for PR Approval notification:

Subject: Purchase Request {{pr_number}} has been Approved

Dear {{requester_name}},


Your Purchase Request {{pr_number}} has been approved.


Details:

  • PR Number: {{pr_number}}
  • Total Amount: {{total_amount}}
  • Approved By: {{approver_name}}
  • Approved On: {{approved_date}}

You can view the PR details by clicking here.


Best regards,
ProKure System

-- Template stored in database
INSERT INTO config.notification_templates
    (template_code, template_name, subject, body_html, body_text, variables, is_active)
VALUES (
    'PR_APPROVED',
    'PR Approval Notification',
    'Purchase Request {{pr_number}} has been Approved',
    '<html>...template HTML...</html>',
    'Plain text version...',
    '["pr_number", "requester_name", "total_amount", "approver_name", "approved_date"]'::jsonb,
    true
);

User Preferences

Users can customize their notification preferences through the settings page:

Preference Key Description Default Type
email_pr_submitted Email when PR needs your approval true Email
email_pr_approved Email when your PR is approved true Email
inapp_pr_approved In-app notification when PR approved true In-App
email_po_created Email when new PO created (vendors) true Email
email_bid_deadline Reminder before RFQ deadline true Email
email_digest Daily digest instead of individual emails false Email

Default Behavior

If a user has not set a preference, the system defaults to sending the notification. Only explicit opt-outs (preference_value = 'false') will suppress notifications.

Real-time In-App Notifications

For real-time delivery, the system uses SignalR (WebSocket) to push notifications to the Angular frontend:

// .NET Hub
public class NotificationHub : Hub
{
    public async Task SendNotification(long userId, NotificationDto notification)
    {
        await Clients.User(userId.ToString())
            .SendAsync("ReceiveNotification", notification);
    }
}

// Angular Service
this.hubConnection.on('ReceiveNotification', (notification) => {
    this.notifications.unshift(notification);
    this.unreadCount++;
    this.playNotificationSound();
});