OUT-3737 | Send flow: email-only delivery via NotificationService#1257
OUT-3737 | Send flow: email-only delivery via NotificationService#1257arpandhakal wants to merge 1 commit into
Conversation
Plumbs the reminder email payload through copilot.createNotification with deliveryTargets.email only. Deliberately does not write to ClientNotification (that table tracks in-product read-state, which reminders don't create) and does not write to TaskReminderSent (the caller owns the ledger insert as the dedupe primitive on success). Throws on Copilot failure so callers can skip the ledger and let the next cron run retry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryAdds
Confidence Score: 3/5Safe to merge in isolation since nothing is wired up yet, but the null-company case will cause every affected reminder to fail permanently once OUT-3730 lands. The helper drops src/jobs/notifications/send-reminder-email.ts — specifically the Important Files Changed
Sequence DiagramsequenceDiagram
participant Cron as Cron Job
participant Helper as sendReminderEmail
participant EmailDetails as getReminderEmailDetails
participant Copilot as CopilotAPI
Cron->>Helper: task, recipientClientId, recipientCompanyId, reminderType
Helper->>EmailDetails: workspace, task, isCompanyRecipient
EmailDetails-->>Helper: subject, header, title, body
Helper->>Helper: build email-only payload
Helper->>Copilot: createNotification
alt recipientCompanyId is null
Copilot-->>Helper: API error
Helper-->>Cron: throws, no TaskReminderSent insert
else recipientCompanyId present
Copilot-->>Helper: notification id
Helper-->>Cron: notification.id
end
Reviews (1): Last reviewed commit: "feat(OUT-3737): add sendReminderEmail he..." | Re-trigger Greptile |
| senderId: task.createdById, | ||
| senderType: 'internalUser', | ||
| recipientClientId, | ||
| recipientCompanyId: recipientCompanyId ?? undefined, |
There was a problem hiding this comment.
recipientCompanyId: null will cause Copilot API rejection when recipientClientId is set
When recipientCompanyId is null, the payload drops the field (line 45) but keeps recipientClientId. validateNotificationRecipient (in src/utils/notifications.ts) enforces that recipientClientId must always be paired with recipientCompanyId — and eligibility.ts line 17 confirms this is a Copilot API-level requirement for email-bearing notifications. Any client task where task.companyId is NULL in the database will produce a payload that the API rejects on every cron run, permanently blocking reminder delivery for that recipient. The test at line 86 of the test file mocks createNotification so this is not caught by the test suite. Consider throwing early when recipientCompanyId is null so the caller (OUT-3730) can skip or log the ineligible row rather than silently eating a 4xx on every run.
Changes
src/jobs/notifications/send-reminder-email.ts—sendReminderEmail()helper that dispatches a single reminder email viacopilot.createNotificationwithdeliveryTargets.emailonly.src/jobs/notifications/send-reminder-email.test.ts— covers payload shape, return value, company-recipient header branch, nullrecipientCompanyId, and error propagation.Implements OUT-3737.
Why not reuse
NotificationService.createThat method assumes a
NotificationTaskActionsaction and writes toClientNotification— which tracks read-state for in-product notifications that reminders don't create. Reminder dedupe state lives inTaskReminderSent(OUT-3734), inserted by the caller (OUT-3730 cron) on success as the idempotency primitive.What the helper deliberately does NOT do
ClientNotification/InternalUserNotification.TaskReminderSent(caller's responsibility on success).CopilotAPIso the cron can amortize one client across many tasks per workspace, per the ticket's "do not rely on request-scopedthis.copilot".Testing Criteria
yarn test src/jobs/notifications/send-reminder-email.test.ts— 5/5 passing.yarn lint:check— 0 errors on new files.yarn prettier:check— clean.Notes
feature/email-reminders, notmain. Bundles into PR feature/email-reminders #1256.TaskReminderTypeenum), OUT-3735 (getReminderEmailDetails) — both already merged intofeature/email-reminders.sendReminderEmailand own the per-workspace fan-out, company member resolution viagetCompanyClients, and ledger inserts.senderId = task.createdByIdper the ticket. Flag in review if reminders should use a different sender identity (e.g. a system user).Impact & Surface Area of Change
NotificationService.createis untouched, as isgetEmailDetails. No DB schema change. No Trigger.dev registration.🤖 Generated with Claude Code