Appearance
Prime message notes
Every email and SMS sent during triage and booking is recorded on the Prime job as a note (UserStory1847/1854 for customers, UserStory1949 for staff). The notes go through a queue, so a slow or failing Prime API never holds up the triage and booking tasks, and a note Prime rejects is kept as a row rather than lost to a log file.
Flow
send site (WebAPI service or SQL proc)
├─ queues the email (ethEmailMessage) or SMS (makesafeSMSMessage)
└─ queues the note (ethPrimeNotificationQueue) ← same step, no Prime call
│
n8n "Prime Notification Queue - Process" (every 5 min, task lock n8nPrimeNotificationQueue)
└─ POST api/prime/Notifications/ProcessQueue
└─ PrimeNotificationService.ProcessNotificationQueue
claim ≤50 due rows → POST each to Prime notifications → MarkSent / MarkFailedWhat is noted
| Message | Recipient | Note type | Where |
|---|---|---|---|
| Triage invite email + SMS | Customer | Customer Contact | TriageInviteService |
| Field booking On Create / Book / Cancel SMS + email | Customer | Customer Contact | BookingService |
| VA booking On Create / Book / Cancel SMS + email | Customer | Customer Contact | VABookingService |
| Triage submitted email | Case manager | Internal | TriageFinalizeQueueService.AlertCaseManagerAsync |
| Restoration make safe email | Make Safe team | Internal | TriageFinalizeQueueService.AlertMakeSafeTeamAsync |
| Incorrect property details email | Case manager | Internal | TriageSiteRejectedQueueService |
| Make safe outcome report email (job-linked calls only) | Make Safe inbox | Internal | MakeSafeOutcomeQueueService |
| VA booked email + SMS | Estimator | Internal | VABookingService.SendEstimatorEmailOnBook |
| New make safe call page SMS (one note listing every mobile paged) | On-call Make Safe team | Internal | spMakesafePhoneCallQueuePageNote |
Customer WIP update email (queued on approval as a WIPInsertPrimeHistoryNote task) | Customer | Customer Contact | WIPUpdateService.ProcessWIPUpdatePrimeHistoryNoteQueue |
| Client WIP stuck-job escalation email (one note listing every recipient, only when the email is queued) | Case manager / estimator / supervisor / restoration / state manager | Internal | ClientWIPQueueService.RunStuckCheckAsync |
Client WIP updates (ethClientWIPUpdate) are not noted yet - their delivery is not built (see ClientWIPUpdates-Spec §11). When it is, it should queue through here.
Make safe pages and calls with no job. Calls from the triage form and from Prime jobs have a job when they are paged. Calls from the AI voice line, JotForm and the ENData and Sedgwick emails often don't. spMakesafePhoneCallQueuePageNote is called both after paging (spMakesafeQueuePhoneCallSMSMessages) and when staff link a job (spMakesafeUpdatePhoneCallPrimeJobID). It does nothing while there is no job, and does nothing if the page is already noted on that job. A note queued at link time says when the SMS actually went out. A second page of the same call is not noted.
The make safe outcome email to the make safe inbox is noted only if the call has a job when the outcome is submitted.
Not noted: messages with no single job (route dispatch emails, orders-not-scheduled, VA ops alerts).
Note format
SMS to {mobile}
{body}
Email from {from} to {to}
Subject: {subject}
{HTML body flattened by PrimeNoteHtmlConverter.ToPlainText}The text is flattened when the note is queued, so ethPrimeNotificationQueue.Note is exactly what Prime receives.
- Customer Contact notes go to Prime with
syncToEndata = falseandsyncToNexus = false(UserStory1854). A customer message a test override redirected goes in as Internal, marked "Redirected by a test override". - Internal notes carry no sync flags.
- Client Contact notes are client WIP updates (AB#1954, ClientWIPUpdates-Spec §7.6). The queue row's
SyncToClientId(migration 0049) is passed toSetInstructingClientSync, which setssyncToEndata = truefor Allianz andsyncToNexus = truefor A&G. - When Prime's Endata/Nexus sync fails it still creates the note but answers 2xx with an HTML body.
CreateNotificationtreats that as sent and logs a warning, because a retry would post the note again. The note is on the job but not synced, which is for Prime to fix. Migration 0050 marks the rows that failed this way before the change as sent. - The same sync failure can also arrive as an error status (
PrimeApiException) whose body is not Prime's own JSON error. A note can't be edited or deleted, so before retrying,CreateNotificationlooks for the note on the job in Prime (same type and text, created in the last 10 minutes). Found = treated as sent with a warning; not found, or the lookup fails = the error stands and the row retries. Timeouts and Prime's own JSON errors retry as before (AB#2037). - Whenever a note is treated as sent this way (either case above), the queue row keeps
SyncWarning(the reason) andSyncResponse(the full raw reply from Prime, for a Prime support ticket) - migration 0056, written byspEthPrimeNotificationQueueMarkSent. A normal send leaves both NULL.
Queue table ethPrimeNotificationQueue (migration 0047)
JobId, NotificationType, Note, Source (the calling method or proc), CreatedAt, plus the retry state:
spEthPrimeNotificationQueueClaimtakes the due rows oldest first and pushes theirNextAttemptAtout 15 minutes as a lease, so an overlapping run can't post a row twice and a run that dies leaves its rows to come back on their own.spEthPrimeNotificationQueueMarkSentstampsSentAt.spEthPrimeNotificationQueueMarkFailedrecordsFailureMessageand backs off 3/6/12/24/48/96 minutes, then 96 each time after. After 10 attempts (roughly 9.5 hours) it setsAbandonedAt.
The processor checks the Prime rate limit (Limit, 20% remaining) before it starts and between notes. It throws if the budget is already too low, and stops early if the budget runs low part-way through. It used Limit3 (50%) until AB#1983, which held every note queued after mid-afternoon until the next day's reset.
Finding missing notes: a row with AbandonedAt set is a message that went out but is not on the Prime job. Nothing alerts on this yet. Query the table:
SentAt IS NULL AND (AbandonedAt IS NOT NULL OR CreatedAt < DATEADD(HOUR, -1, GETDATE()))Queuing a note never throws to the sender. A failure to insert the row is logged and swallowed, because the message is already queued and the caller's retry would send it again.

