11 KiB
Task F.4: Notification Framework Foundation
Status
Planned
Objective
Build a reusable Notification Framework for CRM events, starting with Approval events.
This task introduces a centralized notification foundation so Approval, Lead, Opportunity, Quotation, Follow-up, and future modules can publish events without each feature directly sending email or UI notifications.
The goal is to support:
- event-driven notification creation
- in-app notification inbox / bell foundation
- notification recipient resolution
- notification templates
- read/unread state
- approval event integration
- future channel support for email, LINE, webhook, and reminder/escalation
This task does not implement full email delivery, LINE integration, or escalation scheduling yet.
Mandatory Review
Review before implementation:
AGENTS.mddocs/standards/project-foundations.mddocs/standards/task-catalog.mddocs/implementation/task-f2-approval-matrix-foundation.mddocs/implementation/task-f3-approval-workflow-builder-foundation.md- existing Approval submit/approve/reject/cancel flow
src/db/schema.tssrc/features/crm/approval/**src/features/foundation/approval/**src/features/foundation/audit-log/**src/lib/auth/rbac.tsdocs/security/crm-authorization-boundaries.md
Current Foundation
Already available:
Approval Matrix
Approval Workflow Builder
Approval Requests
Approval Actions
Audit Log Foundation
CRM Authorization Foundation
Current limitation:
Approval actions do not yet publish reusable notifications.
Business Requirement
When approval activity happens, related users should be notified.
Examples:
Quotation submitted for approval
→ notify current approver(s)
Quotation approved
→ notify requester and next approver if any
Quotation rejected
→ notify requester
Quotation returned / cancelled
→ notify requester or related actors
The notification system should be generic enough for future events:
Lead Assigned
Opportunity Assigned
Follow-up Due
Quotation Expiring
Opportunity Won
Opportunity Lost
PO Received
Scope F.4.1 Notification Schema
Add tables:
app_notifications
app_notification_events
app_notification_templates
app_notification_deliveries
app_notification_events
Stores source events published by modules.
Recommended columns:
id
organization_id
event_type
entity_type
entity_id
actor_user_id
payload jsonb
dedupe_key
status
published_at
processed_at
created_at
Purpose:
Event source stream
app_notifications
Stores user-facing in-app notifications.
Recommended columns:
id
organization_id
recipient_user_id
event_id
title
body
link_url
severity
status
read_at
archived_at
metadata jsonb
created_at
updated_at
Purpose:
Notification inbox / bell
app_notification_templates
Stores reusable message templates.
Recommended columns:
id
organization_id
event_type
channel
title_template
body_template
link_template
is_active
metadata jsonb
created_at
updated_at
deleted_at
created_by
updated_by
Purpose:
Event message rendering
app_notification_deliveries
Tracks channel delivery attempts.
Recommended columns:
id
organization_id
notification_id
channel
recipient_address
status
attempt_count
last_error
sent_at
created_at
updated_at
Purpose:
Future email / LINE / webhook delivery tracking
Rules:
- In-app notification is implemented first
- Email/LINE delivery can remain queued or pending
- Use organization scope everywhere
- Use soft delete where appropriate
Scope F.4.2 Notification Event Service
Create:
src/features/foundation/notifications/server/event-service.ts
Functions:
publishNotificationEvent(input)
processNotificationEvent(eventId)
processPendingNotificationEvents()
Input example:
{
organizationId: string;
eventType: string;
entityType: string;
entityId: string;
actorUserId?: string | null;
payload?: Record<string, unknown>;
dedupeKey?: string;
}
Rules:
- publishing an event does not send email directly
- event creates one or more in-app notifications
- duplicate event with same
dedupeKeyshould be ignored or safely reused - processing should be idempotent
- failed processing should leave inspectable state
Scope F.4.3 Recipient Resolver
Create:
src/features/foundation/notifications/server/recipient-resolver.ts
Support recipient types:
explicit_user
approval_current_step_approvers
approval_requester
approval_previous_actor
entity_owner
sales_owner
For F.4 required recipient resolvers:
approval_current_step_approvers
approval_requester
explicit_user
Rules:
- return user IDs only
- never notify deleted/inactive users if such state exists
- avoid notifying the actor if event rules say actor should be excluded
- de-duplicate recipients
Scope F.4.4 Template Renderer
Create:
src/features/foundation/notifications/server/template-renderer.ts
Functions:
renderNotificationTemplate(template, payload)
Supported syntax can be simple:
{{quotationCode}}
{{workflowName}}
{{actorName}}
{{stepName}}
{{entityLink}}
Rules:
- missing variables render as
-or empty string - no arbitrary code execution
- no unsafe HTML injection
- plain text first
Scope F.4.5 Notification Inbox APIs
Create:
GET /api/notifications
GET /api/notifications/unread-count
POST /api/notifications/[id]/read
POST /api/notifications/read-all
POST /api/notifications/[id]/archive
Rules:
- users can only see their own notifications
- organization scope must apply
- unread count must be efficient
- support pagination
- default ordering: newest first
Scope F.4.6 Notification UI Foundation
Add or integrate notification bell.
Required UI:
Notification Bell
Unread Count Badge
Notification Dropdown / Sheet
Mark as Read
Mark All as Read
Open Link
Recommended placement:
top nav / header
Rules:
- in-app only in F.4
- no real-time requirement yet
- polling or manual refetch is acceptable
- empty state required
- error state required
Scope F.4.7 Approval Event Integration
Publish notification events from approval lifecycle.
Required events:
approval.requested
approval.step.approved
approval.step.rejected
approval.completed
approval.cancelled
approval.returned
Required recipients:
approval.requested
Notify:
current step approvers
approval.step.approved
Notify:
requester
next step approvers if any
approval.step.rejected
Notify:
requester
approval.completed
Notify:
requester
approval.cancelled
Notify:
requester
current approvers if needed
approval.returned
Notify:
requester
Rules:
- publish events from approval service layer, not route handlers
- do not block approval transaction on notification delivery failure if possible
- notification failure must be logged/inspectable
- approval action should still succeed if notification creation fails unless strict mode is explicitly required
Scope F.4.8 Default Notification Templates
Seed default templates for approval events.
Example:
approval.requested
Title: Approval Required: {{quotationCode}}
Body: {{actorName}} submitted {{quotationCode}} for approval.
Link: /dashboard/crm/quotations/{{quotationId}}
approval.step.rejected
Title: Approval Rejected: {{quotationCode}}
Body: {{actorName}} rejected {{quotationCode}}. Reason: {{remark}}
Link: /dashboard/crm/quotations/{{quotationId}}
Rules:
- templates should be organization-scoped
- seed should be idempotent
- no customer-sensitive data beyond document code/title in default body
Scope F.4.9 Delivery Channel Foundation
Support channel enum:
in_app
email
line
webhook
F.4 implementation:
in_app = active
email = delivery record only / pending
line = future
webhook = future
Do not implement real SMTP/LINE/Webhook sending yet.
Scope F.4.10 Audit / Observability
Audit or log:
notification_event_published
notification_event_processed
notification_created
notification_read
notification_archived
Rules:
- user read/archive actions should be auditable if current audit policy requires it
- processing failures must store error detail safely
- do not expose stack traces to users
Scope F.4.11 Permissions
Add or verify permissions:
notifications.read
notifications.update
notifications.admin
Rules:
- normal users can read/update only their own notifications
- admin can inspect templates/events only if admin API is added later
- notification bell should not require CRM module-specific permission
Scope F.4.12 Documentation
Create:
docs/implementation/task-f4-notification-framework-foundation.md
Document:
schema
event publishing flow
recipient resolver
template syntax
approval event integration
delivery channel limitations
known limitations
Event Flow
Recommended architecture:
Approval Service
↓
publishNotificationEvent()
↓
app_notification_events
↓
processNotificationEvent()
↓
resolveRecipients()
↓
renderTemplate()
↓
app_notifications
↓
Notification Bell / Inbox
Explicit Non-Scope
Do not implement:
- SMTP email sending
- LINE OA integration
- webhook delivery
- real-time websocket / SSE
- reminder scheduling
- escalation rules
- notification preference center
- admin template editor UI
- full notification analytics
- mobile push notifications
These belong to later F tasks.
Verification
Run:
npm exec tsc --noEmit
npm run build
Manual verification:
Submit quotation for approval
Current approver receives notification
Approve step
Requester receives notification
Next approver receives notification if next step exists
Reject step
Requester receives notification
Unread count updates
Mark one notification as read
Mark all notifications as read
Archive notification
Open notification link
Approval still succeeds if notification processing fails gracefully
Definition of Done
Task is complete when:
- Notification event tables exist
- Notification event service exists
- Recipient resolver exists
- Template renderer exists
- In-app notification records are created from approval events
- Notification inbox APIs exist
- Notification bell UI exists
- Approval submit/approve/reject events publish notifications
- Notification read/unread workflow works
- Existing approval flow remains operational
Result:
Notification Framework Foundation = Established
Approval Notifications = Operational In-App
Ready for F.5 Reminder / Escalation