5.1.6 Mention → email notification job (`work-item/comment.created` + description-mention parity) via the 1.6 pipeline + `mentionNotification` template
Estimate: 30m · Depends on: 5.1.2, 1.6.3
The notification hook — the stub's "Mention → notification hook (via Story 1.6 jobs)". Email only here; the event payload is channel-agnostic so Story 5.7 (in-app bell) and Story 5.4 (watchers) fan in off the SAME events later.
Job (lib/jobs/definitions/mentionNotify.ts, the 1.6 defineJob harness — mirroring emailSend.ts, the production reference): consumes work-item/comment.created (and work-item/mentioned, below). Per mentioned user: skip the author (never self-notify), re-validate view access at send time (the Jira rule — access may have changed since the write; the 5.1.2 scoping reused), render the mentionNotification template and send through the shipped email pipeline. Idempotent per (commentId × userId) via the harness idempotency key — a replay or retry never double-mails. Failures land in the DLQ per the 1.6 contract.
Template (lib/emailTemplates/mentionNotification.tsx, the pure-template contract): "<Author> mentioned you on <PROD-N: title>" subject; body with the author, the issue identifier + title as a deep link, a short PLAIN-TEXT excerpt of the comment (Markdown stripped, mention tokens rendered as @Name — a small lib/mentions helper; no raw token leakage), and the CTA button to the issue. Hand-written plain text with the link unredacted (the dev-console grep contract).
Description-mention parity (Jira-faithful). Mentions in the issue DESCRIPTION notify too: on createWorkItem / description-changing updateWorkItem, parse with the SAME lib/mentions/parse.ts helper, diff against the prior body (only newly-added, view-validated ids), and emit work-item/mentioned (workspaceId, workItemId, authorId, mentionedUserIds) after commit. The job treats both events uniformly (idempotency key swaps commentId for the revision id). No WorkItem schema change — description mentions are notification-only (no stored mention rows; recorded as the deliberate scope line: the queryable substrate is comment-scoped until a use case earns more).
Acceptance criteria
JobEventDataMapgainswork-item/comment.created+work-item/mentioned(typed payloads); both emit AFTER their transactions commit; the job fans out one email per mentioned user, skipping the author and any user who can no longer view the issue at send time.- Idempotency: replaying the event or retrying the job never double-sends (per comment×user / revision×user key); failures land in the DLQ (
@inngest/testcoverage, the 1.6 test harness). mentionNotificationis a pure template (no I/O; props in,{subject, text, html}out) with hand-written plain text + unredacted deep link; the excerpt renders mention tokens as @Name, never rawmention:markup.- Editing a comment notifies ONLY newly-added mentions; description create/edit notifies newly-added description mentions through the same job; the dev email console shows the
[EMAIL]line in the E2E flow. pnpm test:coverageholds the gate on the new job/template/service-diff code.
Context refs
lib/jobs/defineJob.ts+definitions/emailSend.ts(1.6.3) — the harness, idempotency, DLQ + the production job exemplar;lib/jobs/types.ts— the typed event map to extendlib/emailTemplates/contract (CLAUDE.md — pure templates, hand-written plain text);workspaceInvite.tsxas the exemplarlib/mentions/parse.ts+ the view-scoping (5.1.2);workItemsServicecreate/update paths (the description diff point)- Story 5.7 stub (the in-app consumer of these same events) + Story 5.4 (watchers) — the channel-agnostic payload contract