Files
rootnode-academy/docs/REGISTRATION_AND_GUARDIAN_COMMUNICATIONS.md
T
ced4568andClaude Sonnet 5 788fb197e0 docs(phase8): document enrollment operations
New: PHASE8_ENROLLMENT_OPERATIONS.md (phase summary), WINTER_BREAK_CAMP_OPERATIONS.md
(the real operational walkthrough), REGISTRATION_AND_GUARDIAN_COMMUNICATIONS.md,
OFFERING_ADMIN_GUIDE.md.

Updated: ROOTNODE_ACADEMY_BUSINESS_AND_PRODUCT_ARCHITECTURE.md (BUILT NOW/PHASE 8 BUILT
table), OFFERING_AND_REGISTRATION_MODEL.md (both ordering bugs, launch readiness,
registration enforcement), STUDENT_GUARDIAN_DATA_MODEL.md (guardian communications now
wired), WEBSITE_ENROLLMENT_INTEGRATION.md (the real, live registration contract with
worked examples, replacing the old speculative future-flow section), platform/README.md
(test list).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-26 23:59:42 -05:00

3.9 KiB

Registration & Guardian Communications

Phase 8 §8-§11 — how registration-stage notifications extend the existing Phase 5C notification system. No second email subsystem was built: same notification_events table, same durable worker (notification-worker.js), same Google Workspace-backed provider adapter (email/provider.js), same masked-recipient admin API.

What's new vs. Phase 5C

notification_events.student_id is now nullable, and a registration_id column was added (a CHECK constraint guarantees every row still has a real subject) — because a registration-stage notification (Registration Received, Agreement Required) may need to go out before any student account exists at all, for a brand-new guardian-submitted registration. notification_type's CHECK constraint was widened to include five new types. See 033_phase8_...sql and 036_phase8_notification_types.sql.

A real bug found and fixed during this phase's own testing: the new notification types were added to notifications.js/notification-worker.js before the database CHECK constraint was widened to accept them — every registration-stage enqueue call was silently failing (caught by the deliberate "a notification failure never blocks the real action" try/catch) until this was caught and fixed. See docs/PHASE8_ENROLLMENT_OPERATIONS.md.

The five new templates

  1. Registration Received — queued the moment a registration is created (website or admin).
  2. Registration Confirmed — queued when a registration moves to confirmed.
  3. Registration Needs Action — available for an admin/future-flow to queue with a reason when a registration needs something before it can proceed (not automatically triggered by any single lifecycle transition this phase — a deliberate choice, since "needs action" covers many different real reasons an admin should state explicitly).
  4. Agreement Required — queued once per missing required agreement when a registration enters agreements_pending.
  5. Camp/Offering Operational Update — a deliberately generic operational notice (e.g. "camp starts Monday, here's what to bring"), NOT a marketing template — no promotional content, no discount codes, no unrelated announcements. Available for an admin to trigger manually; nothing queues it automatically.

All five are HTML + plain-text, RootNode-branded, no tracking pixels, no external images, no passwords or sensitive data — same templates.js conventions Phase 5C established.

Recipient resolution — extended, never overwritten

resolveRecipient()'s existing order (student's own email → the enrollment's metadata.contact_email) now has a third, final fallback: a real, communications-enabled primary guardian's email (student_guardians/guardians). This is purely additive — a student who already resolves correctly today is completely unaffected, since the new fallback is only ever reached when the first two return nothing. enrollmentContactEmail (Phase 5C, unchanged) and this new guardian fallback deliberately coexist: contact_email remains a per-enrollment override for the live website integration's existing contract, while the guardian relationship is the durable, cross-enrollment identity — prefer the guardian model going forward, but the transitional field is never broken.

For registration-stage notifications (no student yet, possibly), a separate resolveRegistrationRecipient() tries: the registration's own student (if already converted) → its linked guardian → the registrant's own supplied email.

What was deliberately NOT built

No bulk/marketing email composer, no free-form admin-authored message sent to an arbitrary recipient — every notification is one of the fixed templates above, triggered by a real lifecycle event or an explicit, scoped admin action (never a raw "compose email" form). No SMS. No third notification channel beyond email.