Skip to content

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

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:

  1. Work Assignment & ETA - When technician is assigned and ETA confirmed
  2. Work Completion - When work is approved and complete
  3. 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:

  1. Failover to SMS-only delivery (reliable, PSTN-based)
  2. Disable mobile push notifications (app-dependent)
  3. Activate phone call alerts for critical issues only
  4. Double-send critical alerts (SMS + phone call)
  5. Manual escalation for unacknowledged critical alerts
  6. 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


  • ROLES.md - Role-based alert recipients
  • SECURITY.md - Data security and compliance
  • MONITORING.md - System health monitoring
  • Solution Intent & Design - Project overview