Problem
The current Novu in-app notifications display developer-facing terminology that is confusing and unprofessional for non-technical users. Since Familiarise targets a broad professional audience (management, marketing, sales, mechanical engineers, etc.), notification copy must be human-friendly.
Examples from current notifications
| Current message |
Problem |
| "Priya Test Consultee rescheduled the subscription for Full-Stack Mentorship Program from to" |
Missing date values (oldDateTime/newDateTime not populated), raw template leaking through |
| "consultee cancelled the CONSULTATION session for System Design Deep Dive. Reason: SCHEDULE_CONFLICT" |
Uses role name "consultee" instead of actual user name, uppercase enum CONSULTATION, raw enum SCHEDULE_CONFLICT |
| "consultant cancelled the CONSULTATION session for System Design Deep Dive. Reason: SCHEDULE_CONFLICT" |
Uses role name "consultant" instead of actual user name, uppercase enum values |
Root causes
- Novu Dashboard templates use raw
{{payload.*}} values without transformation (e.g., {{payload.appointmentType}} renders as "CONSULTATION" not "consultation")
- Payload construction in API routes passes raw enum values and role labels instead of human-readable strings
- Missing payload values (e.g.,
oldDateTime/newDateTime in reschedule notifications are not populated)
- Fallback strings like
"Consultant", "Consultee", "N/A", "Unknown" leak into notifications when data is missing
Complete Notification Audit
Codebase architecture
- Workflow definitions:
lib/novu/workflows.ts (35 workflow IDs + typed payloads)
- Trigger service:
lib/novu/service.ts (all notify*() functions)
- Novu Dashboard template specs:
docs/notifications/03-novu-template-specs.md
- Subscriber sync:
lib/novu/subscriber.ts, hooks/useNovuSubscriberSync.ts
- Frontend inbox:
components/notifications/NotificationInbox.tsx, providers/NovuProvider.tsx
All notification trigger points and current payloads
1. Appointment Booked (appointment-booked)
- File:
lib/payments/webhooks/handlers.ts (line ~514)
- Current payload issues:
appointmentType: passes raw metadata.appointmentType (e.g., "CONSULTATION", "SUBSCRIPTION")
planTitle: incorrectly passes metadata.planId (a UUID!) as fallback for plan title
consulteeName: falls back to "User"
- Template:
Your {{payload.appointmentType}} "{{payload.planTitle}}" has been booked for {{payload.dateTime}}.
2. Appointment Cancelled (appointment-cancelled)
- File:
app/api/appointments/[appointmentId]/cancel/route.ts (line ~258)
- Current payload issues:
appointmentType: passes raw DB enum value (e.g., "CONSULTATION", "SUBSCRIPTION")
reason: passes raw CancellationReason enum (e.g., "SCHEDULE_CONFLICT", "CONSULTANT_UNAVAILABLE")
cancelledBy: passes "consultant" or "consultee" (role labels, not names)
consulteeName: falls back to "Consultee"
consultantName: falls back to "Consultant"
- No webinar/class participant names extracted
- Template:
Your {{payload.appointmentType}} "{{payload.planTitle}}" has been cancelled{{#if payload.reason}}: {{payload.reason}}{{/if}}.
- Also triggered from:
actions/maintenance/freeze-appointments.ts (with cancelledBy: "system", reason: "Scheduled platform maintenance")
3. Appointment Rescheduled (appointment-rescheduled)
- File:
app/api/appointments/[appointmentId]/reschedule/route.ts (line ~399)
- Current payload issues:
oldDateTime and newDateTime are NOT populated at all (missing from payload construction)
appointmentType: passes lowercase "consultation" or "subscription" (inconsistent with cancel route which passes uppercase)
consulteeName: falls back to "Consultee"
consultantName: falls back to "Consultant"
4. Appointment Reminder (appointment-reminder)
- File:
scripts/appointments/send-appointment-reminders.ts (line ~212)
- Current payload issues:
appointmentType: passes lowercase ("consultation", "subscription", "webinar", "class")
consulteeName: falls back to "Consultee" (inappropriate for webinar/class where there may be many participants)
- Template:
Reminder: Your {{payload.appointmentType}} "{{payload.planTitle}}" is coming up on {{payload.dateTime}}.
5. Appointment Completed (appointment-completed)
- File:
scripts/appointments/auto-complete-appointments.ts (lines ~284, ~405)
- Current payload issues:
appointmentType: passes lowercase ("consultation", "subscription")
consulteeName: falls back to "Consultee"
consultantName: falls back to "Consultant"
6. Payment Success (payment-success)
- File:
lib/payments/webhooks/handlers.ts (line ~499)
- Current payload issues:
appointmentType: passes raw metadata.appointmentType
planTitle: passes metadata.planId (UUID!) as fallback -- critical bug
consultantName: falls back to "Consultant"
7. Payment Failed (payment-failed)
- File:
lib/payments/webhooks/handlers.ts (referenced in service, may be in Stripe/Razorpay webhook handlers)
- Payload issues:
failureReason: likely passes raw gateway error strings
appointmentType: raw enum value
8. Refund Processed (refund-processed)
- File:
app/api/webhooks/utils.ts (line ~212)
- Current payload: minimal -- only
amount, currency, dashboardUrl
- Issues: No human-friendly context about what was refunded
9. Refund Requested (refund-requested)
- Defined in service but no active call site found -- may not be triggered yet
10. Support Ticket Created (support-ticket-created)
- File:
app/api/user/support-tickets/route.ts (line ~188)
- Payload:
ticketId, ticketTitle, dashboardUrl
- Issues: Relatively clean, but ticket ID is raw UUID
11. Support Ticket Update (support-ticket-update)
- File:
app/api/staff/support-tickets/[ticketId]/route.ts (line ~272)
- Payload issues:
status: passes raw DB enum (e.g., "IN_PROGRESS", "RESOLVED")
12. Support Ticket Response (support-ticket-response)
- File:
app/api/staff/support-tickets/[ticketId]/responses/route.ts (line ~93)
- Payload: Clean --
ticketTitle, message, dashboardUrl
13. Feedback Received (feedback-received)
- File:
app/api/user/feedbacks/route.ts (line ~82)
- Payload issues:
category: passes raw DB enum value
14. New Review Received (new-review-received)
- File:
app/api/user/reviews/route.ts (line ~135)
- Payload: Relatively clean --
reviewerName, rating, comment
15. Trial Session Requested (trial-session-requested)
- File:
app/api/trials/route.ts (line ~349)
- Payload issues:
status: passes raw enum "PENDING"
16. Trial Session Scheduled (trial-session-scheduled)
- File:
app/api/trials/[trialId]/route.ts (line ~419)
- Payload issues:
status: passes raw enum "SCHEDULED"
17. Trial Session Completed (trial-session-completed)
- File:
app/api/trials/[trialId]/route.ts (line ~458)
- Payload issues:
status: passes raw enum "COMPLETED"
18. Trial Session Cancelled (trial-session-cancelled)
- File:
app/api/trials/[trialId]/route.ts (line ~479)
- Payload issues:
status: passes raw enum value ("CANCELLED" or "REJECTED")
19. Subscription Started (subscription-started)
- Files:
app/api/events/subscriptions/route.ts (line ~255), app/api/events/subscriptions/[subscriptionId]/route.ts
- Payload: Relatively clean
20. Subscription Cancelled (subscription-cancelled)
- Files:
app/api/events/subscriptions/route.ts (line ~276), app/api/events/subscriptions/[subscriptionId]/route.ts
- Payload: Relatively clean
21. Subscription Renewed (subscription-renewed)
- Defined in service but no active call site found
22. New Booking Request (new-booking-request)
- File:
app/api/slots/request-for-approval/route.ts (line ~226)
- Payload issues:
appointmentType: passes raw "CONSULTATION"
consulteeName: falls back to "A consultee"
23. Verification Status Changed (verification-status-changed)
- Files:
app/api/staff/moderation/profiles/[verificationId]/route.ts (line ~214), app/api/admin/verification/[verificationId]/route.ts (line ~219)
- Payload issues:
status: passes raw enum ("VERIFIED", "REJECTED", "PENDING_VERIFICATION")
24. Payout Processed (payout-processed)
- File:
lib/payments/payouts/payout-service.ts (line ~805)
- Payload: Clean --
amount, currency, payoutId
25. General Announcement (general-announcement)
- File:
app/api/announcements/route.ts (line ~96)
- Payload: Clean -- admin-authored content
26. New Consultant Application (new-consultant-application)
- Files:
app/api/verification/submit/route.ts (line ~126), utils/onboarding-server.ts
- Payload: Clean --
applicantName, applicantEmail
27. Waitlist Spot Available (waitlist-spot-available)
- Defined in service, triggered from waitlist handlers
- Payload: Clean
28. Dispute Created (dispute-created)
- File:
app/api/webhooks/utils.ts (line ~330)
- Payload issues:
reason: passes raw Stripe/Razorpay dispute reason strings
status: passes raw mapped enum ("NEEDS_RESPONSE", "WARNING_NEEDS_RESPONSE", etc.)
29. Dispute Resolved (dispute-resolved)
- File:
app/api/webhooks/utils.ts (line ~383)
- Payload issues:
status: passes raw enum ("WON", "LOST", "CHARGE_REFUNDED")
30. Recording Available (recording-available)
- File:
lib/stream/recording-handlers.ts (line ~354)
- Payload issues:
appointmentType: passes lowercase ("consultation", "subscription", etc.)
consultantName: falls back to "Unknown Consultant"
31. Recording Failed (recording-failed)
- File:
lib/stream/recording-handlers.ts (line ~442)
- Payload issues:
errorMessage: passes raw Stream.io error strings
32. Referral Bonus Earned / Referee Welcome Bonus / Referral Credits Applied
- Defined in service, triggered from
lib/referrals/service.ts
- Payload issues:
appointmentType (in credits applied): likely raw enum
33. Collaborator Invited (collaborator-invited)
- File:
lib/collaborators/service.ts (line ~166)
- Payload issues:
planType: passes raw "webinar" or "class"
role: passes raw DB enum (e.g., "CO_HOST", "GUEST_SPEAKER", "MODERATOR")
34. Collaborator Accepted (collaborator-accepted)
- File:
lib/collaborators/service.ts (lines ~232, ~285)
- Payload issues:
planType: raw "webinar" or "class"
role: raw DB enum
35. Collaborator Removed (collaborator-removed)
- File:
lib/collaborators/service.ts (lines ~338, ~376)
- Payload issues:
planType: raw "webinar" or "class"
36-38. Maintenance Scheduled/Started/Ended
- Files:
actions/maintenance/drain-sessions.ts (line ~171), actions/maintenance/post-recovery.ts (line ~117), actions/maintenance/freeze-appointments.ts
- Payload issues:
phase: passes raw "OFFLINE", "OFF" strings
- Note: These are system broadcasts; less critical for user-facing copy
Enum-to-Human-Readable Mapping Tables
AppointmentsType / appointmentType
| Raw value |
Human-friendly |
CONSULTATION |
Consultation |
SUBSCRIPTION |
Mentorship |
WEBINAR |
Webinar |
CLASS |
Class |
TRIAL |
Trial session |
CancellationReason
| Raw value |
Human-friendly |
SCHEDULE_CONFLICT |
Scheduling conflict |
FOUND_ALTERNATIVE |
Found an alternative |
FINANCIAL_REASONS |
Financial reasons |
PERSONAL_EMERGENCY |
Personal emergency |
NO_LONGER_NEEDED |
No longer needed |
CONSULTANT_UNAVAILABLE |
Expert unavailable |
CONSULTANT_EMERGENCY |
Expert emergency |
PAYMENT_FAILED |
Payment issue |
EXPIRED |
Booking expired |
CONSULTANT_ISSUE |
Issue with expert |
TECHNICAL_ISSUE |
Technical issue |
TrialSessionStatus
| Raw value |
Human-friendly |
PENDING |
Pending review |
SCHEDULED |
Scheduled |
COMPLETED |
Completed |
CONVERTED |
Converted to subscription |
CANCELLED |
Cancelled |
REJECTED |
Declined |
ConsultantVerificationStatus
| Raw value |
Human-friendly |
PENDING_VERIFICATION |
Pending verification |
UNDER_REVIEW |
Under review |
VERIFIED |
Verified |
REJECTED |
Needs resubmission |
Support Ticket Status
| Raw value |
Human-friendly |
OPEN |
Open |
IN_PROGRESS |
In progress |
RESOLVED |
Resolved |
CLOSED |
Closed |
Dispute Status
| Raw value |
Human-friendly |
NEEDS_RESPONSE |
Needs your response |
UNDER_REVIEW |
Under review |
WON |
Resolved in your favor |
LOST |
Resolved against you |
CHARGE_REFUNDED |
Charge refunded |
WARNING_NEEDS_RESPONSE |
Warning: needs response |
WARNING_UNDER_REVIEW |
Warning: under review |
WARNING_CLOSED |
Warning resolved |
Collaborator Roles
| Raw value |
Human-friendly |
CO_HOST |
Co-host |
GUEST_SPEAKER |
Guest speaker |
MODERATOR |
Moderator |
PANELIST |
Panelist |
ASSISTANT |
Assistant |
cancelledBy
| Raw value |
Human-friendly (for notification copy) |
consultant |
"your expert" / the actual consultant name |
consultee |
"the client" / the actual consultee name |
system |
"the system due to maintenance" |
Implementation Plan
Phase 1: Create a shared humanization utility (new file)
Create lib/novu/humanize.ts with:
// Maps raw enum values to human-friendly strings
export function humanizeAppointmentType(raw: string): string { ... }
export function humanizeCancellationReason(raw: string): string { ... }
export function humanizeTrialStatus(raw: string): string { ... }
export function humanizeVerificationStatus(raw: string): string { ... }
export function humanizeSupportTicketStatus(raw: string): string { ... }
export function humanizeDisputeStatus(raw: string): string { ... }
export function humanizeCollaboratorRole(raw: string): string { ... }
export function humanizeCancelledBy(raw: string, name?: string): string { ... }
// Formats ISO datetime to user-friendly string
export function humanizeDateTime(iso: string | undefined): string { ... }
// Ensures names are never raw role labels
export function humanizeName(name: string | null | undefined, role: 'consultant' | 'consultee'): string { ... }
Phase 2: Fix payload construction at each call site
For every notification trigger point listed above, update the payload to:
- Use
humanizeAppointmentType() for all appointmentType fields
- Use
humanizeCancellationReason() for all reason fields
- Use
humanizeDateTime() for all date/time fields
- Use actual user names (never
"Consultant", "Consultee", "User")
- Use
humanize*() for all status/enum fields
- Fix the reschedule route to actually populate
oldDateTime and newDateTime
- Fix the payment success route to not pass
metadata.planId (UUID) as planTitle
Phase 3: Update Novu Dashboard templates
For templates that use text-transform: capitalize or similar CSS tricks, these can be removed once payloads are pre-humanized. Update in-app notification copy in the Novu Dashboard to be complete, well-formatted sentences.
Phase 4: Handle edge cases
- Missing data fallbacks: Instead of
"Consultant" / "Consultee", use "your expert" / "the client" (role-appropriate language without exposing internal terminology)
- Multi-participant events (webinars/classes): Don't show
consulteeName -- use "your upcoming webinar" style language
- Date formatting: Use
Intl.DateTimeFormat with user's locale for all dates; provide a sensible fallback if timezone unknown
- Reason codes from payment gateways: Map Stripe/Razorpay raw failure codes to user-friendly messages
Specific Bug Fixes Required
| Bug |
File |
Fix |
| Reschedule notification missing dates |
app/api/appointments/[appointmentId]/reschedule/route.ts |
Populate oldDateTime and newDateTime from the slot data before marking tentative |
Payment success planTitle shows UUID |
lib/payments/webhooks/handlers.ts (~line 504) |
Fetch actual plan title instead of using metadata.planId as fallback |
| Cancel route only extracts names for consultation/subscription |
app/api/appointments/[appointmentId]/cancel/route.ts |
Add webinar/class consultant name extraction |
| Inconsistent appointmentType casing |
Multiple files |
Standardize: always pass through humanizeAppointmentType() |
cancelledBy shows role not name |
Cancel route |
Pass the cancelling user's actual name, not the role string |
Testing Plan
Files to modify
New file:
Core payload construction (all trigger points):
lib/payments/webhooks/handlers.ts
app/api/appointments/[appointmentId]/cancel/route.ts
app/api/appointments/[appointmentId]/reschedule/route.ts
app/api/events/subscriptions/route.ts
app/api/events/subscriptions/[subscriptionId]/route.ts
app/api/trials/route.ts
app/api/trials/[trialId]/route.ts
app/api/user/support-tickets/route.ts
app/api/user/reviews/route.ts
app/api/user/feedbacks/route.ts
app/api/slots/request-for-approval/route.ts
app/api/verification/submit/route.ts
app/api/staff/support-tickets/[ticketId]/route.ts
app/api/staff/support-tickets/[ticketId]/responses/route.ts
app/api/staff/moderation/profiles/[verificationId]/route.ts
app/api/admin/verification/[verificationId]/route.ts
app/api/announcements/route.ts
app/api/webhooks/utils.ts
lib/stream/recording-handlers.ts
lib/payments/payouts/payout-service.ts
lib/collaborators/service.ts
scripts/appointments/send-appointment-reminders.ts
scripts/appointments/auto-complete-appointments.ts
actions/maintenance/freeze-appointments.ts
actions/maintenance/drain-sessions.ts
actions/maintenance/post-recovery.ts
utils/onboarding-server.ts
Novu Dashboard templates (external):
- All 16 Tier-1 workflow templates listed in
docs/notifications/03-novu-template-specs.md
Documentation update:
docs/notifications/03-novu-template-specs.md (update template specs to reflect humanized payloads)
Problem
The current Novu in-app notifications display developer-facing terminology that is confusing and unprofessional for non-technical users. Since Familiarise targets a broad professional audience (management, marketing, sales, mechanical engineers, etc.), notification copy must be human-friendly.
Examples from current notifications
oldDateTime/newDateTimenot populated), raw template leaking throughCONSULTATION, raw enumSCHEDULE_CONFLICTRoot causes
{{payload.*}}values without transformation (e.g.,{{payload.appointmentType}}renders as "CONSULTATION" not "consultation")oldDateTime/newDateTimein reschedule notifications are not populated)"Consultant","Consultee","N/A","Unknown"leak into notifications when data is missingComplete Notification Audit
Codebase architecture
lib/novu/workflows.ts(35 workflow IDs + typed payloads)lib/novu/service.ts(allnotify*()functions)docs/notifications/03-novu-template-specs.mdlib/novu/subscriber.ts,hooks/useNovuSubscriberSync.tscomponents/notifications/NotificationInbox.tsx,providers/NovuProvider.tsxAll notification trigger points and current payloads
1. Appointment Booked (
appointment-booked)lib/payments/webhooks/handlers.ts(line ~514)appointmentType: passes rawmetadata.appointmentType(e.g.,"CONSULTATION","SUBSCRIPTION")planTitle: incorrectly passesmetadata.planId(a UUID!) as fallback for plan titleconsulteeName: falls back to"User"Your {{payload.appointmentType}} "{{payload.planTitle}}" has been booked for {{payload.dateTime}}.2. Appointment Cancelled (
appointment-cancelled)app/api/appointments/[appointmentId]/cancel/route.ts(line ~258)appointmentType: passes raw DB enum value (e.g.,"CONSULTATION","SUBSCRIPTION")reason: passes rawCancellationReasonenum (e.g.,"SCHEDULE_CONFLICT","CONSULTANT_UNAVAILABLE")cancelledBy: passes"consultant"or"consultee"(role labels, not names)consulteeName: falls back to"Consultee"consultantName: falls back to"Consultant"Your {{payload.appointmentType}} "{{payload.planTitle}}" has been cancelled{{#if payload.reason}}: {{payload.reason}}{{/if}}.actions/maintenance/freeze-appointments.ts(withcancelledBy: "system",reason: "Scheduled platform maintenance")3. Appointment Rescheduled (
appointment-rescheduled)app/api/appointments/[appointmentId]/reschedule/route.ts(line ~399)oldDateTimeandnewDateTimeare NOT populated at all (missing from payload construction)appointmentType: passes lowercase"consultation"or"subscription"(inconsistent with cancel route which passes uppercase)consulteeName: falls back to"Consultee"consultantName: falls back to"Consultant"4. Appointment Reminder (
appointment-reminder)scripts/appointments/send-appointment-reminders.ts(line ~212)appointmentType: passes lowercase ("consultation","subscription","webinar","class")consulteeName: falls back to"Consultee"(inappropriate for webinar/class where there may be many participants)Reminder: Your {{payload.appointmentType}} "{{payload.planTitle}}" is coming up on {{payload.dateTime}}.5. Appointment Completed (
appointment-completed)scripts/appointments/auto-complete-appointments.ts(lines ~284, ~405)appointmentType: passes lowercase ("consultation","subscription")consulteeName: falls back to"Consultee"consultantName: falls back to"Consultant"6. Payment Success (
payment-success)lib/payments/webhooks/handlers.ts(line ~499)appointmentType: passes rawmetadata.appointmentTypeplanTitle: passesmetadata.planId(UUID!) as fallback -- critical bugconsultantName: falls back to"Consultant"7. Payment Failed (
payment-failed)lib/payments/webhooks/handlers.ts(referenced in service, may be in Stripe/Razorpay webhook handlers)failureReason: likely passes raw gateway error stringsappointmentType: raw enum value8. Refund Processed (
refund-processed)app/api/webhooks/utils.ts(line ~212)amount,currency,dashboardUrl9. Refund Requested (
refund-requested)10. Support Ticket Created (
support-ticket-created)app/api/user/support-tickets/route.ts(line ~188)ticketId,ticketTitle,dashboardUrl11. Support Ticket Update (
support-ticket-update)app/api/staff/support-tickets/[ticketId]/route.ts(line ~272)status: passes raw DB enum (e.g.,"IN_PROGRESS","RESOLVED")12. Support Ticket Response (
support-ticket-response)app/api/staff/support-tickets/[ticketId]/responses/route.ts(line ~93)ticketTitle,message,dashboardUrl13. Feedback Received (
feedback-received)app/api/user/feedbacks/route.ts(line ~82)category: passes raw DB enum value14. New Review Received (
new-review-received)app/api/user/reviews/route.ts(line ~135)reviewerName,rating,comment15. Trial Session Requested (
trial-session-requested)app/api/trials/route.ts(line ~349)status: passes raw enum"PENDING"16. Trial Session Scheduled (
trial-session-scheduled)app/api/trials/[trialId]/route.ts(line ~419)status: passes raw enum"SCHEDULED"17. Trial Session Completed (
trial-session-completed)app/api/trials/[trialId]/route.ts(line ~458)status: passes raw enum"COMPLETED"18. Trial Session Cancelled (
trial-session-cancelled)app/api/trials/[trialId]/route.ts(line ~479)status: passes raw enum value ("CANCELLED"or"REJECTED")19. Subscription Started (
subscription-started)app/api/events/subscriptions/route.ts(line ~255),app/api/events/subscriptions/[subscriptionId]/route.ts20. Subscription Cancelled (
subscription-cancelled)app/api/events/subscriptions/route.ts(line ~276),app/api/events/subscriptions/[subscriptionId]/route.ts21. Subscription Renewed (
subscription-renewed)22. New Booking Request (
new-booking-request)app/api/slots/request-for-approval/route.ts(line ~226)appointmentType: passes raw"CONSULTATION"consulteeName: falls back to"A consultee"23. Verification Status Changed (
verification-status-changed)app/api/staff/moderation/profiles/[verificationId]/route.ts(line ~214),app/api/admin/verification/[verificationId]/route.ts(line ~219)status: passes raw enum ("VERIFIED","REJECTED","PENDING_VERIFICATION")24. Payout Processed (
payout-processed)lib/payments/payouts/payout-service.ts(line ~805)amount,currency,payoutId25. General Announcement (
general-announcement)app/api/announcements/route.ts(line ~96)26. New Consultant Application (
new-consultant-application)app/api/verification/submit/route.ts(line ~126),utils/onboarding-server.tsapplicantName,applicantEmail27. Waitlist Spot Available (
waitlist-spot-available)28. Dispute Created (
dispute-created)app/api/webhooks/utils.ts(line ~330)reason: passes raw Stripe/Razorpay dispute reason stringsstatus: passes raw mapped enum ("NEEDS_RESPONSE","WARNING_NEEDS_RESPONSE", etc.)29. Dispute Resolved (
dispute-resolved)app/api/webhooks/utils.ts(line ~383)status: passes raw enum ("WON","LOST","CHARGE_REFUNDED")30. Recording Available (
recording-available)lib/stream/recording-handlers.ts(line ~354)appointmentType: passes lowercase ("consultation","subscription", etc.)consultantName: falls back to"Unknown Consultant"31. Recording Failed (
recording-failed)lib/stream/recording-handlers.ts(line ~442)errorMessage: passes raw Stream.io error strings32. Referral Bonus Earned / Referee Welcome Bonus / Referral Credits Applied
lib/referrals/service.tsappointmentType(in credits applied): likely raw enum33. Collaborator Invited (
collaborator-invited)lib/collaborators/service.ts(line ~166)planType: passes raw"webinar"or"class"role: passes raw DB enum (e.g.,"CO_HOST","GUEST_SPEAKER","MODERATOR")34. Collaborator Accepted (
collaborator-accepted)lib/collaborators/service.ts(lines ~232, ~285)planType: raw"webinar"or"class"role: raw DB enum35. Collaborator Removed (
collaborator-removed)lib/collaborators/service.ts(lines ~338, ~376)planType: raw"webinar"or"class"36-38. Maintenance Scheduled/Started/Ended
actions/maintenance/drain-sessions.ts(line ~171),actions/maintenance/post-recovery.ts(line ~117),actions/maintenance/freeze-appointments.tsphase: passes raw"OFFLINE","OFF"stringsEnum-to-Human-Readable Mapping Tables
AppointmentsType / appointmentType
CONSULTATIONSUBSCRIPTIONWEBINARCLASSTRIALCancellationReason
SCHEDULE_CONFLICTFOUND_ALTERNATIVEFINANCIAL_REASONSPERSONAL_EMERGENCYNO_LONGER_NEEDEDCONSULTANT_UNAVAILABLECONSULTANT_EMERGENCYPAYMENT_FAILEDEXPIREDCONSULTANT_ISSUETECHNICAL_ISSUETrialSessionStatus
PENDINGSCHEDULEDCOMPLETEDCONVERTEDCANCELLEDREJECTEDConsultantVerificationStatus
PENDING_VERIFICATIONUNDER_REVIEWVERIFIEDREJECTEDSupport Ticket Status
OPENIN_PROGRESSRESOLVEDCLOSEDDispute Status
NEEDS_RESPONSEUNDER_REVIEWWONLOSTCHARGE_REFUNDEDWARNING_NEEDS_RESPONSEWARNING_UNDER_REVIEWWARNING_CLOSEDCollaborator Roles
CO_HOSTGUEST_SPEAKERMODERATORPANELISTASSISTANTcancelledBy
consultantconsulteesystemImplementation Plan
Phase 1: Create a shared humanization utility (new file)
Create
lib/novu/humanize.tswith:Phase 2: Fix payload construction at each call site
For every notification trigger point listed above, update the payload to:
humanizeAppointmentType()for allappointmentTypefieldshumanizeCancellationReason()for allreasonfieldshumanizeDateTime()for all date/time fields"Consultant","Consultee","User")humanize*()for all status/enum fieldsoldDateTimeandnewDateTimemetadata.planId(UUID) asplanTitlePhase 3: Update Novu Dashboard templates
For templates that use
text-transform: capitalizeor similar CSS tricks, these can be removed once payloads are pre-humanized. Update in-app notification copy in the Novu Dashboard to be complete, well-formatted sentences.Phase 4: Handle edge cases
"Consultant"/"Consultee", use"your expert"/"the client"(role-appropriate language without exposing internal terminology)consulteeName-- use"your upcoming webinar"style languageIntl.DateTimeFormatwith user's locale for all dates; provide a sensible fallback if timezone unknownSpecific Bug Fixes Required
app/api/appointments/[appointmentId]/reschedule/route.tsoldDateTimeandnewDateTimefrom the slot data before marking tentativeplanTitleshows UUIDlib/payments/webhooks/handlers.ts(~line 504)metadata.planIdas fallbackapp/api/appointments/[appointmentId]/cancel/route.tshumanizeAppointmentType()cancelledByshows role not nameTesting Plan
CancellationReasonenum values render as human-readable stringsFiles to modify
New file:
lib/novu/humanize.tsCore payload construction (all trigger points):
lib/payments/webhooks/handlers.tsapp/api/appointments/[appointmentId]/cancel/route.tsapp/api/appointments/[appointmentId]/reschedule/route.tsapp/api/events/subscriptions/route.tsapp/api/events/subscriptions/[subscriptionId]/route.tsapp/api/trials/route.tsapp/api/trials/[trialId]/route.tsapp/api/user/support-tickets/route.tsapp/api/user/reviews/route.tsapp/api/user/feedbacks/route.tsapp/api/slots/request-for-approval/route.tsapp/api/verification/submit/route.tsapp/api/staff/support-tickets/[ticketId]/route.tsapp/api/staff/support-tickets/[ticketId]/responses/route.tsapp/api/staff/moderation/profiles/[verificationId]/route.tsapp/api/admin/verification/[verificationId]/route.tsapp/api/announcements/route.tsapp/api/webhooks/utils.tslib/stream/recording-handlers.tslib/payments/payouts/payout-service.tslib/collaborators/service.tsscripts/appointments/send-appointment-reminders.tsscripts/appointments/auto-complete-appointments.tsactions/maintenance/freeze-appointments.tsactions/maintenance/drain-sessions.tsactions/maintenance/post-recovery.tsutils/onboarding-server.tsNovu Dashboard templates (external):
docs/notifications/03-novu-template-specs.mdDocumentation update:
docs/notifications/03-novu-template-specs.md(update template specs to reflect humanized payloads)