Skip to content

feat(notifications): appointment booking notifications and reminders - #187

Open
Micheal1605 wants to merge 12 commits into
DocForum:mainfrom
Micheal1605:feat/issue-98-phase-6-appointment-notifications-booking
Open

Micheal1605 wants to merge 12 commits into
DocForum:mainfrom
Micheal1605:feat/issue-98-phase-6-appointment-notifications-booking

Conversation

@Micheal1605

Copy link
Copy Markdown

Overview

This PR wires appointment booking events into the notification pipeline. When a booking commits, booking-confirmed (patient) and new-booking (doctor) notifications are emitted through the outbox, and a reminder job sends 24h and 2h reminders that are idempotent per appointment and reminder type. Cancellation and reschedule notifications are sent to the other party, and reminder sends check appointment state at send time so cancelled or rescheduled appointments are suppressed.

Related Issue

Changes

🔔 Notification services and repositories

  • [MODIFY] backend/src/modules/notifications/services/index.ts

    • Added appointment notification triggers (booking-confirmed, new-booking, cancellation, reschedule) emitted via the outbox on booking commit.
    • Added reminder dispatch logic for 24h and 2h reminders, idempotent per appointment + reminder type.
    • Reminder send path re-checks appointment state so cancelled/rescheduled appointments are skipped.
    • Reminder copy uses the recipient's time zone when known.
    • Notification preference lookup is a no-op default until user preferences exist.
  • [MODIFY] backend/src/modules/notifications/repositories/index.ts

    • Added repository support for tracking sent reminders per appointment + reminder type to enforce idempotency.

📅 Appointment services

  • [MODIFY] backend/src/modules/appointments/services/index.ts
    • Emits booking-confirmed and new-booking notifications when a booking commits.
    • Emits cancellation and reschedule notifications to the other party.

📚 Docs

  • [MODIFY] docs/api/README.md
    • Documented the appointment notification behavior and reminder semantics.
  • [MODIFY] ROADMAP.md
    • Updated Phase 6 item status and changelog line for appointment notifications.

Verification Results

npm run typecheck
✅ passed

npm test
✅ passed

npm run test:integration
✅ passed (booking notification + reminder idempotency/suppression coverage)
Acceptance Criteria Status
Booking-confirmed and new-booking notifications emitted via outbox on commit ✅
24h and 2h reminders sent, idempotent per appointment and reminder type ✅
Reminders suppressed for cancelled or rescheduled appointments (state checked at send time) ✅
Cancellation and reschedule notifications sent to the other party ✅
Reminder copy respects recipient time zone when known ✅
Notification preferences respected with no-op default ✅
FR-15/16 partially marked in PRD; ROADMAP and API docs updated ✅

Closes #98

@drips-wave

drips-wave Bot commented Oct 20, 2026

Copy link
Copy Markdown

@Micheal1605 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@github-actions github-actions Bot added the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 20, 2026
@github-actions

Copy link
Copy Markdown

@Micheal1605 This PR now has merge conflicts with main, most likely because #194 (feat(infra): automated Postgres backups with a tested restore drill) was just merged, so it can't be merged until it's rebased:

git fetch upstream            # upstream = https://github.com/DocForum/docforum-core.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-lease

The usual culprits are shared files like ROADMAP.md and ARCHITECTURE*.md. CI re-runs when you push, and the needs-rebase label is removed automatically once the conflicts are resolved.

Automated message. It won't repeat unless the PR conflicts again after your next push.

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Micheal1605 CI is failing on this branch, so it can't be merged yet. The errors from the latest run:

Backend (typecheck + test + build)

src/modules/appointments/services/index.ts(188,30): error TS2339: Property 'emitBookingConfirmed' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(189,30): error TS2339: Property 'emitNewBookingAlert' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(251,34): error TS2339: Property 'emitAppointmentCancelled' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(278,32): error TS2339: Property 'emitAppointmentCancelled' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(316,28): error TS2551: Property 'updateAvailabilitySlotLink' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/appointments/repositories/index")'. Did you mean 'c
src/modules/appointments/services/index.ts(321,35): error TS2739: Type '{ userId: string; }' is missing the following properties from type 'AuditActor': role, requestId
src/modules/appointments/services/index.ts(331,32): error TS2339: Property 'emitAppointmentRescheduled' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(72,32): error TS2339: Property 'emitBookingConfirmed' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.

Please reproduce this locally, fix the errors above and push. CI will re-run on your next push.

This PR also has merge conflicts with main, so please rebase onto the latest main while you're at it.

@github-actions

Copy link
Copy Markdown

@Micheal1605 This PR now has merge conflicts with main, most likely because #140 (feat(auth): password reset flow with single-use, hashed, expiring tokens) was just merged, so it can't be merged until it's rebased:

git fetch upstream            # upstream = https://github.com/DocForum/docforum-core.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-lease

The usual culprits are shared files like ROADMAP.md and ARCHITECTURE*.md. CI re-runs when you push, and the needs-rebase label is removed automatically once the conflicts are resolved.

Automated message. It won't repeat unless the PR conflicts again after your next push.

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Micheal1605 CI is failing on this branch against the latest main, so it can't be merged yet. The errors from the latest run:

Backend (typecheck + test + build)

src/modules/appointments/services/index.ts(72,32): error TS2339: Property 'emitBookingConfirmed' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(73,32): error TS2339: Property 'emitNewBookingAlert' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(188,30): error TS2339: Property 'emitBookingConfirmed' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(189,30): error TS2339: Property 'emitNewBookingAlert' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(251,34): error TS2339: Property 'emitAppointmentCancelled' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(278,32): error TS2339: Property 'emitAppointmentCancelled' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(316,28): error TS2551: Property 'updateAvailabilitySlotLink' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/appointments/repositories/index")'. Did you mean 'c
src/modules/appointments/services/index.ts(321,35): error TS2739: Type '{ userId: string; }' is missing the following properties from type 'AuditActor': role, requestId

Reproduce this locally on the latest main, fix the errors above, and push.

This PR also conflicts with main again, so please rebase first, then fix the above.

@github-actions github-actions Bot added needs-rebase Has merge conflicts with main; the author needs to rebase and removed needs-rebase Has merge conflicts with main; the author needs to rebase labels Oct 21, 2026
@github-actions

Copy link
Copy Markdown

@Micheal1605 This PR now has merge conflicts with main, most likely because #156 (feat(patients): account deletion with clinical-record retention: anonymise identity, keep the audit trail) was just merged, so it can't be merged until it's rebased:

git fetch upstream            # upstream = https://github.com/DocForum/docforum-core.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-lease

The usual culprits are shared docs like ROADMAP.md and ARCHITECTURE*.md. If the conflict is in the ROADMAP.md changelog, move your entry into a new file under changelog.d/ (see changelog.d/README.md) rather than resolving it in place, so it can't conflict again. CI re-runs when you push, and the needs-rebase label is removed automatically once the conflicts are resolved.

Automated message. It won't repeat unless the PR conflicts again after your next push.

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Micheal1605 CI is failing on this branch against the latest main, so it can't be merged yet. The errors from the latest run:

Backend (typecheck + test + build)

src/modules/appointments/services/index.ts(73,32): error TS2339: Property 'emitBookingConfirmed' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(74,32): error TS2339: Property 'emitNewBookingAlert' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(215,30): error TS2339: Property 'emitBookingConfirmed' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(216,30): error TS2339: Property 'emitNewBookingAlert' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(278,34): error TS2339: Property 'emitAppointmentCancelled' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(305,32): error TS2339: Property 'emitAppointmentCancelled' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(343,28): error TS2551: Property 'updateAvailabilitySlotLink' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/appointments/repositories/index")'. Did you mean 'c
src/modules/appointments/services/index.ts(348,35): error TS2739: Type '{ userId: string; }' is missing the following properties from type 'AuditActor': role, requestId

Reproduce this locally on the latest main, fix the errors above, and push.

This PR also conflicts with main again, so please rebase first, then fix the above.

@github-actions github-actions Bot removed the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 22, 2026
@github-actions github-actions Bot added the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 22, 2026
@github-actions

Copy link
Copy Markdown

@Micheal1605 This PR now has merge conflicts with main, most likely because #190 (test(integration): end-to-end care-thread integration test scaffold) was just merged, so it can't be merged until it's rebased:

git fetch upstream            # upstream = https://github.com/DocForum/docforum-core.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-lease

The usual culprits are shared docs like ROADMAP.md and ARCHITECTURE*.md. If the conflict is in the ROADMAP.md changelog, move your entry into a new file under changelog.d/ (see changelog.d/README.md) rather than resolving it in place, so it can't conflict again. CI re-runs when you push, and the needs-rebase label is removed automatically once the conflicts are resolved.

Automated message. It won't repeat unless the PR conflicts again after your next push.

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Micheal1605 CI is failing on this branch against the latest main, so it can't be merged yet. The errors from the latest run:

Backend (typecheck + test + build)

src/modules/appointments/services/index.ts(85,32): error TS2339: Property 'emitBookingConfirmed' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(86,32): error TS2339: Property 'emitNewBookingAlert' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(227,30): error TS2339: Property 'emitBookingConfirmed' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(228,30): error TS2339: Property 'emitNewBookingAlert' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(290,34): error TS2339: Property 'emitAppointmentCancelled' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(317,32): error TS2339: Property 'emitAppointmentCancelled' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(355,28): error TS2551: Property 'updateAvailabilitySlotLink' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/appointments/repositories/index")'. Did you mean 'c
src/modules/appointments/services/index.ts(360,35): error TS2739: Type '{ userId: string; }' is missing the following properties from type 'AuditActor': role, requestId

notifications doesn't export emitBookingConfirmed, emitNewBookingAlert or emitAppointmentCancelled. These names were probably lost while resolving the rebase. Restore their definitions or exports from your earlier commits.

This PR also conflicts with main again, so please rebase first, then fix the above.

@github-actions github-actions Bot removed the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 23, 2026
@github-actions github-actions Bot added the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 24, 2026
@github-actions

Copy link
Copy Markdown

@Micheal1605 This PR now has merge conflicts with main, most likely because #233 (fix: document held-slot TTL finding and add release job for availability holds) was just merged, so it can't be merged until it's rebased:

git fetch upstream            # upstream = https://github.com/DocForum/docforum-core.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-lease

The usual culprits are shared docs like ROADMAP.md and ARCHITECTURE*.md. If the conflict is in the ROADMAP.md changelog, move your entry into a new file under changelog.d/ (see changelog.d/README.md) rather than resolving it in place, so it can't conflict again. CI re-runs when you push, and the needs-rebase label is removed automatically once the conflicts are resolved.

Automated message. It won't repeat unless the PR conflicts again after your next push.

@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Micheal1605 CI is failing on this branch against the latest main, so it can't be merged yet. The errors from the latest run:

Backend (typecheck + test + build)

src/modules/appointments/services/index.ts(85,32): error TS2339: Property 'emitBookingConfirmed' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(86,32): error TS2339: Property 'emitNewBookingAlert' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(227,30): error TS2339: Property 'emitBookingConfirmed' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(228,30): error TS2339: Property 'emitNewBookingAlert' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(290,34): error TS2339: Property 'emitAppointmentCancelled' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(317,32): error TS2339: Property 'emitAppointmentCancelled' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/appointments/services/index.ts(355,28): error TS2551: Property 'updateAvailabilitySlotLink' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/appointments/repositories/index")'. Did you mean 'c
src/modules/appointments/services/index.ts(360,35): error TS2739: Type '{ userId: string; }' is missing the following properties from type 'AuditActor': role, requestId
src/modules/appointments/services/index.ts(370,32): error TS2339: Property 'emitAppointmentRescheduled' does not exist on type 'typeof import("/home/runner/work/docforum-core/docforum-core/backend/src/modules/notifications/services/index")'.
src/modules/notifications/channels/sendgrid.ts(12,14): error TS2322: Type '{ send(_message: any, _channel?: string): Promise<void>; }' is not assignable to type '"email"'.

notifications/services doesn't export emitBookingConfirmed, emitNewBookingAlert, emitAppointmentCancelled or emitAppointmentRescheduled, and updateAvailabilitySlotLink is missing too. AuditActor now needs role and requestId. sendgrid.ts:12 assigns a channel object where the type expects the string 'email'.

@Micheal1605

Copy link
Copy Markdown
Author

@afeezorobsco-cyber — this PR is ready for another review. I resolved the merge conflicts with the main branch (pushed bc6c537).
Files changed: ROADMAP.md, docs/api/README.md

@github-actions github-actions Bot removed the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 25, 2026
@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Micheal1605 CI is failing at npm run typecheck:

src/modules/appointments/services/index.ts(85,32): error TS2339: Property 'emitBookingConfirmed' does not exist on type '...notifications/services/index'.
src/modules/appointments/services/index.ts(86,32): error TS2339: Property 'emitNewBookingAlert' does not exist on type '...notifications/services/index'.
src/modules/appointments/services/index.ts(290,34): error TS2339: Property 'emitAppointmentCancelled' does not exist on type '...notifications/services/index'.
src/modules/appointments/services/index.ts(355,28): error TS2551: Property 'updateAvailabilitySlotLink' does not exist on type '...appointments/repositories/index'.
src/modules/appointments/services/index.ts(360,35): error TS2739: Type '{ userId: string; }' is missing the following properties from type 'AuditActor': role, requestId

The appointments service calls notification emitters that the PR doesn't export, and it uses an older AuditActor shape than main. Please add the emitters, pass role/requestId, run npm run typecheck in backend/, and push.

@github-actions github-actions Bot added the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 27, 2026
@github-actions

Copy link
Copy Markdown

@Micheal1605 This PR now has merge conflicts with main, most likely because #164 (Implement cancellation policy (issue #56), waitlist with time-boxed offers (issue #55), and reschedule (issue #54)) was just merged, so it can't be merged until it's rebased:

git fetch upstream            # upstream = https://github.com/DocForum/docforum-core.git
git rebase upstream/main
# resolve the conflicts, re-run the tests, then:
git push --force-with-lease

The usual culprits are shared docs like ROADMAP.md and ARCHITECTURE*.md. If the conflict is in the ROADMAP.md changelog, move your entry into a new file under changelog.d/ (see changelog.d/README.md) rather than resolving it in place, so it can't conflict again. CI re-runs when you push, and the needs-rebase label is removed automatically once the conflicts are resolved.

Automated message. It won't repeat unless the PR conflicts again after your next push.

…ons-booking

Resolve docs/api/README.md: keep main's API reference and add this PR's
reschedule row and Notifications section. The branch's copy of the file had a
stray NUL byte (so git treated it as binary) and an older, partly garbled
snapshot that would have dropped most of main's reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Micheal1605 I resolved this PR's merge conflict with main (commit d7b8f45). Your copy of docs/api/README.md had a stray NUL byte, so git treated it as binary, and it was an older snapshot that would have dropped most of main's API reference. I kept main's file and added your reschedule row and Notifications section.

CI still fails on the same typecheck errors as before. The appointments service calls notification emitters that the notifications service doesn't export:

src/modules/appointments/services/index.ts(85,32): error TS2339: Property 'emitBookingConfirmed' does not exist on type 'typeof import(".../notifications/services/index")'.
src/modules/appointments/services/index.ts(86,32): error TS2339: Property 'emitNewBookingAlert' does not exist ...
src/modules/appointments/services/index.ts(629,34): error TS2339: Property 'emitAppointmentCancelled' does not exist ...

Please pull the merge commit first (git pull), then add or export those functions in backend/src/modules/notifications/services/index.ts, run npm run typecheck in backend/, and push.

@github-actions github-actions Bot removed the needs-rebase Has merge conflicts with main; the author needs to rebase label Oct 27, 2026
Micheal1605 added a commit to Micheal1605/docforum-core that referenced this pull request Oct 28, 2026
@Micheal1605
Micheal1605 force-pushed the feat/issue-98-phase-6-appointment-notifications-booking branch from a0b4c0b to 46984de Compare October 28, 2026 14:42
@afeezorobsco-cyber

Copy link
Copy Markdown
Contributor

@Micheal1605 Thanks for the notification fixes. After updating to main there are new typecheck errors:

src/export-sections.ts(12,10): error TS2305: Module '"./modules/appointments/services"' has no exported member 'appointmentsExportSection'.
src/modules/appointments/controllers/index.ts(65,44): error TS2339: Property 'leaveWaitlist' does not exist on type '...appointments/services/index'.
src/modules/appointments/controllers/index.ts(72,45): error TS2339: Property 'listWaitlistEntries' does not exist ...
src/modules/appointments/controllers/index.ts(79,49): error TS2339: Property 'acceptWaitlistOffer' does not exist ...

main's appointments service exports appointmentsExportSection and the waitlist functions, but this branch's appointments/services/index.ts overwrote them. Please merge main's version of that file and re-apply only your notification calls, run npm run typecheck in backend/, and push.

@Micheal1605

Copy link
Copy Markdown
Author

@afeezorobsco-cyber — this PR is ready for another review. I fixed the failing CI checks on this PR (pushed 42d79a0).
Files changed: ROADMAP.md, backend/src/modules/appointments/services/index.ts, backend/src/modules/notifications/services/index.ts, backend/src/modules/notifications/repositories/index.ts

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Phase 6] Appointment notifications: booking confirmations, new-booking alerts, and reminders

2 participants