Notifications & Edge Cases

Part of: Pickatale Master Build Wiki | Version: v1.7 | Last Updated: 2026-04-18


22. Notification Triggers

Source: (D) | Status: Confirmed as spec Delivery: SendGrid (email) for adults. In-app (teacher portal, parent portal) for notifications. No email to children.

ID Trigger Channel Recipient Template Variables
VERIFY_EMAIL User registers Email Registrant name, verify_url, expires_in
WELCOME_TEACHER Email verified (teacher) Email Teacher name, portal_url
WELCOME_PARENT Email verified (parent) Email Parent name, portal_url
INVITE_EMAIL Teacher invites colleague Email Invitee inviter_name, school_name, invite_url, expires_in
PASSWORD_RESET User requests reset Email User name, reset_url, expires_in
PASSWORD_CHANGED Password reset completed Email User name, timestamp
ACCOUNT_LOCKED_ALERT 5 failed login attempts Email User name, unlock_time
SCHOOL_SUBSCRIPTION_EXPIRED School subscription ends Email School admin + all teachers school_name, expired_date, renewal_url, data_export_url
PARENT_CLAIM_REQUEST Parent claims teacher's student In-app (Teacher Portal) Teacher parent_name, child_name, approve_url
PARENT_CLAIM_APPROVED Teacher approves parent claim Email Parent child_name, portal_url
TEACHER_ALERT_LOW_SCORE Bot detects avg quiz <65% on 2+ quizzes In-app (Teacher Portal) Class teacher child_name, avg_score, report_url
TEACHER_ALERT_INACTIVE Child inactive 4+ days In-app (Teacher Portal) Class teacher child_name, last_active, report_url
TEACHER_ALERT_NO_PLACEMENT Child not completed placement test In-app (Teacher Portal) Class teacher child_name
WEEKLY_PARENT_DIGEST Every Sunday 08:00 UTC Email Parent child_name, week_summary, highlights, home_tip, portal_url
NIGHTLY_PARENT_DIGEST After Learner Bot nightly cycle (04:00 UTC) Email (optional β€” user setting) Parent child_name, session_summary, progress
PAYMENT_RECEIPT Successful payment Email Subscription owner amount, plan, next_renewal, invoice_url
RENEWAL_FAILED Stripe webhook: payment_failed Email Subscription owner amount, retry_date, update_payment_url
SUBSCRIPTION_EXPIRING 7 days before expiry Email Subscription owner expiry_date, renew_url
DATA_EXPORT_READY GDPR export completed Email Requesting user download_url, expires_in
ACCOUNT_SUSPENDED Admin suspends account Email Suspended user name, reason, contact_url
CHILD_LOCKED_PIN 5 failed PIN attempts In-app (Teacher Portal) Class teacher child_name, class_name


23. Edge Cases and Failure Handling

Source: (D) | Status: Confirmed as spec

23.1 Child Forgot PIN

23.2 Teacher Forgot Password

23.3 Parent Wants to Claim Existing Teacher-Created Child

23.4 Subscription Expires Mid-Term

  1. subscriptions.state β†’ past_due on payment failure
  2. Grace period: 7 days full access
  3. grace_ends_at triggers: state β†’ expired, children enter free tier
  4. Teacher sees "Subscription expired" banner β€” upgrade path shown
  5. All bot cycles stop for expired learners
  6. Reading sessions still work (free-tier book limits apply)
  7. Reports frozen β€” last generated report cached

23.5 Class Deleted with Active Children

23.6 Duplicate Email on Registration

23.7 Duplicate Child Profile (Same Name Imported Twice)

23.8 Invited User Never Verifies

23.9 School Stops Paying

  1. School subscription β†’ past_due β†’ 7-day grace β†’ expired
  2. All school teachers: Teacher Portal access reduced to free tier
  3. All school students: reading limited to free 50 books
  4. Bot cycles stop
  5. 30-day notice: send SCHOOL_SUBSCRIPTION_EXPIRED email with data export link
  6. After 30 days no renewal: accounts marked for archiving, data export window offered

23.10 Teacher Leaves School

  1. School admin: Reassign teacher's classes to another teacher
  2. UPDATE classes.teacher_id = new_teacher_id
  3. All students remain in class unchanged
  4. Departing teacher: account set to suspended or transferred to personal plan
  5. If no school admin exists: platform admin handles via admin tools

23.11 Child Changes Class

23.12 No Reading Data Yet (New Child, First Login)

23.13 Telemetry Fails but Reading Session Completes

23.14 Placement Test Skipped or Failed Midway

23.15 Assessment Bot Returns Ambiguous Level Recommendation



33. Notification Triggers β€” Full Contracts

Source: (D) | Status: Confirmed as spec. Delivery via SendGrid. MUST BUILD.

ID Trigger Channel Recipient Template Variables
VERIFY_EMAIL User registers Email Registrant name, verify_url, expires_in_hours
WELCOME_TEACHER Email verified (teacher) Email Teacher name, portal_url
WELCOME_PARENT Email verified (parent) Email Parent name, portal_url
INVITE_EMAIL Invite created Email Invitee inviter_name, school_name, invite_url, expires_in_days
PASSWORD_RESET Forgot password submitted Email User name, reset_url, expires_in_minutes
PASSWORD_CHANGED Password reset completed Email User name, changed_at
ACCOUNT_LOCKED_ALERT 5 failed login attempts Email User name, unlock_time, support_url
ACCOUNT_SUSPENDED Admin suspends account Email User name, reason, contact_url
SCHOOL_SUBSCRIPTION_EXPIRED School sub expires Email School admin + all teachers school_name, expired_date, renewal_url, data_export_url
SUBSCRIPTION_EXPIRED Individual sub expires Email User name, expired_date, renewal_url
RENEWAL_FAILED Stripe payment_failed webhook Email Subscription owner name, amount, retry_date, update_payment_url
SUBSCRIPTION_EXPIRING 7 days before expiry Email Subscription owner name, expiry_date, renew_url
PARENT_CLAIM_APPROVED Teacher approves claim Email Parent child_name, portal_url
PARENT_CLAIM_REJECTED Teacher rejects claim Email Parent child_name, reason
WEEKLY_PARENT_DIGEST Every Sunday 08:00 UTC Email Parent (full tier) child_name, week_summary, books_read, miles, highlight, home_tip, portal_url
CHILD_LOCKED_PIN 5 failed PIN attempts In-app (Teacher Portal) Class teacher child_name, class_name, reset_url
TEACHER_ALERT_LOW_SCORE Bot: avg quiz <65% on 2+ quizzes In-app (Teacher Portal) Class teacher child_name, avg_score, quiz_count, report_url
TEACHER_ALERT_INACTIVE No sessions in 4+ days In-app (Teacher Portal) Class teacher child_name, last_active_date
TEACHER_ALERT_NO_PLACEMENT Placement test not completed In-app (Teacher Portal) Class teacher child_name
PAYMENT_RECEIPT Stripe invoice.payment_succeeded Email Subscription owner name, amount, plan, next_renewal, invoice_url
DATA_EXPORT_READY GDPR export completed Email Requesting user download_url, expires_in_hours


34. Edge Cases β€” Full Contracts

Source: (D) | Status: Confirmed as spec

34.1 Parent Claims Existing Teacher-Created Child

Scenario: Teacher created "sofia001". Parent registers and wants to link Sofia.

Flow: Section 31.2 (parent claim). No data conflict β€” claim only adds visibility.

Edge case: Child already has 2 parents linked.

Edge case: Teacher rejects claim.


34.2 Duplicate Child Profile

Detection: Same name in same class during CSV import β†’ warning flag.

Resolution options:

  1. Import as new (teacher accepts duplicate β€” maybe twins).
  2. Skip duplicate row.
  3. Admin merge post-import (Flow 30.10).

Never auto-merge. Teacher or admin must explicitly confirm.


34.3 Duplicate Email on Registration

Check: users table by email. If exists:


34.4 School Subscription Ends Mid-Year

Scenario: School subscription state β†’ expired (payment lapsed).

Sequence:

  1. Stripe payment_failed β†’ past_due + 7-day grace.
  2. Grace period: send RENEWAL_FAILED to school admin. Full access maintained.
  3. Grace expires: expired. All teachers β†’ free tier. All students β†’ free tier (50 books).
  4. Learner Bot: stop nightly cycles for all school students.
  5. 30-day window: school admin can still log in, export data, renew.
  6. After 30 days no renewal: INSERT scheduled_archive (school_id, archive_at=now()+30d).
  7. On archive_at: all student accounts β†’ archived. Data retained per GDPR retention schedule.

Audit Log: Every state transition logged.


34.5 Teacher Leaves School

Scenario: Teacher account needs to be decoupled from school. Students must not be orphaned.

Admin action (school_admin or platform_admin):

  1. GET /api/admin/school/:school_id/teachers/:teacher_id/classes β€” list affected classes.
  2. PATCH /api/v1/classes/:class_id {teacher_id: new_teacher_id} β€” reassign each class.
  3. UPDATE users.school_id=null for departing teacher (or transfer to personal plan).
  4. Students remain in classes unchanged.

If no replacement teacher: Classes marked unassigned. Students remain in_class. School admin must re-assign.

Audit Log: Full chain logged.


34.6 Child Changes Class (Mid-Year)

Flow: Section 30.7 (move student).

Data continuity: All historical data follows learner_id. New teacher sees full history.

Learner Bot: On next nightly cycle, Orchestrator picks up new class context (year_level, curriculum_territory may change). Bot does NOT reset memory. Bot notes class change in reading_pattern memory.

Quiz continuity: In-progress assignments from old class: remain accessible, not auto-archived. New teacher can view.


34.7 No Telemetry But Reading Session Completes

Scenario: App offline or telemetry service down.

Client behaviour:

  1. Service Worker queues all events in IndexedDB.
  2. On reconnect: workbox-background-sync replays events (up to 72h).
  3. Session state: reading_sessions created at book_opened event. If session_ended received (even delayed) β†’ marked completed.

Bot behaviour: If session summary fires late (>1h after session ended) β†’ bot still processes. No error.

If events lost entirely (>72h offline): Session remains in state open indefinitely. Cron job (runs every 6h): mark sessions open with last_event_at < now()-24h as abandoned. Bot gets no data for this session.

Audit Log: INSERT audit_log (action='session_force_abandoned', metadata={session_id, reason: 'timeout'})


34.8 Reset Password for Child (Teacher-Issued Credentials)

Children have no email. Password reset means PIN reset.

Flow: Teacher Portal β†’ Student row β†’ Reset PIN (Flow 30.6).

If teacher also unavailable: School admin resets PIN.

If school admin also unavailable: Platform admin resets via admin tools (Section 24.3).

No self-service PIN reset for children β€” by design.


34.9 Subscription Expires with Active Reading History

What is preserved:

What is locked:

What is NOT deleted: Nothing is deleted on expiry. Data only deleted on GDPR request or account archival.

Teacher Portal: Shows "Subscription expired" banner on every page. Renew link displayed.


34.10 Invited User Never Verifies

Token expires after 7 days (for invites) or 48h (for self-registration).

Invite: Teacher sees invite in list with status pending. "Resend" button available.

Self-registration (teacher/parent): User in pending_verification state.


34.11 Entitlement Check Failure (Service Down)

Scenario: Account Center / billing endpoint unreachable when child tries to read.

Behaviour: checkEntitlement() catches timeout. Default fallback: {tier: 'free'}.

Rationale: Fail closed on entitlement β€” free tier access maintained. Never block reading entirely due to billing service outage.

Audit Log: INSERT audit_log (action='entitlement_check_failed', metadata={error, fallback: 'free'})