SendNotificationCommand
Category: Communication Commands
Added: v2.2 (January 2026)
Resilient: â
Yes (Offline support with message queuing)
Send real-time notifications to users via SignalR with automatic fallback for offline users.
Overviewâ
SendNotificationCommand enables instant push notifications to logged-in users through SignalR. Unlike email or SMS which have delays, SignalR notifications appear immediately in the user's browser or app.
Key Featuresâ
- đ Instant Delivery - Sub-second delivery to online users
- đž Offline Resilience - Auto-queues messages for offline users
- đĨ Multi-User Support - Send to current user, specific user, or multiple users
- đ¯ Targeted Delivery - Send to individual users or user groups
- đĻ Rich Payloads - Include custom data, actions, and metadata
- đ Priority Levels - High, normal, and low priority notifications
- âąī¸ Expiration Control - Auto-expire old notifications (default: 24 hours)
When to Useâ
| â Use SendNotificationCommand For: | â Don't Use For: |
|---|---|
| Transaction status updates | Long-term archival messages |
| Approval/rejection alerts | Regulatory/compliance notices |
| Real-time warnings | Marketing campaigns |
| Process completion notifications | Bulk announcements (use email) |
| User-triggered events | Legal documents |
| Dashboard updates | Financial statements |
Parametersâ
Required Parametersâ
| Parameter | Type | Description |
|---|---|---|
title | string | Notification title/heading (required) |
message | string | Notification body/content (required) |
Target Parameters (Choose One)â
| Parameter | Type | Default | Description |
|---|---|---|---|
target | string | "current" | Who receives: "current", "user", or "users" |
userId | string | null | Single user ID (when target="user") |
userIds | array<string> | null | Multiple user IDs (when target="users") |
Optional Parametersâ
| Parameter | Type | Default | Description |
|---|---|---|---|
notificationType | string | "info" | Type: "info", "success", "warning", "error", "notification" |
priority | string | "normal" | Priority: "high", "normal", "low" |
action | string | null | Action when clicked (e.g., "navigate:/transactions/123") |
data | object | null | Additional custom data payload |
persistForOfflineUsers | boolean | true | Store for offline users and deliver later |
expirationMinutes | number | 1440 | Minutes before notification expires (default: 1440 = 24 hours) |
Target Modesâ
1. Current User (target: "current")â
Send notification to the user executing the workflow (most common).
var result = doCmd('SendNotificationCommand', {
Data: {
target: 'current',
title: 'Loan Approved!',
message: 'Your loan application #' + context.loanId + ' has been approved.',
notificationType: 'success'
}
});
Use Case: User submits a form, workflow processes it, user gets notified.
2. Specific User (target: "user")â
Send notification to one specific user by their ID.
var result = doCmd('SendNotificationCommand', {
Data: {
target: 'user',
userId: context.approverId, // Specific user ID
title: 'Pending Approval',
message: 'Loan application from ' + context.customerName + ' needs your approval.',
notificationType: 'warning',
priority: 'high',
action: 'navigate:/approvals/' + context.loanId
}
});
Use Case: Workflow assigns task to specific approver.
3. Multiple Users (target: "users")â
Send same notification to multiple users simultaneously.
var approvers = ['user123', 'user456', 'user789'];
var result = doCmd('SendNotificationCommand', {
Data: {
target: 'users',
userIds: approvers,
title: 'Team Notification',
message: 'Large deposit transaction requires team review.',
notificationType: 'warning',
priority: 'high'
}
});
Use Case: Notify all members of approval group.
Notification Types & Iconsâ
| Type | Icon | Use For |
|---|---|---|
info | âšī¸ | General information, updates |
success | â | Successful operations, approvals |
warning | â ī¸ | Warnings, pending actions |
error | â | Errors, rejections, failures |
notification | đ | Generic notifications |
Resilient Delivery (Offline Support)â
When persistForOfflineUsers: true (default), notifications are automatically stored for offline users and delivered when they reconnect.
How It Worksâ
1. User is offline â Notification stored in database
2. User comes online â SignalR connects
3. All pending notifications delivered automatically
4. Notifications marked as "Delivered"
5. Expired notifications (>24h by default) auto-cleaned
Example with Custom Expirationâ
var result = doCmd('SendNotificationCommand', {
Data: {
target: 'user',
userId: context.customerId,
title: 'Urgent: Account Locked',
message: 'Multiple failed login attempts detected.',
notificationType: 'error',
priority: 'high',
persistForOfflineUsers: true,
expirationMinutes: 2880 // Keep for 2 days (48 hours)
}
});
Action Handlingâ
The action parameter tells the client what to do when notification is clicked.
Supported Action Patternsâ
// Navigate to specific page
action: 'navigate:/transactions/123'
// Open modal/dialog
action: 'modal:transaction-details?id=123'
// Execute client-side function
action: 'callback:refreshBalance'
// External link
action: 'url:https://banklingo.app/help'
// Custom JSON action
action: '{"type":"refresh","target":"dashboard","params":{"tab":"loans"}}'
Example with Navigationâ
var result = doCmd('SendNotificationCommand', {
Data: {
target: 'current',
title: 'Transaction Completed',
message: 'Your transfer of $5,000 was successful.',
notificationType: 'success',
action: 'navigate:/transactions/' + context.transactionId,
data: {
transactionId: context.transactionId,
amount: 5000,
currency: 'USD'
}
}
});
Custom Data Payloadsâ
Include additional structured data for client-side processing.
var result = doCmd('SendNotificationCommand', {
Data: {
target: 'current',
title: 'Loan Disbursed',
message: 'Your loan has been disbursed to account ' + context.accountNumber,
notificationType: 'success',
data: {
loanId: context.loanId,
amount: context.loanAmount,
currency: 'NGN',
disbursementDate: new Date().toISOString(),
accountNumber: context.accountNumber,
tenor: context.tenor,
interestRate: context.interestRate
}
}
});
Frontend handling:
signalRConnection.on("ReceiveNotification", (notification) => {
console.log('Title:', notification.Title);
console.log('Message:', notification.Message);
console.log('Custom Data:', notification.Data);
// Access custom data
if (notification.Data) {
const loanId = notification.Data.loanId;
const amount = notification.Data.amount;
// ... process custom data
}
});
Priority Levelsâ
| Priority | Behavior | Use For |
|---|---|---|
high | Prominent display, sound/vibration | Urgent approvals, errors, security alerts |
normal | Standard notification | General updates, info messages |
low | Subtle display, no sound | Background updates, non-critical info |
Complete Workflow Exampleâ
Loan Approval Notification Workflowâ
// In your BPMN workflow script
// Step 1: Notify customer of submission
doCmd('SendNotificationCommand', {
Data: {
target: 'current',
title: 'Application Received',
message: 'Your loan application is being reviewed.',
notificationType: 'info'
}
});
// Step 2: Notify approver
var approverId = context.approver_id;
doCmd('SendNotificationCommand', {
Data: {
target: 'user',
userId: approverId,
title: 'Approval Required',
message: 'Loan application #' + context.loan_id + ' needs approval.',
notificationType: 'warning',
priority: 'high',
action: 'navigate:/approvals/' + context.loan_id,
data: {
loanId: context.loan_id,
customerName: context.customer_name,
amount: context.loan_amount
}
}
});
// Step 3: After approval, notify customer
if (context.approval_status === 'Approved') {
doCmd('SendNotificationCommand', {
Data: {
target: 'user',
userId: context.customer_id,
title: 'Loan Approved! đ',
message: 'Congratulations! Your loan of âĻ' +
formatMoney(context.loan_amount) + ' has been approved.',
notificationType: 'success',
priority: 'high',
action: 'navigate:/loans/' + context.loan_id,
persistForOfflineUsers: true,
expirationMinutes: 2880 // 48 hours
}
});
}
Best Practicesâ
â Do'sâ
-
Use Descriptive Titles: Clear, concise, action-oriented
â title: 'Loan Approved'
â title: 'Notification' -
Include Context: Add relevant IDs and data
data: {
loanId: context.loanId,
amount: context.amount
} -
Set Appropriate Priority: Reserve
highfor urgent itemspriority: 'high' // Only for time-sensitive actions -
Add Actions: Make notifications actionable
action: 'navigate:/approvals/' + loanId -
Use Persistence: Enable for important notifications
persistForOfflineUsers: true
â Don'tsâ
-
Don't Spam: Avoid excessive notifications
â Sending notification for every minor update
â Batch updates or use summary notifications -
Don't Include Sensitive Data: PINs, passwords, full account numbers
â message: 'Your PIN is 1234'
â message: 'Your PIN has been updated' -
Don't Use Long Messages: Keep under 200 characters
â message: 'Very long paragraph with lots of details...'
â message: 'Loan approved. View details for more info.' -
Don't Overuse High Priority: Reduces effectiveness
â priority: 'high' // For everything
â priority: 'normal' // For most notifications
Frontend Integrationâ
JavaScript SignalR Clientâ
// Connect to SignalR hub
const connection = new signalR.HubConnectionBuilder()
.withUrl("/hub/notification")
.withAutomaticReconnect()
.build();
// Listen for notifications
connection.on("ReceiveNotification", (notification) => {
// Display notification
showToast({
title: notification.Title,
message: notification.Message,
type: notification.Type,
priority: notification.Priority
});
// Handle action
if (notification.Action) {
handleNotificationAction(notification.Action, notification.Data);
}
// Log for debugging
console.log('Notification received:', notification);
});
// Start connection
connection.start()
.then(() => console.log('SignalR connected'))
.catch(err => console.error('SignalR connection error:', err));
// Handle actions
function handleNotificationAction(action, data) {
if (action.startsWith('navigate:')) {
const path = action.replace('navigate:', '');
window.location.href = path;
}
else if (action.startsWith('modal:')) {
const modal = action.replace('modal:', '');
openModal(modal, data);
}
// ... handle other action types
}
Error Handlingâ
try {
var result = doCmd('SendNotificationCommand', {
Data: {
target: 'user',
userId: context.userId,
title: 'Important Update',
message: 'Your transaction is complete'
}
});
if (result.IsSuccessful) {
logger.info('Notification sent successfully');
} else {
logger.error('Notification failed: ' + result.Message);
}
} catch (error) {
logger.error('Error sending notification: ' + error.message);
// Fallback: Send email instead
doCmd('SendMailCommand', {
Data: {
email: [context.userEmail],
subject: 'Important Update',
message: 'Your transaction is complete'
}
});
}
Performance Considerationsâ
Scalabilityâ
- â Lightweight: ~1KB per notification
- â Fast: <100ms delivery to online users
- â Efficient: Database queuing for offline users only
Limitsâ
| Metric | Limit | Notes |
|---|---|---|
| Max title length | 500 chars | Enforced by database |
| Max message length | Unlimited | Consider UX for display |
| Max users per call | 100 | For target="users" |
| Max data payload | 10KB | Keep payloads small |
| Expiration range | 1-720 hours | 1 hour to 30 days |
Database Schemaâ
CREATE TABLE [dbo].[PendingNotification] (
[Id] BIGINT PRIMARY KEY IDENTITY,
[NotificationId] NVARCHAR(100) NOT NULL,
[UserId] NVARCHAR(100) NOT NULL,
[TenantId] NVARCHAR(100) NOT NULL,
[Title] NVARCHAR(500) NOT NULL,
[Message] NVARCHAR(MAX) NOT NULL,
[NotificationType] NVARCHAR(50),
[Priority] NVARCHAR(50),
[Action] NVARCHAR(500),
[DataJson] NVARCHAR(MAX),
[CreatedAt] DATETIME2 NOT NULL,
[ExpiresAt] DATETIME2 NOT NULL,
[Status] NVARCHAR(50) NOT NULL,
[DeliveryAttempts] INT DEFAULT 0,
[DeliveredAt] DATETIME2 NULL,
[LastAttemptAt] DATETIME2 NULL,
[ErrorMessage] NVARCHAR(MAX) NULL,
INDEX IX_PendingNotification_UserId_Status (UserId, Status),
INDEX IX_PendingNotification_ExpiresAt (ExpiresAt)
);
Comparison with Other Commandsâ
| Feature | SendNotificationCommand | SendMailCommand | SendSMSCommand |
|---|---|---|---|
| Delivery Speed | Instant (<1s) | Minutes (5-30s) | Seconds (3-10s) |
| Offline Support | â Yes (queued) | â Yes (email queues) | â No |
| Cost | Free | Low | Moderate-High |
| Interactive | â Yes (actions) | â No | â No |
| Rich Content | â Yes (HTML/JSON) | â Yes (HTML) | â No (plain text) |
| User Must Be | Logged in | Have email | Have phone |
| Best For | Real-time updates | Formal communications | OTPs, alerts |
Related Commandsâ
- SendMailCommand - Email notifications
- SendSMSCommand - SMS notifications
- Communication Handlers - Email & SMS in workflows
Support & Troubleshootingâ
Common Issuesâ
1. Notifications not received
- Check user is logged in and SignalR connected
- Verify userId is correct
- Check browser console for SignalR errors
2. Offline notifications not delivered
- Ensure
persistForOfflineUsers: true - Check
PendingNotificationtable for stored messages - Verify expiration hasn't passed
3. Action not working
- Verify action format:
"navigate:/path"or"modal:modalName" - Check client-side action handler implementation
Version Historyâ
- v2.2 (Jan 2026): Initial release with offline support
- SignalR real-time delivery
- Database persistence for offline users
- Multi-user targeting
- Rich data payloads
- Automatic expiration cleanup
For critical notifications, use layered approach:
// 1. Try instant notification first
doCmd('SendNotificationCommand', { Data: { ... } });
// 2. Always send email backup
doCmd('SendMailCommand', { Data: { ... } });
// 3. SMS for high-priority items
if (priority === 'high') {
doCmd('SendSMSCommand', { Data: { ... } });
}
This ensures users receive the message through at least one channel.