Repository navigation
Richer conversation notification emails (include content and thread context) #825
Description
Part 1 of #811 — independently shippable.
Summary
Today's notification emails for Community Discussions and Product Discussions are one-line summaries (e.g. "X commented on your post 'Y' in Z") with a "View notification" button — no comment content, no thread context. This issue covers making those emails rich enough to understand the conversation without opening CourseLit.
Applies to the five conversation activity types:
COMMUNITY_POST_CREATED(new post — include post body excerpt)COMMUNITY_COMMENT_CREATEDCOMMUNITY_REPLY_CREATEDCOURSE_DISCUSSION_COMMENT_CREATED(eventTypecomment_createdandreply_created)
All other notification emails remain unchanged, and the in-app (APP) channel is untouched.
Approach
Enrich content queue-side at send time using the existing notification entity resolver — no changes to recordActivity callers, no fatter job metadata in Redis, no stale content if a comment is edited before dispatch.
-
Extend
NotificationEntityResolver(packages/common-logic/src/utils/notification-entity-resolver.ts) with optionalgetDiscussionComment(commentId, domainId)andgetDiscussionReply(replyId, domainId)using the same lazy-model pattern with the product-discussion schemas from@courselit/orm-models. (Community comment content is already reachable via the existinggetComment(); addcontentto thegetPost()projection — post content isTextEditorContent | string, handle both.) -
New
getNotificationEmailContent()inpackages/common-logic/src/utils/get-notification-email-content.ts:- Returns
{ subject, message, href, commentText?, parentText?, parentAuthorName?, threadTitle?, replyContext? }. - Internally calls
getNotificationMessageAndHref()formessage/href(single source of truth). - Populates the extra fields only for the five conversation activity types; product-discussion content converted with
extractTextFromTextEditorContentfrom@courselit/utils(community content is a plain string). - Caps: comment body ~1000 chars, parent excerpt ~200 chars.
- Optional
resolveUserName(userId)callback so the queue can resolve parent-author names without common-logic knowing user DB shape. replyContextcarries thread coordinates — unused in this phase, consumed later by reply-by-email (Enhanced conversation email notifications and reply-by-email support #811).
- Returns
-
Extend the email template (
apps/queue/src/notifications/services/channels/notification-email-template.ts):- Parent context block (small gray, In reply to {author}: "{excerpt}") when applicable.
- Comment body block (15px, line breaks preserved — verify markdown hard-break behavior in
renderEmailToHtml). - CTA text "View discussion" for conversation types (default stays "View notification").
- All content passed through the existing
encodePlainTextForMarkdown.
-
Wire
EmailChannel(apps/queue/src/notifications/services/channels/email.ts): swapgetNotificationMessageAndHref→getNotificationEmailContent, pass the new fields into the template, usecontent.subjectas the mail subject.- Subject stays the one-line summary (actor + action + truncated title) for inbox scanability.
Acceptance criteria
- New community post notification emails include the post title and a body excerpt.
- Community comment/reply notification emails include the comment/reply body, post title, and parent-comment excerpt (for replies).
- Product discussion comment/reply notification emails include the content, course title, and parent context.
- Emails clearly identify the author and contain a CTA linking to the conversation.
- All non-conversation notification emails are byte-for-byte unchanged.
- In-app notifications are unchanged.
- Unit tests:
getNotificationEmailContent(per conversation type + one non-conversation type asserting unchanged output), template block presence/absence.
Out of scope
Reply-by-email (tokens, Reply-To, inbound webhooks) — tracked in #811.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
rajat1saxena commentedon Jul 18, 2026
Fixes #826