Dispatch Alerts & Notifications System¶
Overview¶
Pomp's Dispatch Center implements a comprehensive, multi-channel alert system to ensure real-time operational visibility across dispatchers, store managers, technicians, and customers. Alerts are generated for critical dispatch events—new work orders, technician assignments, status updates, SLA breaches, and exceptions—and delivered through web, mobile, SMS, and phone channels based on role and alert priority.
This document defines: - All alert types and triggers for dispatch operations - Global standard alert rules applied consistently across 200+ stores - Multi-channel routing (web, mobile, SMS via Genesys Cloud, phone calls via Genesys Cloud) - 5-minute escalation workflows for critical alerts - Customer notification strategy (limited to assignment, ETA, and closure) - Implementation patterns for alert generation and delivery - Compliance and audit requirements
Table of Contents¶
- Alert Channels & Delivery
- Alert Types & Triggers
- Alert Recipients & Routing Logic
- Escalation Workflows
- Customer Notification Strategy
- Genesys Cloud Integration
- Alert Acknowledgment & Tracking
- Mobile App Alert Behavior
- Web Dashboard Alert Display
- Implementation Details
- Alert Configuration & Rules
- Monitoring & Metrics
- Best Practices & Guidelines
Alert Channels & Delivery¶
Pomp's Dispatch Center supports multiple alert channels to reach the right person at the right time with the right information.
Channel Definitions¶
| Channel | Delivery Method | Use Case | Audience | Immediate | Acknowledgment |
|---|---|---|---|---|---|
| Web | In-browser notification + dashboard update | Non-urgent to medium priority; office staff | Dispatchers, Managers, Billing, CSR | ✅ | Optional |
| Mobile Push | React Native push notification | Medium to high priority; field/mobile staff | Technicians, Mobile Dispatchers | ✅ | Optional |
| In-App Message | Alert banner/modal in application | Medium priority; context-specific | All roles | ✅ | Auto-dismiss |
| SMS | Text message via Genesys Cloud | High priority; time-critical | Technicians, Dispatchers, Managers | ✅ | Optional (confirmation) |
| Phone Call | Outbound call via Genesys Cloud IVR | Critical priority; requires acknowledgment | Dispatchers, Store Managers, Regional Mgmt | ✅ | Required (confirmation) |
Channel Selection Logic¶
Alert Priority & Recipient Context
↓
Low (Informational)
→ Web only (notification center)
Medium (Operational)
→ Web + In-App
→ Technicians: Mobile Push
→ SMS if recipient DND/offline
High (Time-Critical)
→ Web + Mobile Push + SMS
→ Phone Call if no acknowledgment in 5 min
Critical (System/SLA)
→ All channels (immediate phone call)
→ SMS to secondary recipients
→ Force acknowledgment required
Alert Types & Triggers¶
1. Work Order Creation & Intake¶
Alert 1.1: New Work Order Submitted¶
Trigger: Service request submitted via customer portal, phone, or REACH system
Recipients: - Central Dispatcher(s) - Primary - Assigned Store Manager - Secondary (if workable at their location)
Channels: - Central Dispatcher: Web + SMS (if offline) - Store Manager: Web + In-App
Message Template:
Web/In-App:
Title: "New Work Order: [Customer Name]"
Body: "Location: [Address]
Service Type: [Type]
Priority: [Priority Level]
Request Time: [Time Submitted]"
Action: Open dispatch board for assignment
SMS (Dispatcher):
"New WO: [Customer] @ [Address]. Priority: [Level].
Assigned to: [Store]. Ref: [ID]"
Escalation: If not acknowledged in 5 min, escalate to Regional Dispatcher
Urgency: High
Acknowledgment: Optional (but tracked for metrics)
Alert 1.2: Work Order Comment/Update from REACH¶
Trigger: Customer or REACH system adds comment to existing work order
Recipients: - Assigned Technician - Primary - Dispatcher - Secondary - Store Manager - Tertiary (if ongoing issue)
Channels: - Technician: Mobile Push + SMS (if urgent) - Dispatcher: Web + In-App - Store Manager: Web + In-App
Message Template:
Mobile Push (Technician):
"New comment on WO [ID]: [Customer Name]
[Comment Preview: first 50 chars]"
Web (Dispatcher):
Title: "Comment Added: [WO ID]"
Body: "From: [Reach/Customer]
Comment: [Full text]
Time: [Timestamp]"
Escalation: If comment indicates priority change or issue, re-prioritize work order
Urgency: Medium
Acknowledgment: Optional
2. Technician Assignment & Dispatch¶
Alert 2.1: Work Assigned to Technician¶
Trigger: Dispatcher assigns work order to specific technician
Recipients: - Assigned Technician - Primary - Store Manager (of assigned tech's store) - Secondary - Dispatcher - Confirmation
Channels: - Technician: Mobile Push + SMS - Store Manager: Web + In-App - Dispatcher: Web (confirmation)
Message Template:
Mobile Push/SMS (Technician):
"Work Assigned: [Customer Name]
Address: [Address]
Service: [Type]
ETA Window: [Start Time - End Time]
[Tap to accept/decline]"
Web (Store Manager):
Title: "Technician Assignment Confirmation"
Body: "Technician: [Tech Name]
Customer: [Name]
Address: [Address]
ETA: [Window]
Status: [Awaiting Tech Response]"
Store Manager Visibility: Store manager can see this assignment in their dashboard and can reject it (which escalates back to dispatcher for reassignment)
Escalation: If technician doesn't accept/respond in 5 min, escalate to Store Manager and then Dispatcher
Urgency: High
Acknowledgment: Required (accept/decline)
Alert 2.2: Technician Accepted Work Order¶
Trigger: Technician accepts assignment via mobile app
Recipients: - Store Manager - Primary - Dispatcher - Secondary (for tracking) - Customer - Tertiary (ETA notification)
Channels: - Store Manager: Web + In-App - Dispatcher: Web - Customer: SMS (ETA only)
Message Template:
Web (Store Manager & Dispatcher):
Title: "Work Accepted: [Tech Name]"
Body: "Technician: [Name]
Customer: [Customer Name]
ETA: [Time Range]
Status: En Route / Starting Prep"
SMS (Customer):
"Your service is assigned. Technician [Tech Name]
will arrive [ETA window]. Ref: [Job ID]"
Escalation: None (assignment confirmed)
Urgency: Medium
Acknowledgment: Not required
Alert 2.3: Technician Rejected/Declined Work Order¶
Trigger: Technician declines assignment or fails to respond in 5 minutes
Recipients: - Store Manager - Primary - Dispatcher - Secondary - Regional Dispatcher - Tertiary (if repeated issue)
Channels: - Store Manager: Web + SMS - Dispatcher: Web + SMS - Regional Dispatcher: SMS (if pattern detected)
Message Template:
SMS (Dispatcher):
"WO [ID] declined by [Tech Name] @ [Store].
Reason: [Reason]. Needs reassignment. Ref: [WO ID]"
Web (Store Manager):
Title: "Technician Declined: [WO ID]"
Body: "Tech: [Name] declined assignment
Reason: [Reason]
Available Alternative: [List other techs]
Action: Reassign via dispatch board"
Escalation: If reassignment also fails in 5 min, escalate to Regional Dispatcher
Urgency: High
Acknowledgment: Optional
3. Technician Status Updates¶
Alert 3.1: Technician Status - On My Way¶
Trigger: Technician updates status to "On My Way" in mobile app
Recipients: - Store Manager - Primary - Dispatcher - Secondary (for tracking)
Channels: - Store Manager: Web + In-App (low priority update) - Dispatcher: Web
Message Template:
Web (Store Manager):
Title: "[Tech Name] Heading to Customer"
Body: "Customer: [Customer Name]
Address: [Address]
ETA: [Time]
Status: En Route"
Escalation: None
Urgency: Low
Acknowledgment: Not required
Alert 3.2: Technician Status - Arrived at Customer¶
Trigger: Technician updates status to "Arrived" in mobile app
Recipients: - Store Manager - Primary - Dispatcher - Secondary
Channels: - Store Manager: Web (in-app banner) - Dispatcher: Web
Message Template:
Web (Store Manager):
Title: "[Tech Name] Arrived at Customer"
Body: "Customer: [Customer Name]
Address: [Address]
Arrival Time: [Time]
Status: On-site, Work in Progress"
Escalation: None
Urgency: Low
Acknowledgment: Not required
Alert 3.3: Technician Status - Work Complete¶
Trigger: Technician marks work as "Complete" and uploads required photos in mobile app
Recipients: - Store Manager - Primary - Dispatcher - Secondary - Customer - Tertiary (job closure notification)
Channels: - Store Manager: Web + SMS (if critical) - Dispatcher: Web - Customer: SMS (job complete + closure info)
Message Template:
Web (Store Manager):
Title: "Work Completed: [Tech Name]"
Body: "Technician: [Name]
Customer: [Customer Name]
Address: [Address]
Completion Time: [Time]
Photos Uploaded: [Count]
Notes: [Tech notes]
Action: Review & Approve"
SMS (Customer):
"Your service is complete! Technician [Tech Name]
completed work at [Time]. Invoice will follow.
Thank you! Ref: [Job ID]"
Escalation: If photos missing or incomplete, alert Store Manager and Technician for corrections
Urgency: High
Acknowledgment: Required (Store Manager approval)
4. Work Order Approvals & Rejections¶
Alert 4.1: Work Order Rejected by Store Manager¶
Trigger: Store Manager rejects completed work order with comments
Recipients: - Assigned Technician - Primary - Dispatcher - Secondary
Channels: - Technician: Mobile Push + SMS - Dispatcher: Web + SMS
Message Template:
Mobile Push/SMS (Technician):
"Work Order [ID] Rejected
Reason: [Manager Feedback]
Action Required: Review comments and resubmit
[Tap to view details]"
SMS (Dispatcher):
"WO [ID] rejected by Store Manager [Name].
Reason: [Brief reason]. Tech: [Tech Name].
Action: Coordinate rework. Ref: [WO ID]"
Escalation: If rework not resubmitted in 5 min, escalate to Dispatcher
Urgency: High
Acknowledgment: Required (Technician must acknowledge and resubmit)
Alert 4.2: Work Order Approved by Store Manager¶
Trigger: Store Manager approves completed work order
Recipients: - Dispatcher - Primary - Billing Clerk - Secondary (for invoice processing) - Technician - Tertiary (confirmation)
Channels: - Dispatcher: Web (confirmation) - Billing Clerk: Web + In-App (work ready for billing) - Technician: Mobile Push (confirmation)
Message Template:
Web (Dispatcher & Billing):
Title: "Work Order Approved: [WO ID]"
Body: "Technician: [Tech Name]
Customer: [Customer Name]
Approved Time: [Time]
Total Cost: [Amount]
Status: Ready for Billing"
Mobile Push (Technician):
"Work [ID] approved by Store Manager.
Great work!"
Escalation: None
Urgency: Medium
Acknowledgment: Not required
5. Exceptions & Issues¶
Alert 5.1: Missing or Incomplete Photos¶
Trigger: Technician attempts to close work order without required photos or submits incomplete documentation
Recipients: - Assigned Technician - Primary - Store Manager - Secondary
Channels: - Technician: Mobile Push + In-App (mandatory alert in app) - Store Manager: Web + In-App
Message Template:
Mobile Push (Technician):
"Missing Photos Required
Missing: [Photo Type List]
Cannot close work order until photos uploaded.
[Tap to take photos]"
Web (Store Manager):
Title: "Photos Incomplete: [WO ID]"
Body: "Technician: [Tech Name]
Missing: [Photo Types]
Customer: [Name]
Status: Awaiting documentation"
Escalation: If not corrected in 15 min, alert Store Manager for follow-up
Urgency: Medium
Acknowledgment: Required (Technician must upload photos)
Alert 5.2: Equipment Shortage / Inventory Issue Reported¶
Trigger: Technician reports equipment unavailable or inventory shortage during work
Recipients: - Dispatcher - Primary - Store Manager - Secondary - Regional Dispatcher - Tertiary (if critical)
Channels: - Dispatcher: Web + SMS - Store Manager: Web + SMS - Regional Dispatcher: Phone Call (if critical) + SMS
Message Template:
SMS (Dispatcher):
"Equipment Issue: WO [ID]
Tech: [Tech Name] @ [Store]
Issue: [Equipment/Item not available]
Customer: [Name]
Action: Provide alternative or cancel. Ref: [WO ID]"
Web (Store Manager):
Title: "Equipment Shortage Alert"
Body: "Work Order: [WO ID]
Technician: [Tech Name]
Issue: [Missing Equipment Description]
Customer: [Name]
Required Action: Approve alternative or cancel
[Button] Approve Alternative / Cancel Work"
Phone Call (Regional Dispatcher - if critical): "Critical equipment shortage reported. [Store Name], [Equipment Type]. Contact dispatcher for resolution. Press 1 to confirm receipt."
Escalation: If not resolved in 10 min, escalate to Regional Dispatcher and escalation manager
Urgency: Critical
Acknowledgment: Required (confirm resolution action)
Alert 5.3: SLA Breach Warning (Approaching Deadline)¶
Trigger: Work order approaching SLA deadline (warning at 80% of time window)
Recipients: - Dispatcher - Primary - Store Manager - Secondary - Regional Dispatcher - Tertiary (if SLA type requires it)
Channels: - Dispatcher: Web + SMS - Store Manager: Web + SMS - Regional Dispatcher: SMS
Message Template:
SMS (Dispatcher):
"SLA Warning: WO [ID]
Customer: [Name]
SLA Deadline: [Time]
Time Remaining: [Minutes]
Status: [Current Status]
Ref: [WO ID]"
Web (Store Manager):
Title: "⚠️ SLA Approaching Deadline"
Body: "Work Order: [WO ID]
Customer: [Name]
Deadline: [Time]
Time Remaining: [Minutes]
Tech: [Tech Name]
Current Status: [Status]
Action: Monitor completion"
Escalation: Escalate to Regional Dispatcher if not completed before deadline
Urgency: High
Acknowledgment: Optional (for tracking)
Alert 5.4: SLA Breach (Exceeded Deadline)¶
Trigger: Work order exceeds SLA deadline without completion
Recipients: - Regional Dispatcher - Primary - Dispatcher - Secondary (escalation notification) - Admin - Tertiary (for trending/analysis)
Channels: - Regional Dispatcher: Phone Call + SMS - Dispatcher: Web + SMS - Admin: Web (reporting only)
Message Template:
Phone Call (Regional Dispatcher):
"CRITICAL: SLA Breach. Work Order [ID].
Customer: [Name]
Deadline: [Time] - now [Hours] hours overdue.
Tech: [Tech Name] @ [Store].
Press 1 to acknowledge and resolve."
SMS (Dispatcher):
"🚨 SLA BREACH: WO [ID]
Customer: [Name]
Overdue: [Hours] hours
Tech: [Tech Name]
Action: Immediate follow-up required. Ref: [WO ID]"
Web (Admin - Reporting):
SLA Breach logged for compliance tracking
Impact: [KPI degradation]
Root Cause: [TBD]
Escalation: Immediate escalation to Regional management and Admin for incident review
Urgency: Critical
Acknowledgment: Required (phone call confirmation)
Alert 5.5: Technician Unresponsive / Offline¶
Trigger: Technician app loses connection for >10 min or technician becomes unavailable without status update
Recipients: - Store Manager - Primary - Dispatcher - Secondary
Channels: - Store Manager: Web + SMS - Dispatcher: Web + SMS
Message Template:
SMS (Dispatcher):
"Technician Unresponsive: [Tech Name] @ [Store]
Last Seen: [Time] ago
Active Work Order: [WO ID]
Customer: [Name]
Action: Attempt contact. Ref: [Tech ID]"
Web (Store Manager):
Title: "⚠️ Technician Offline"
Body: "Technician: [Tech Name]
Last Known Location: [Location]
Last Status: [Status]
Time Offline: [Duration]
Active Job: [WO ID] - [Customer Name]
Action: Attempt phone contact"
Escalation: If offline >15 min, escalate to dispatcher for contact attempt and potential reassignment
Urgency: High
Acknowledgment: Optional (for tracking)
Alert 5.6: No Response to New Assignment¶
Trigger: Technician receives assignment alert but doesn't accept/decline within 5 minutes
Recipients: - Store Manager - Primary - Dispatcher - Secondary (after Store Manager notified)
Channels: - Store Manager: Web + SMS - Dispatcher: Web + SMS (after 5 min)
Message Template:
SMS (Store Manager @ 5 min):
"Tech [Tech Name] not responding to assignment
WO [ID] - [Customer Name]
Status: Awaiting response [5 min+ overdue]
Action: Check status or reassign. Ref: [WO ID]"
SMS (Dispatcher @ 10 min):
"No Response Alert: Tech [Tech Name]
WO [ID] - [Customer]
Unresponsive [10 min+]
Action: Reassign or contact tech. Ref: [WO ID]"
Escalation: If not addressed in 10 min, escalate to Dispatcher for action
Urgency: High
Acknowledgment: Optional
Alert 5.7: Unassigned Work Order (Pending Assignment)¶
Trigger: Work order remains unassigned for >15 minutes after creation
Recipients: - Dispatcher - Primary - Regional Dispatcher - Secondary (if continues >30 min)
Channels: - Dispatcher: Web + SMS - Regional Dispatcher: SMS (at 30 min mark)
Message Template:
SMS (Dispatcher @ 15 min):
"Unassigned Work: [WO ID]
Customer: [Name] @ [Address]
Unassigned: [15 min+]
Priority: [Level]
Action: Assign available technician. Ref: [WO ID]"
SMS (Regional Dispatcher @ 30 min):
"URGENT: WO [ID] unassigned [30 min+]
Customer: [Name]
Store: [Store Name]
Action: Escalate to central dispatch if needed"
Escalation: At 30 min, escalate to Regional Dispatcher for urgent assignment or reroute
Urgency: High
Acknowledgment: Optional
6. Customer Notifications (Limited Scope)¶
Per business requirements, customers receive only three notifications:
Alert 6.1: Work Assigned & ETA Confirmation¶
Trigger: Work order assigned to technician and ETA window confirmed
Recipients: Customer
Channels: Web (default) + Optional SMS (if opted-in)
Message Template:
Web (Customer Portal):
Title: "Service Assigned"
Body: "Your service is assigned. Technician [Tech Name]
will arrive [ETA Window: e.g., 2:00 PM - 3:30 PM].
Job ID: [ID].
[Track technician] [View details]"
SMS (if opted-in):
"Your service is assigned. Technician [Tech Name]
will arrive [ETA Window: e.g., 2:00 PM - 3:30 PM].
Job ID: [ID]"
No acknowledgment required
Alert 6.2: Work Completion Notice¶
Trigger: Technician marks work as complete and approved by Store Manager
Recipients: Customer
Channels: Web (default) + Optional SMS (if opted-in)
Message Template:
Web (Customer Portal):
Title: "Service Complete"
Body: "Your service is complete! Technician [Tech Name]
completed work at [Time].
Invoice will follow. Thank you!
Job ID: [ID]
[View invoice] [Rate service] [Contact support]"
SMS (if opted-in):
"Your service is complete! Technician [Tech Name]
completed work at [Time]. Invoice will follow.
Thank you! Job ID: [ID]"
No acknowledgment required
Alert 6.3: Work Cancelled / Not Completed¶
Trigger: Work order cancelled by dispatcher or technician unable to complete
Recipients: Customer
Channels: Web (default) + Optional SMS (if opted-in)
Message Template:
Web (Customer Portal):
Title: "Service Update"
Body: "Update on your service request [Job ID]:
Work was not completed.
Reason: [e.g., equipment shortage, customer not available].
Next steps: [Reschedule / Contact support]
Customer Service: [Phone Number]
[Reschedule] [Contact us]"
SMS (if opted-in):
"Update: Service request [Job ID] not completed.
Reason: [Brief reason].
Please call us at [Support Number]."
No acknowledgment required
Alert Recipients & Routing Logic¶
Role-Based Alert Routing¶
Central Dispatcher(s)¶
Receives alerts for: - New work order submitted (Primary recipient) - Work order comments/updates from REACH (Secondary) - Technician assignment acceptance/rejection (Secondary) - Status updates (informational - web only) - Equipment issues (Secondary) - SLA breach warnings (Secondary) - Technician unresponsive (Secondary) - Unassigned work orders (Primary at 15 min, Secondary at 30 min)
Default Channels: Web (primary) + SMS (if offline or high priority)
Acknowledgment: Tracked for response time metrics
Store Manager(s)¶
Receives alerts for: - New work order submitted (Secondary - if at their location) - Technician assignment confirmation (Primary) - Work completion (Primary - requires approval) - Work rejection/rework needed (Secondary) - Missing photos (Secondary) - Equipment shortage (Secondary) - SLA warnings (Secondary) - Technician unresponsive (Primary) - No response to assignment (Primary at 5 min) - Status updates (informational - web/in-app only)
Default Channels: Web + In-App (primary) + SMS (if urgent)
Acknowledgment: Required for work order approvals; tracked for response times
Assigned Technician(s)¶
Receives alerts for: - Work assignment (Primary - requires accept/decline) - Work comments/updates from REACH (Primary) - Work rejection with comments (Primary - requires resubmission) - Missing photos (Primary - mandatory) - Status confirmation (informational)
Default Channels: Mobile Push + SMS (primary for assignments)
Acknowledgment: Required for assignments; tracked for response times
Regional Dispatcher(s)¶
Receives alerts for: - Unassigned work orders >30 min (Primary - escalation) - SLA breaches (Primary) - Equipment critical issues (Primary) - Repeated technician assignment failures (Secondary - pattern detection)
Default Channels: SMS + Phone Call (for critical alerts)
Acknowledgment: Required for critical alerts (phone call); tracked for escalation
Billing Clerk(s)¶
Receives alerts for: - Work order approved & ready for billing (Secondary - informational)
Default Channels: Web + In-App (no SMS needed)
Acknowledgment: Not required
Customer Service Rep(s)¶
Receives alerts for: - Customer escalations (if integrated with CRM) - High-priority SLA issues (if integrated)
Default Channels: Web + In-App
Acknowledgment: Not required
Admin / System Administrators¶
Receives alerts for: - SLA breaches (reporting/trending) - System issues affecting alerts - Critical exceptions
Default Channels: Web (reporting/dashboard)
Acknowledgment: Not required (informational)
Routing Logic by Alert Type¶
Alert Created
↓
Determine Priority (Low/Medium/High/Critical)
↓
Identify Primary Recipient by Role
↓
Identify Secondary Recipients (if applicable)
↓
Check Recipient Availability:
├─ Online → Web + Mobile Push
├─ Offline but in app → Web (queued)
└─ Do Not Disturb (if enabled) → SMS only or queue
↓
Check Time Window:
├─ Business Hours → All channels
├─ After Hours (if critical) → Phone Call + SMS
└─ Weekend (if critical) → Phone Call required
↓
Send to Selected Channel(s)
↓
Start Acknowledgment Timer (if required)
↓
Log in Audit Trail
Escalation Workflows¶
5-Minute Escalation Rule¶
Pomp's Dispatch Center implements a global 5-minute acknowledgment threshold for all time-critical alerts. If the primary recipient does not acknowledge the alert within 5 minutes, the system automatically escalates to the next recipient in the chain.
Escalation Chain Examples¶
New Work Order Escalation¶
T+0: Work order submitted
└─→ Alert: Central Dispatcher
T+5 (if no acknowledgment): Escalate
└─→ Alert: Regional Dispatcher + SMS
T+10 (if still no ack): Escalate
└─→ Alert: Admin + Phone Call (if critical priority)
T+15: Manual intervention required
└─→ Operations Manager contacted
Technician Assignment Escalation¶
T+0: Work assigned to technician
└─→ Mobile Push + SMS: Technician
T+5 (if no accept/decline): Escalate
└─→ Alert: Store Manager (web + SMS)
T+10 (if still pending): Escalate
└─→ Alert: Dispatcher (SMS + web)
T+15: Reassignment required
└─→ Manual work order reassignment
└─→ Alert: Technician with new assignment
Equipment Shortage Escalation¶
T+0: Equipment issue reported by technician
└─→ SMS: Dispatcher
└─→ Web: Store Manager
T+5 (if no acknowledgment): Escalate
└─→ Phone Call: Regional Dispatcher + SMS confirmation required
T+10 (if unresolved): Escalate
└─→ Alert: Operations Manager + Admin
T+15: Work order must be cancelled or reassigned
└─→ Dispatcher makes final decision
SLA Breach Escalation¶
T+0: Work order exceeds SLA deadline
└─→ Phone Call: Regional Dispatcher (must acknowledge)
└─→ SMS: Central Dispatcher
T+5 (if no ack): Re-call Regional Dispatcher
T+15: Alert: Admin for compliance reporting
Escalation Recipient Rules¶
| Current Level | Escalates To | Method | Max Escalation |
|---|---|---|---|
| Dispatcher | Regional Dispatcher | SMS + Phone Call (if critical) | 2x escalations |
| Store Manager | Dispatcher | SMS + Web | 1x escalation |
| Technician | Store Manager → Dispatcher | SMS + Mobile Push | 2x escalations |
| Regional Dispatcher | Admin + Operations Manager | Phone Call | Final (no further escalation) |
Customer Notification Strategy¶
Customer Notification Scope¶
Per business requirements, customers receive exactly three notifications during a work order lifecycle via web notifications by default. Customers may opt-in to receive SMS notifications instead or in addition to web notifications:
- Work Assignment & ETA - When technician is assigned and ETA confirmed
- Work Completion - When work is approved and complete
- Work Cancelled/Not Completed - If work cannot be completed
Customer Notification Workflow¶
Work Order Assigned
↓
Technician ETA Confirmed
↓
Check Customer Notification Preference
├─ Web Notifications (Default)
│ └─ Display in customer portal: "Service assigned. Technician [Name] arriving [ETA]"
│
└─ SMS Opt-In (Optional)
└─ SMS: "Service assigned. Technician [Name] arriving [ETA]"
↓
Work In Progress
↓
(No customer updates during work)
↓
Technician Completes & Store Manager Approves
↓
Web: Notification in customer portal
+ Optional SMS (if opted-in): "Service complete at [Time]. Invoice follows. Thank you!"
↓
Alternative: Work Cancelled
↓
Web: Notification in customer portal
+ Optional SMS (if opted-in): "Service not completed. Reason: [Brief reason].
Please call [Support Number]"
What Customers Do NOT Receive¶
- ❌ Intermediate status updates (on my way, arrived, etc.)
- ❌ Equipment shortage alerts
- ❌ Technician reassignment notifications
- ❌ SLA breach notifications
- ❌ Delay warnings
- ❌ Work order comments/updates
- ❌ Rejection/rework notifications
Customer Notification Preferences¶
Default Notification Channel: Web notifications in customer portal
Optional SMS Opt-In: - Customers may opt-in to receive SMS notifications - Opt-in preference stored in customer profile - System sends notifications via web by default - If SMS opt-in enabled, sends SMS in addition to or instead of web - Customers can manage preferences: "Web only", "SMS only", or "Both" - One-click unsubscribe from SMS notifications (STOP command compliance)
Customer SMS Guidelines (if opted-in)¶
- Message Length: 160 characters (single SMS)
- Tone: Professional, concise, actionable
- Content: Only essential info (ETA, completion, cancellation)
- Phone Number: Masked/generic (Genesys Cloud inbound number)
- Response Handling: Do not process customer replies via SMS (direct to Customer Service phone number)
- Opt-Out: Customers can reply "STOP" to unsubscribe from SMS
STOP Compliance¶
- Customers who reply "STOP" to SMS are automatically opted out
- System updates customer profile to reflect SMS opt-out
- Subsequent notifications sent via web only (if still active)
- Compliance with TCPA and carrier regulations
Genesys Cloud Integration¶
Genesys Cloud Architecture¶
Pomp's Dispatch Center integrates with Genesys Cloud for SMS and phone call delivery of time-critical alerts.
Dispatch Alert Service
↓
Genesys Cloud API Gateway
├─ SMS Channel (Twilio/AWS backend)
│ └─ Queue SMS messages
│ └─ Track delivery & opt-out status
│ └─ Log message content & recipient
│
└─ Voice Channel (PureCloud IVR)
└─ Queue outbound calls
└─ Play pre-recorded announcement
└─ Collect DTMF confirmation (Press 1 to confirm)
└─ Log call recording & confirmation
SMS Integration¶
Genesys Cloud SMS API¶
Endpoint: https://api.genesyscloud.com/api/v2/messaging/integrations/open
Authentication: OAuth 2.0 (API Token in Authorization header)
Message Format:
{
"toAddress": "+1-555-123-4567",
"toAddressMessengerType": "sms",
"textBody": "New WO: [Customer] @ [Address]. Priority: [Level]. Ref: [ID]",
"fromAddress": "+1-555-POMPS-9", // Masked Pomp's number via Genesys Cloud
"customAttributes": {
"alertType": "NEW_WORK_ORDER",
"workOrderId": "WO-12345",
"priority": "HIGH",
"timestamp": "2026-01-29T14:30:00Z",
"customerOptIn": true
}
}
Rate Limiting: 100 SMS/second per tenant
Delivery Status Tracking:
- PENDING - Sent to Genesys queue
- QUEUED - Awaiting carrier delivery
- DELIVERED - Received by carrier
- FAILED - Delivery failed (with reason code)
- OPTED_OUT - Recipient has opted out
Customer Opt-In Check:
Always verify customerOptIn: true in customAttributes before sending customer SMS notifications
SMS Opt-In Compliance¶
// Check if customer has opted into SMS notifications
public async Task<bool> CanSendSMSAsync(string customerId, string alertType)
{
var customer = await _customerService.GetByIdAsync(customerId);
var smsPreference = customer.NotificationPreferences?.SMSOptIn ?? false;
if (!smsPreference)
{
// Customer has not opted into SMS
// Send via web notification instead
await _auditService.LogAsync("SMS.SkippedNoOptIn", customerId, alertType);
return false;
}
// Check Genesys opt-out list (TCPA/carrier regulations)
var optOutStatus = await _genesysService
.CheckOptOutStatusAsync(customer.PhoneNumber);
if (optOutStatus.IsOptedOut)
{
// Customer replied STOP or is on do-not-call list
await _customerService.UpdateSMSOptInAsync(customerId, optIn: false);
await _auditService.LogAsync("SMS.OptOut", customerId, "Genesys compliance");
return false;
}
return true;
}
// Auto-process opt-out from SMS response
// If customer replies "STOP" or "UNSUBSCRIBE"
public async Task ProcessStopResponseAsync(string phoneNumber, string messageContent)
{
if (messageContent.Contains("STOP", StringComparison.OrdinalIgnoreCase) ||
messageContent.Contains("UNSUBSCRIBE", StringComparison.OrdinalIgnoreCase))
{
// Find customer by phone number
var customer = await _customerService.GetByPhoneNumberAsync(phoneNumber);
// Opt them out of SMS
await _customerService.UpdateSMSOptInAsync(customer.Id, optIn: false);
// Log compliance action
await _auditService.LogAsync("SMS.OptOut", customer.Id, "Customer-initiated STOP response");
// Send confirmation (final message per TCPA)
await _genesysService.SendSMSAsync(new SMSRequest
{
ToNumber = phoneNumber,
Body = "You have been unsubscribed from service notifications. "
});
}
}
// Customer SMS preference model
public class CustomerNotificationPreferences
{
public int CustomerId { get; set; }
public bool WebNotificationsEnabled { get; set; } = true; // Default
public bool SMSOptIn { get; set; } = false; // Must explicitly opt-in
public string PreferredChannel { get; set; } // "Web", "SMS", or "Both"
public DateTime LastModified { get; set; }
public DateTime OptInDate { get; set; } // When customer opted into SMS
public string OptInIpAddress { get; set; } // For verification
}
Phone Call Integration (IVR)¶
Genesys Cloud Voice API¶
Endpoint: https://api.genesyscloud.com/api/v2/outbound/campaigns
Use Cases: - Critical SLA breaches (requires immediate acknowledgment) - Equipment critical issues - Escalated alerts (no response after 5 min SMS)
Outbound Call Flow:
Genesys Cloud IVR
↓
Ring Phone
↓
Recipient Answers
↓
Play Pre-recorded Message:
"Critical dispatch alert: [Alert message].
Press 1 to acknowledge this alert."
↓
DTMF Confirmation:
├─ Press 1 → Acknowledged
│ └─ Log confirmation
│ └─ Update alert status
│ └─ Cancel escalations
│
├─ No Response (10 sec timeout) → Repeat message
│
└─ Hangs Up → Retry call in 2 minutes (max 3 attempts)
Phone Call Message Templates¶
SLA Breach Alert:
"CRITICAL alert. Work order number [ID]
for customer [Name] has exceeded the agreed service level agreement.
Current delay: [Hours] hours.
Assigned technician: [Tech Name].
Press 1 to acknowledge receipt and initiate resolution."
Equipment Critical Issue:
"Critical equipment shortage reported.
Work order number [ID] at [Store Name]
requires immediate attention.
Equipment needed: [Item].
Press 1 to acknowledge and confirm action."
Regional Dispatcher Escalation:
"Escalation alert. Multiple work orders unresolved in [Region].
Unassigned jobs: [Count].
SLA breaches: [Count].
Please review dispatch board immediately.
Press 1 to confirm receipt."
Phone Call Configuration¶
// Genesys Cloud outbound call configuration
public class CriticalAlertCallConfig
{
public string ToPhoneNumber { get; set; } // Regional Dispatcher mobile
public string AlertType { get; set; } // SLA_BREACH, EQUIPMENT_CRITICAL, etc.
public string IVRScript { get; set; } // Pre-recorded message
public int RetryAttempts { get; set; } = 3; // Max 3 call attempts
public int RetryIntervalSeconds { get; set; } = 120; // Retry every 2 min
public int DTMFTimeoutSeconds { get; set; } = 10; // Wait for confirmation
public string RecordingQueue { get; set; } = "dispatch_alerts_recordings";
public bool RequireRecording { get; set; } = true; // Record all calls
public int CallMaxDurationSeconds { get; set; } = 120; // 2 min max
}
Webhook Callbacks from Genesys Cloud¶
Genesys Cloud sends webhooks back to Dispatch system for delivery status updates:
{
"eventType": "message.delivered",
"timestamp": "2026-01-29T14:32:15Z",
"data": {
"messageId": "msg-xyz789",
"alertId": "alert-123456",
"toNumber": "+1-555-123-4567",
"deliveryStatus": "DELIVERED",
"reason": "Delivered to carrier",
"genesysReference": "GCLOUD-987654"
}
}
{
"eventType": "call.completed",
"timestamp": "2026-01-29T14:35:42Z",
"data": {
"callId": "call-abc123",
"alertId": "alert-123456",
"toNumber": "+1-555-987-6543",
"status": "CONFIRMED", // or FAILED, NO_ANSWER, etc.
"dtmfInput": "1", // Confirmation received
"recordingUrl": "https://genesys.recordings.com/...",
"duration": 45 // seconds
}
}
Genesys Cloud Configuration & Credentials¶
// Startup configuration
services.AddScoped<IGenesysCloudService, GenesysCloudService>();
// In appsettings.json
{
"GenesysCloud": {
"ClientId": "[OAuth Client ID]",
"ClientSecret": "[OAuth Client Secret]",
"RegionUrl": "https://api.us-east-1.genesyscloud.com",
"BaseUrl": "https://api.genesyscloud.com",
"Credentials": {
"TokenUrl": "https://login.us-east-1.genesyscloud.com/oauth/token",
"TokenExpirationMinutes": 30
},
"SMS": {
"FromNumber": "+1-555-POMPS-9", // Masked number for outbound SMS
"SenderId": "Pomp's Dispatch",
"MaxRetries": 3,
"RetryDelaySeconds": 30
},
"Voice": {
"IVRQueueName": "dispatch_alerts_ivr",
"MaxRetries": 3,
"RetryDelayMinutes": 2,
"RecordingEnabled": true
}
}
}
Alert Acknowledgment & Tracking¶
Acknowledgment Requirements by Alert Type¶
| Alert Type | Acknowledgment | Timeout | Consequence of No Ack |
|---|---|---|---|
| New Work Order | Optional | N/A | Escalate to Regional Dispatcher |
| Technician Assignment | Required | 5 min | Escalate to Store Manager |
| Work Completion | Required | N/A (Store Manager) | Work remains pending |
| Rejection/Rework | Required | 5 min | Escalate to Dispatcher |
| Equipment Critical | Required | 5 min | Escalate to Regional Dispatcher + Phone Call |
| SLA Breach | Required | Phone call | Escalate to Admin |
| All Other Alerts | Optional | N/A | Logged but no escalation |
Acknowledgment Tracking¶
// Alert acknowledgment entity
public class AlertAcknowledgment
{
public int AlertId { get; set; }
public int UserId { get; set; }
public DateTime AlertCreatedTime { get; set; }
public DateTime? AcknowledgedTime { get; set; }
public bool IsAcknowledged => AcknowledgedTime.HasValue;
public TimeSpan ResponseTime =>
(AcknowledgedTime ?? DateTime.UtcNow) - AlertCreatedTime;
public string AcknowledgmentChannel { get; set; } // Web, Mobile, SMS, Phone
public string AcknowledgmentAction { get; set; } // Accept, Approve, Confirm, etc.
public string IpAddress { get; set; }
public string DeviceId { get; set; }
}
// Service for tracking acknowledgments
public async Task<bool> AcknowledgeAlertAsync(
int alertId,
int userId,
string action,
string channel)
{
var alert = await _alertRepository.GetByIdAsync(alertId);
if (!alert.RequiresAcknowledgment)
return false; // Alert doesn't require acknowledgment
var ack = new AlertAcknowledgment
{
AlertId = alertId,
UserId = userId,
AlertCreatedTime = alert.CreatedTime,
AcknowledgedTime = DateTime.UtcNow,
AcknowledgmentChannel = channel,
AcknowledgmentAction = action
};
await _acknowledgmentRepository.AddAsync(ack);
// Stop escalation timers
await _escalationService.CancelEscalationsAsync(alertId);
// Update alert status
alert.Status = AlertStatus.Acknowledged;
alert.AcknowledgedBy = userId;
alert.AcknowledgedTime = DateTime.UtcNow;
await _alertRepository.UpdateAsync(alert);
// Log in audit trail
await _auditService.LogAsync("Alert.Acknowledged", alertId, userId);
return true;
}
Response Time Metrics¶
Acknowledgment response times are tracked for SLA monitoring:
| Role | Target Response Time | Alert Type |
|---|---|---|
| Dispatcher | < 2 minutes | New Work Order |
| Dispatcher | < 1 minute | Equipment Critical |
| Store Manager | < 3 minutes | Technician Assignment |
| Store Manager | < 5 minutes | Work Completion |
| Technician | < 2 minutes | New Assignment |
| Regional Dispatcher | < 5 minutes | SLA Breach |
Mobile App Alert Behavior¶
React Native Push Notification Handling¶
Alert Delivery States¶
Foreground (App is open): - Display banner alert at top of screen - Play notification sound (if enabled) - Increment badge count - Navigate to relevant screen on tap - Log acknowledgment when user interacts
Background (App is running but not in foreground): - Display system push notification - Show app icon badge - Queue notification for when app returns to foreground - Navigate to relevant screen on tap
Killed (App is force-closed): - Display system push notification in notification center - Show app icon badge - Queue notification until app reopened - Display queued notifications when app launches
Push Notification Configuration¶
// React Native alert configuration
export const AlertNotificationConfig = {
channels: {
work_assignment: {
name: 'Work Assignment',
description: 'New work orders assigned to you',
priority: 'high',
sound: 'default',
vibrate: true,
lights: true
},
status_update: {
name: 'Status Updates',
description: 'Updates on your work orders',
priority: 'default',
sound: 'default',
vibrate: false
},
critical_alert: {
name: 'Critical Alerts',
description: 'Critical issues requiring immediate action',
priority: 'high',
sound: 'critical_alert',
vibrate: [100, 200, 100] // Pattern vibration
}
}
}
// Listen for push notifications
useEffect(() => {
const unsubscribe = messaging().onMessage(async (remoteMessage) => {
const alertData = {
alertType: remoteMessage.data.alertType,
workOrderId: remoteMessage.data.workOrderId,
priority: remoteMessage.data.priority
}
// Display alert banner
showAlertBanner(remoteMessage.notification.title,
remoteMessage.notification.body)
// Navigate to relevant screen
navigateToAlertContext(alertData)
// Log acknowledgment
logAlertView(alertData)
})
return unsubscribe
}, [])
Badge Count & Deep Linking¶
// Update app badge count
const updateBadgeCount = async (count: number) => {
notifee.setBadgeCount(count)
}
// Deep link to alert context
const handleAlertTap = (alertData: AlertData) => {
switch (alertData.alertType) {
case 'WORK_ASSIGNMENT':
navigation.navigate('Assignments', {
workOrderId: alertData.workOrderId
})
break
case 'WORK_REJECTION':
navigation.navigate('ReworkNeeded', {
workOrderId: alertData.workOrderId
})
break
case 'MISSING_PHOTOS':
navigation.navigate('PhotoCapture', {
workOrderId: alertData.workOrderId
})
break
// ... other cases
}
}
Offline Queue & Sync¶
// Queue alerts received while offline
const queueOfflineAlert = async (alert: Alert) => {
const db = await getDatabase()
await db.addAsync('pending_alerts', {
...alert,
synced: false,
queuedTime: Date.now()
})
}
// Sync pending alerts when online
useEffect(() => {
const subscription = NetInfo.addEventListener(state => {
if (state.isConnected) {
syncPendingAlerts()
}
})
return () => subscription()
}, [])
const syncPendingAlerts = async () => {
const db = await getDatabase()
const pending = await db.queryAsync('pending_alerts',
(record) => record.synced === false)
for (const alert of pending) {
try {
// Send acknowledgment to server
await api.acknowledgeAlert(alert.id)
// Mark as synced
await db.updateAsync('pending_alerts', alert.id,
{ synced: true })
} catch (error) {
console.error('Sync failed for alert', alert.id)
}
}
}
Web Dashboard Alert Display¶
Alert Notification Center¶
The web dashboard (Vue.js 3) displays alerts in a dedicated notification center with filtering, search, and history.
Alert Center UI Components¶
// Alert notification panel (Vue 3 Composition API)
<template>
<div class="alert-center">
<!-- Header -->
<div class="alert-header">
<h3>Notifications</h3>
<div class="alert-controls">
<button @click="markAllAsRead" v-if="unreadCount > 0">
Mark all as read
</button>
<button @click="clearAll">Clear All</button>
</div>
</div>
<!-- Alert Tabs -->
<div class="alert-tabs">
<button
:class="{ active: filterType === 'all' }"
@click="filterType = 'all'">
All ({{ totalCount }})
</button>
<button
:class="{ active: filterType === 'unread' }"
@click="filterType = 'unread'">
Unread ({{ unreadCount }})
</button>
<button
:class="{ active: filterType === 'critical' }"
@click="filterType = 'critical'">
Critical ({{ criticalCount }})
</button>
</div>
<!-- Alert List -->
<div class="alert-list">
<div
v-for="alert in filteredAlerts"
:key="alert.id"
:class="['alert-item', alert.priority.toLowerCase(),
{ unread: !alert.isRead }]"
@click="selectAlert(alert)">
<!-- Alert Icon by Type -->
<div class="alert-icon">
<i :class="getIconClass(alert.type)"></i>
</div>
<!-- Alert Content -->
<div class="alert-content">
<h4>{{ alert.title }}</h4>
<p class="alert-message">{{ alert.message }}</p>
<p class="alert-time">{{ formatTime(alert.createdTime) }}</p>
</div>
<!-- Alert Priority Badge -->
<div class="alert-badge" :class="alert.priority.toLowerCase()">
{{ alert.priority }}
</div>
<!-- Read Status -->
<div class="alert-read-status">
<div v-if="!alert.isRead" class="unread-indicator"></div>
</div>
<!-- Action Buttons (if applicable) -->
<div class="alert-actions" v-if="alert.requiresAction">
<button
@click.stop="handleAlertAction(alert, 'acknowledge')"
class="btn-primary">
{{ alert.actionLabel }}
</button>
</div>
</div>
<!-- Empty State -->
<div v-if="filteredAlerts.length === 0" class="alert-empty">
<p>No alerts to display</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useAlertStore } from '@/stores/alerts'
const alertStore = useAlertStore()
const filterType = ref('all')
const filteredAlerts = computed(() => {
const alerts = alertStore.alerts
switch (filterType.value) {
case 'unread':
return alerts.filter(a => !a.isRead)
case 'critical':
return alerts.filter(a => a.priority === 'Critical')
default:
return alerts
}
})
const unreadCount = computed(() =>
alertStore.alerts.filter(a => !a.isRead).length
)
const criticalCount = computed(() =>
alertStore.alerts.filter(a => a.priority === 'Critical').length
)
const totalCount = computed(() => alertStore.alerts.length)
const selectAlert = (alert) => {
alertStore.selectAlert(alert)
alertStore.markAsRead(alert.id)
}
const markAllAsRead = () => {
alertStore.markAllAsRead()
}
const handleAlertAction = async (alert, action) => {
await alertStore.acknowledgeAlert(alert.id, action)
}
const getIconClass = (type: string) => {
const iconMap = {
'WORK_ASSIGNMENT': 'icon-assignment',
'EQUIPMENT_ISSUE': 'icon-warning',
'SLA_BREACH': 'icon-alert-triangle',
'WORK_COMPLETE': 'icon-check-circle',
// ... other types
}
return iconMap[type] || 'icon-notification'
}
const formatTime = (time: Date) => {
// Format as relative time (e.g., "5 minutes ago")
return getRelativeTime(time)
}
const clearAll = () => {
if (confirm('Clear all alerts?')) {
alertStore.clearAll()
}
}
</script>
<style scoped>
.alert-center {
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
max-height: 600px;
overflow-y: auto;
}
.alert-header {
padding: 16px;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.alert-item {
padding: 12px 16px;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
transition: background-color 0.2s;
}
.alert-item:hover {
background-color: #f9f9f9;
}
.alert-item.unread {
background-color: #f5f5ff;
border-left: 4px solid #0066cc;
}
.alert-item.critical {
border-left: 4px solid #cc0000;
}
.alert-item.high {
border-left: 4px solid #ff6600;
}
.alert-icon {
font-size: 20px;
min-width: 24px;
}
.alert-content {
flex: 1;
min-width: 0;
}
.alert-content h4 {
margin: 0 0 4px 0;
font-size: 14px;
font-weight: 600;
}
.alert-message {
margin: 0;
font-size: 13px;
color: #666;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.alert-time {
margin: 4px 0 0 0;
font-size: 12px;
color: #999;
}
.alert-badge {
padding: 2px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
}
.alert-badge.critical {
background-color: #ffcccc;
color: #cc0000;
}
.alert-badge.high {
background-color: #ffe6cc;
color: #ff6600;
}
.alert-badge.medium {
background-color: #fff3cc;
color: #ff9900;
}
.unread-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #0066cc;
}
</style>
Toast Notifications for Urgent Alerts¶
// Toast notification for critical alerts
const showCriticalAlert = (alert: Alert) => {
showToast({
type: 'error', // or 'warning', 'success', 'info'
title: alert.title,
message: alert.message,
duration: 0, // Don't auto-dismiss critical
actions: [
{
label: 'View',
onClick: () => navigateToAlert(alert)
},
{
label: 'Acknowledge',
onClick: () => acknowledgeAlert(alert.id)
}
]
})
}
Implementation Details¶
Alert Service Architecture¶
// Core alert service
public interface IDispatchAlertService
{
Task SendAlertAsync(DispatchAlert alert);
Task SendAlertAsync(string alertType,
Dictionary<string, object> data,
List<int> recipientIds);
Task EscalateAlertAsync(int alertId);
Task AcknowledgeAlertAsync(int alertId, int userId);
}
// Implementation
public class DispatchAlertService : IDispatchAlertService
{
private readonly IGenesysCloudService _genesysService;
private readonly IWebSocketService _webSocketService;
private readonly IPushNotificationService _pushService;
private readonly IAlertRepository _alertRepository;
private readonly IEscalationService _escalationService;
private readonly IAuditService _auditService;
public async Task SendAlertAsync(DispatchAlert alert)
{
// Validate alert
if (!ValidateAlert(alert))
throw new InvalidAlertException("Invalid alert data");
// Determine recipients
var recipients = await DetermineRecipientsAsync(alert);
// Determine channels
var channels = DetermineChannels(alert.Priority, recipients);
// Send via appropriate channels
foreach (var recipient in recipients)
{
foreach (var channel in channels)
{
await SendViaChannelAsync(alert, recipient, channel);
}
}
// Save alert to database
await _alertRepository.AddAsync(alert);
// Schedule escalation if required
if (alert.RequiresAcknowledgment)
{
await _escalationService.ScheduleEscalationAsync(
alert.Id,
TimeSpan.FromMinutes(5));
}
// Log in audit trail
await _auditService.LogAsync("Alert.Sent", alert.Id);
}
private async Task SendViaChannelAsync(
DispatchAlert alert,
AlertRecipient recipient,
AlertChannel channel)
{
switch (channel)
{
case AlertChannel.Web:
await SendWebNotificationAsync(alert, recipient);
break;
case AlertChannel.MobilePush:
await SendMobilePushAsync(alert, recipient);
break;
case AlertChannel.SMS:
await SendSMSAsync(alert, recipient);
break;
case AlertChannel.PhoneCall:
await SendPhoneCallAsync(alert, recipient);
break;
case AlertChannel.InApp:
await SendInAppMessageAsync(alert, recipient);
break;
}
}
private async Task SendWebNotificationAsync(
DispatchAlert alert,
AlertRecipient recipient)
{
// Send via WebSocket to connected clients
await _webSocketService.SendToUserAsync(
recipient.UserId,
new WebSocketMessage
{
Type = "alert",
Data = new
{
alert.Id,
alert.Title,
alert.Message,
alert.Priority,
alert.CreatedTime
}
});
}
private async Task SendMobilePushAsync(
DispatchAlert alert,
AlertRecipient recipient)
{
// Send push notification via Firebase Cloud Messaging
await _pushService.SendAsync(new PushNotificationRequest
{
UserId = recipient.UserId,
Title = alert.Title,
Body = alert.Message,
Data = new Dictionary<string, string>
{
{ "alertId", alert.Id.ToString() },
{ "alertType", alert.AlertType },
{ "priority", alert.Priority.ToString() }
},
Priority = alert.Priority == AlertPriority.Critical ?
PushPriority.High : PushPriority.Default
});
}
private async Task SendSMSAsync(
DispatchAlert alert,
AlertRecipient recipient)
{
// Check if recipient can receive SMS
if (!await _genesysService.CanSendSMSAsync(recipient.PhoneNumber))
{
await _auditService.LogAsync(
"SMS.Failed",
alert.Id,
"Recipient opted out");
return;
}
// Send via Genesys Cloud SMS API
await _genesysService.SendSMSAsync(new SMSRequest
{
ToNumber = recipient.PhoneNumber,
Body = alert.SMSBody,
CustomAttributes = new Dictionary<string, string>
{
{ "alertId", alert.Id.ToString() },
{ "alertType", alert.AlertType },
{ "priority", alert.Priority.ToString() }
}
});
}
private async Task SendPhoneCallAsync(
DispatchAlert alert,
AlertRecipient recipient)
{
// Initiate outbound call via Genesys Cloud IVR
var callRequest = new OutboundCallRequest
{
ToNumber = recipient.PhoneNumber,
IVRScript = alert.PhoneCallScript,
RetryAttempts = 3,
RetryIntervalSeconds = 120,
DTMFTimeoutSeconds = 10,
RecordingEnabled = true,
CustomAttributes = new Dictionary<string, string>
{
{ "alertId", alert.Id.ToString() },
{ "alertType", alert.AlertType }
}
};
var callId = await _genesysService.InitiateOutboundCallAsync(callRequest);
// Log call initiation
await _auditService.LogAsync(
"PhoneCall.Initiated",
alert.Id,
$"CallId: {callId}");
}
private async Task SendInAppMessageAsync(
DispatchAlert alert,
AlertRecipient recipient)
{
// Store in-app message in database for retrieval by app
await _alertRepository.AddInAppMessageAsync(new InAppMessage
{
UserId = recipient.UserId,
Title = alert.Title,
Body = alert.Message,
Priority = alert.Priority,
CreatedTime = DateTime.UtcNow,
ExpiresTime = DateTime.UtcNow.AddHours(24)
});
}
public async Task EscalateAlertAsync(int alertId)
{
var alert = await _alertRepository.GetByIdAsync(alertId);
if (alert.IsAcknowledged)
return; // No escalation needed
// Determine next escalation recipient(s)
var escalationRecipients = await
DetermineEscalationRecipientsAsync(alert);
// Send escalation alerts
foreach (var recipient in escalationRecipients)
{
var escalationAlert = new DispatchAlert
{
OriginalAlertId = alert.Id,
AlertType = alert.AlertType,
Title = $"[ESCALATED] {alert.Title}",
Message = alert.Message,
Priority = alert.Priority > AlertPriority.High ?
alert.Priority : AlertPriority.High,
RequiresAcknowledgment = true,
CreatedTime = DateTime.UtcNow
};
await SendAlertAsync(escalationAlert);
}
// Log escalation
await _auditService.LogAsync("Alert.Escalated", alertId);
}
}
Alert Data Model¶
// Alert entity
public class DispatchAlert
{
public int Id { get; set; }
public string AlertType { get; set; } // NEW_WORK_ORDER, TECH_ASSIGNED, etc.
public string Title { get; set; }
public string Message { get; set; }
public string SMSBody { get; set; } // SMS-specific message (160 chars)
public string PhoneCallScript { get; set; } // IVR script
public AlertPriority Priority { get; set; } // Low, Medium, High, Critical
public DateTime CreatedTime { get; set; }
// Context
public int? WorkOrderId { get; set; }
public int? TechnicianId { get; set; }
public int? StoreId { get; set; }
public int? CustomerId { get; set; }
// Acknowledgment
public bool RequiresAcknowledgment { get; set; }
public bool IsAcknowledged { get; set; }
public int? AcknowledgedByUserId { get; set; }
public DateTime? AcknowledgedTime { get; set; }
public string AcknowledgmentAction { get; set; } // Accept, Approve, Confirm
// Escalation
public int? OriginalAlertId { get; set; } // If this is escalated alert
public bool IsEscalated { get; set; }
public DateTime? EscalationScheduledTime { get; set; }
// Delivery tracking
public List<AlertDelivery> Deliveries { get; set; } = new();
}
public enum AlertPriority
{
Low = 1,
Medium = 2,
High = 3,
Critical = 4
}
public class AlertDelivery
{
public int Id { get; set; }
public int AlertId { get; set; }
public int RecipientUserId { get; set; }
public AlertChannel Channel { get; set; } // Web, SMS, Phone, Push
public AlertDeliveryStatus Status { get; set; }
public DateTime SentTime { get; set; }
public DateTime? DeliveredTime { get; set; }
public DateTime? ReadTime { get; set; }
public string ExternalReference { get; set; } // Genesys Cloud ref, SMS ID, etc.
}
public enum AlertChannel
{
Web = 1,
MobilePush = 2,
SMS = 3,
PhoneCall = 4,
InApp = 5
}
public enum AlertDeliveryStatus
{
Pending = 0,
Sent = 1,
Delivered = 2,
Failed = 3,
OptedOut = 4,
NoResponse = 5
}
Alert Configuration & Rules¶
Global Alert Rules¶
All 200+ stores follow these global, standardized alert rules:
# Global alert configuration (appsettings.json)
AlertConfiguration:
GlobalRules: true # No per-store customization
EscalationRules:
DefaultTimeout: 300 # 5 minutes in seconds
MaxEscalations: 3
EscalationChain:
Level1: Primary recipient (Dispatcher, etc.)
Level2: Secondary recipient (Store Manager, Regional Dispatcher, etc.)
Level3: Tertiary recipient (Admin, Operations Manager)
Channels:
Web:
Enabled: true
Priority: 1 # Send first
MobilePush:
Enabled: true
Priority: 2
SMS:
Enabled: true
Priority: 3
OptOutCompliance: true
PhoneCall:
Enabled: true
Priority: 4 # Only for critical
RetryAttempts: 3
RetryIntervalSeconds: 120
InApp:
Enabled: true
Priority: 2
AlertTypes:
NewWorkOrder:
Priority: High
Channels: [Web, SMS]
Recipients: [Dispatcher, StoreManager]
RequiresAck: false
Escalation: true
TechnicianAssignment:
Priority: High
Channels: [MobilePush, SMS]
Recipients: [Technician]
RequiresAck: true
Escalation: true
EscalationTo: [StoreManager, Dispatcher]
WorkCompletion:
Priority: High
Channels: [Web, InApp]
Recipients: [StoreManager]
RequiresAck: true
Escalation: false
EquipmentCritical:
Priority: Critical
Channels: [SMS, PhoneCall, Web]
Recipients: [Dispatcher, StoreManager]
RequiresAck: true
Escalation: true
EscalationTo: [RegionalDispatcher]
SLABreach:
Priority: Critical
Channels: [PhoneCall, SMS, Web]
Recipients: [RegionalDispatcher]
RequiresAck: true
Escalation: true
EscalationTo: [Admin]
CustomerNotifications:
Enabled: true
NotificationTypes:
- WorkAssignedETA # Only ETA
- WorkCompletion # Completion notice
- WorkCancelled # Cancellation notice
Channel: SMS # Only SMS
OptOutSupport: true
Do Not Disturb (DND) Settings¶
public class UserDNDSettings
{
public int UserId { get; set; }
public bool Enabled { get; set; }
public TimeOnly StartTime { get; set; } // e.g., 6:00 PM
public TimeOnly EndTime { get; set; } // e.g., 8:00 AM
public List<DayOfWeek> AppliedDays { get; set; }
// Critical alerts still delivered during DND?
public bool AllowCriticalDuringDND { get; set; } = true;
// Override for specific alert types
public List<string> ExemptAlertTypes { get; set; } = new();
}
// Check if alert can be sent given DND settings
public async Task<bool> CanSendToUserAsync(int userId, DispatchAlert alert)
{
var dndSettings = await _userService.GetDNDSettingsAsync(userId);
if (!dndSettings.Enabled)
return true; // DND not enabled
var now = TimeOnly.FromDateTime(DateTime.Now);
var isInDNDWindow = (dndSettings.StartTime < dndSettings.EndTime) ?
(now >= dndSettings.StartTime && now <= dndSettings.EndTime) :
(now >= dndSettings.StartTime || now <= dndSettings.EndTime);
if (!isInDNDWindow)
return true; // Not in DND window
// In DND window - check exceptions
if (alert.Priority == AlertPriority.Critical &&
dndSettings.AllowCriticalDuringDND)
return true; // Critical alerts always go through
if (dndSettings.ExemptAlertTypes.Contains(alert.AlertType))
return true; // Alert type is exempted
return false; // DND blocks this alert
}
Monitoring & Metrics¶
Alert Metrics Tracking¶
public class AlertMetrics
{
// Delivery metrics
public int TotalAlertsSent { get; set; }
public int AlertsDeliveredSuccessfully { get; set; }
public int AlertsFailedDelivery { get; set; }
public decimal DeliverySuccessRate =>
(decimal)AlertsDeliveredSuccessfully / TotalAlertsSent;
// Response metrics
public TimeSpan AverageAcknowledgmentTime { get; set; }
public int AcknowledgmentsReceived { get; set; }
public int AlertsWithoutAcknowledgment { get; set; }
// Channel metrics
public Dictionary<AlertChannel, int> AlertsByChannel { get; set; }
public Dictionary<AlertChannel, decimal> SuccessRateByChannel { get; set; }
// Escalation metrics
public int TotalEscalations { get; set; }
public int EscalationsResolved { get; set; }
// SLA metrics
public int SLABreachesDetected { get; set; }
public int TimeoutEscalations { get; set; }
}
// Service to track metrics
public class AlertMetricsService
{
public async Task<AlertMetrics> GetMetricsAsync(
DateTime startDate,
DateTime endDate)
{
var alerts = await _alertRepository
.GetByDateRangeAsync(startDate, endDate);
return new AlertMetrics
{
TotalAlertsSent = alerts.Count,
AlertsDeliveredSuccessfully = alerts
.SelectMany(a => a.Deliveries)
.Count(d => d.Status == AlertDeliveryStatus.Delivered),
AlertsFailedDelivery = alerts
.SelectMany(a => a.Deliveries)
.Count(d => d.Status == AlertDeliveryStatus.Failed),
AverageAcknowledgmentTime = CalculateAverageAckTime(alerts),
AcknowledgmentsReceived = alerts
.Count(a => a.IsAcknowledged),
AlertsWithoutAcknowledgment = alerts
.Count(a => a.RequiresAcknowledgment && !a.IsAcknowledged),
AlertsByChannel = alerts
.SelectMany(a => a.Deliveries)
.GroupBy(d => d.Channel)
.ToDictionary(g => g.Key, g => g.Count()),
TotalEscalations = alerts
.Count(a => a.IsEscalated)
};
}
}
Dashboard Alerts Widget¶
// Vue 3 component for alert metrics dashboard
<template>
<div class="alert-metrics-dashboard">
<h2>Dispatch Alert Metrics</h2>
<!-- Time Period Selector -->
<div class="controls">
<select v-model="selectedPeriod" @change="refreshMetrics">
<option value="today">Today</option>
<option value="week">This Week</option>
<option value="month">This Month</option>
<option value="custom">Custom Range</option>
</select>
</div>
<!-- Key Metrics -->
<div class="metrics-grid">
<MetricCard
title="Total Alerts Sent"
:value="metrics.totalAlertsSent"
icon="send" />
<MetricCard
title="Delivery Success Rate"
:value="`${metrics.deliverySuccessRate.toFixed(1)}%`"
icon="check-circle" />
<MetricCard
title="Avg Response Time"
:value="formatTime(metrics.averageAcknowledgmentTime)"
icon="clock" />
<MetricCard
title="SLA Breaches"
:value="metrics.slaBreachesDetected"
icon="alert-triangle" />
</div>
<!-- Channel Performance Chart -->
<div class="chart-container">
<h3>Delivery by Channel</h3>
<BarChart :data="channelData" />
</div>
<!-- Response Time Trend -->
<div class="chart-container">
<h3>Acknowledgment Response Time Trend</h3>
<LineChart :data="responseTrendData" />
</div>
<!-- Alert Type Distribution -->
<div class="chart-container">
<h3>Alerts by Type</h3>
<PieChart :data="alertTypeData" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useAlertMetricsStore } from '@/stores/alertMetrics'
const metricsStore = useAlertMetricsStore()
const selectedPeriod = ref('today')
const metrics = ref(null)
onMounted(async () => {
await refreshMetrics()
})
const refreshMetrics = async () => {
metrics.value = await metricsStore.getMetrics(selectedPeriod.value)
}
</script>
Best Practices & Guidelines¶
Alert Content Guidelines¶
Do: - ✅ Be clear & specific: "New WO: [Customer Name] @ [Address]" - ✅ Include action-oriented info: What needs to happen next - ✅ Use consistent formatting: Standardized subject lines - ✅ Respect character limits: SMS 160 chars, push title 50 chars - ✅ Include reference ID: Work Order ID for tracking - ✅ Prioritize critical info: Lead with most important detail
Don't: - ❌ Use vague language: Avoid "Alert" or "Update" as entire message - ❌ Overload with detail: Save details for dashboard/app - ❌ Use excessive punctuation: No "!!!" or "???" - ❌ Include HTML or markdown: Plain text only - ❌ Exceed 160 chars (SMS): Test message length before sending
Alert Fatigue Prevention¶
Alert Frequency Throttling:
- Same user, same alert type, same context
- If sent <5 min ago → Don't resend, update existing
- If sent 5-30 min ago → Send as summary/batch
- If sent >30 min ago → Send as new alert
Examples:
- Don't send "Equipment shortage" alert every 30 sec
- Instead: Send once, then batch updates every 5 min
- After resolution: Send single "Issue Resolved" alert
Acknowledgment Expectations¶
- Dispatcher: Expected to acknowledge new work orders in <2 min
- Store Manager: Expected to acknowledge assignments in <3 min
- Technician: Expected to respond to assignment in <2 min
- Regional Dispatcher: Expected to acknowledge SLA breaches in <5 min
Escalation Best Practices¶
- Always escalate through defined chains (don't skip levels)
- Stop escalations immediately upon acknowledgment
- Log all escalations for post-incident analysis
- Escalate based on time, not user preference
- Never escalate more than 3 levels
Crisis Mode¶
In case of critical system issue affecting alerts:
- Failover to SMS-only delivery (reliable, PSTN-based)
- Disable mobile push notifications (app-dependent)
- Activate phone call alerts for critical issues only
- Double-send critical alerts (SMS + phone call)
- Manual escalation for unacknowledged critical alerts
- Post-incident review when system restored
Summary¶
The Pomp's Dispatch Alert System is a global, standardized, multi-channel notification framework that ensures critical dispatch events reach the right people at the right time through their preferred channels (web, mobile, SMS, phone). With 5-minute escalation workflows, acknowledgment tracking, Genesys Cloud integration, and audit compliance, the system drives operational excellence across 200+ stores while respecting customer privacy and user preferences.
Key Features: - ✅ 15+ alert types covering all dispatch scenarios - ✅ Multi-channel delivery (web, mobile push, SMS, phone calls) - ✅ Global standardized rules (no per-store customization) - ✅ 5-minute escalation thresholds for time-critical alerts - ✅ Genesys Cloud integration for SMS/phone compliance - ✅ Customer-limited notifications (assignment, ETA, completion only) - ✅ Comprehensive audit trails for compliance and analysis - ✅ Response time tracking for SLA monitoring
Related Documents¶
- ROLES.md - Role-based alert recipients
- SECURITY.md - Data security and compliance
- MONITORING.md - System health monitoring
- Solution Intent & Design - Project overview