Work reminders
How the work notes and reminders run, and how to check them. Rules: WRK-009 (the reminders), WRK-018 (a job given, a job finished) and COMM-041 (the phone push). What people see: Work inbox → Who is told, and when.
How it runs
- A job is given, handed on, finished or cancelled. Triggers on the work ledger
(
WorkItemEvent_notify_new_holder,WorkItemEvent_notify_giver) callwork_tell(). It writes oneAppNotificationper person (kindwork, marked"pushWanted") and queues the e-mail copy inCommunicationQueue(categorywork, message typework_notice). The web or the app that made the change then asks thecommunications-dispatcherto send now (its kick). - Every 15 minutes the pg_cron job
alhuda-work-reminders(*/15 * * * *) runsSELECT public.work_reminders_run(). It needs no secret: it only writes rows. For each open or in-progress work item due within the last 30 days it works out the working time left before the due time, or past it (less waiting), and fires the rungs that are due — due soon, due, 1.5×, 2×, +24 working hours — once per item, rung and due time (WorkReminder), throughwork_tell(). A rung crossed more than a working hour ago is marked done without a note (told = false): after an outage the first run does not flood anyone. - Every five minutes the existing job
alhuda-communications-dispatcherruns thecommunications-dispatcher. It puts back e-mail rows a stopped run left at processing for ten minutes (communication_queue_reap), sends the queued e-mails through themailer, and then rings the marked notes of the last day throughpush-send(store:false), stamping"pushedAt"first — 8 seconds per push, 20 seconds in all per run. A push that failed is written in"pushError"and not tried again.
Checking
The job, in the SQL editor:
SELECT jobname, schedule, active FROM cron.job WHERE jobname = 'alhuda-work-reminders';
SELECT status, return_message, start_time FROM cron.job_run_details
WHERE jobid = (SELECT jobid FROM cron.job WHERE jobname = 'alhuda-work-reminders')
ORDER BY start_time DESC LIMIT 5;
What fired lately, and to whom (told = false: passed long ago, marked done without a note):
SELECT r."firedAt", r.rung, r.told, w.title, r."userIds", r."overdueMinutes"
FROM "WorkReminder" r JOIN "WorkItem" w ON w.id = r."itemId"
ORDER BY r."firedAt" DESC LIMIT 20;
E-mail rows put back after a stopped run:
SELECT "createdAt", "messageType", attempts, status, "lastError" FROM "CommunicationQueue"
WHERE "lastError" LIKE 'The dispatcher run that took this message stopped%' ORDER BY "createdAt" DESC LIMIT 20;
Notes waiting to ring, and pushes that failed:
SELECT count(*) FROM "AppNotification" WHERE "pushWanted" AND "pushedAt" IS NULL;
SELECT "createdAt", "userId", title, "pushError" FROM "AppNotification"
WHERE "pushError" IS NOT NULL ORDER BY "createdAt" DESC LIMIT 20;
A note marked for the phone with "pushedAt" set and no "pushError" was handed to
push-send. If the person has no registered device, nothing rang, and that is not an error:
the note is still in their notifications. The e-mail copies are in /communications →
scheduled queue (category work).
By hand
A run now (the service role, or the SQL editor):
It answers { now, checked, fired: { due_soon, due, lead, manager, leadership }, notes }. A
second run at once fires nothing more. To see what a given moment would do, run it inside a
transaction and roll back:
Stopping
- The e-mail copies: Admin → Reminders → Automatic e-mails by category → Work off. The notes and the phone push still go.
- The reminders themselves (only if they misbehave):
SELECT cron.unschedule('alhuda-work-reminders');. Re-applying the migration schedules it again. A job given or finished still tells people.
Deploy
The migration 20261008150000_work_reminders.sql adds the columns, the table, the functions, the
triggers and the job. Where pg_cron is missing it logs a notice and succeeds; run
work_reminders_run() by hand then. Deploy with it:
communications-dispatcher— it now rings the marked notes (keptPush.ts);push-send— it now ignores an old app build's own push for a work note, so a hand-over rings once;mailer— thework_noticetemplate.
Until the dispatcher is deployed, work notes still appear in the notifications and the e-mails still go, but no phone rings. The dispatcher's schedule needs its two Vault secrets (Deploy runbook → The dispatcher's schedule).