-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Persist Bounties emails to Message + NotificationEmail #2884
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds per-recipient NotificationEmail tracking and related schema fields, introduces an "em_" ID prefix, updates cron handlers to send batch emails per-user and persist notificationEmail rows, tightens read-in-app/email update filters, and refactors the resend email-opened webhook into a transactional flow. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Cron as Cron (bounties/messages)
participant DB as Prisma DB
participant Email as Resend (sendBatchEmail)
rect rgb(240,248,255)
note over Cron: Build usersToNotify and latest unread message context
Cron->>DB: Query programs/partners, users, unread messages (ordered desc)
alt Recipients exist
Cron->>Email: sendBatchEmail(to: usersToNotify[].email, payloads)
Email-->>Cron: { data: [{id: emailId}, ...] }
Cron->>DB: createMany(notificationEmail entries per recipient: id em_..., type, programId/partnerId/bountyId, recipientUserId, emailId, messageId)
else No recipients
note over Cron: Exit
end
end
sequenceDiagram
autonumber
participant Resend as Resend
participant API as Webhook /email-opened
participant TX as Prisma Tx
participant DB as DB
Resend->>API: POST email-opened (emailId)
API->>TX: run transaction
rect rgb(245,255,245)
TX->>DB: updateMany notificationEmail set openedAt (where openedAt null & emailId match)
TX->>DB: findFirst notificationEmail by emailId
alt notificationEmail found with programId & partnerId
TX->>DB: updateMany messages set readInEmail (where readInEmail null, match programId/partnerId, createdAt <= notificationEmail.createdAt)
else
note over TX: No-op
end
end
TX-->>API: result
sequenceDiagram
autonumber
participant UI as App UI
participant API as mark-*-messages-read
participant DB as Prisma DB
UI->>API: Mark messages read (partner/program)
API->>DB: updateMany messages (filters + readInApp: null) set readInApp=now
DB-->>API: count updated
API-->>UI: success
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/prisma/schema/notification.prisma (1)
10-10
: Index and FK for bountyId recommended.You’ll likely filter/join by bountyId; add an index and (optionally) a FK to enforce integrity.
Add an index:
@@index([bountyId])
Optionally model the relation (if Bounty.id exists in the same schema):
bountyId String? bounty Bounty? @relation(fields: [bountyId], references: [id], onDelete: SetNull)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/app/(ee)/api/bounties/[bountyId]/route.ts
(1 hunks)apps/web/app/(ee)/api/bounties/route.ts
(1 hunks)apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts
(6 hunks)packages/prisma/schema/notification.prisma
(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: TWilson023
PR: dubinc/dub#2736
File: apps/web/app/api/workspaces/[idOrSlug]/notification-preferences/route.ts:13-14
Timestamp: 2025-08-26T14:20:23.943Z
Learning: The updateNotificationPreference action in apps/web/lib/actions/update-notification-preference.ts already handles all notification preference types dynamically, including newBountySubmitted, through its schema validation using the notificationTypes enum and Prisma's dynamic field update pattern.
📚 Learning: 2025-08-26T14:20:23.943Z
Learnt from: TWilson023
PR: dubinc/dub#2736
File: apps/web/app/api/workspaces/[idOrSlug]/notification-preferences/route.ts:13-14
Timestamp: 2025-08-26T14:20:23.943Z
Learning: The updateNotificationPreference action in apps/web/lib/actions/update-notification-preference.ts already handles all notification preference types dynamically, including newBountySubmitted, through its schema validation using the notificationTypes enum and Prisma's dynamic field update pattern.
Applied to files:
packages/prisma/schema/notification.prisma
apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts (3)
packages/email/src/index.ts (1)
sendBatchEmail
(32-70)apps/web/lib/api/create-id.ts (1)
createId
(62-67)packages/prisma/client.ts (1)
NotificationEmailType
(14-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (8)
packages/prisma/schema/notification.prisma (1)
3-3
: Enum addition LGTM.New NotificationEmailType.Bounty is consistent with downstream usage.
apps/web/app/(ee)/api/bounties/[bountyId]/route.ts (1)
227-227
: Propagating userId to cron: good.Matches the cron’s schema (userId nullish) and preserves actor context.
Confirm the PATCH flow’s qstash trigger runs only when startsAt changes (guarded by the timestamp inequality) as intended for idempotency.
apps/web/app/(ee)/api/bounties/route.ts (1)
256-257
: Include userId in creation notify payload: good.Consistent with the update flow and cron handler.
apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts (5)
1-1
: Import createId: OK.Required for deterministic message IDs.
9-9
: Using NotificationEmailType from Prisma: OK.Enum aligns with schema changes.
18-20
: Body schema accepts userId nullish: OK.Allows defaulting to workspace owner in handler.
126-148
: Batch email payload LGTM.Idempotency-Key per page and template inputs look correct.
If Resend isn’t configured and SMTP fallback is used, sendBatchEmail returns { data: null }. Decide whether you still want to persist Message rows in that case; see next comment.
178-179
: Propagate userId to next page: good.Preserves actor context across pagination.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts (2)
132-154
: Handle email provider errors explicitly.Capture and check error from sendBatchEmail.
Apply:
-const { data } = await sendBatchEmail( +const { data, error } = await sendBatchEmail( programEnrollments.map(({ partner }) => ({ variant: "notifications", to: partner.email!, subject: `New bounty available for ${bounty.program.name}`, react: NewBountyAvailable({ email: partner.email!, bounty: { name: bounty.name, type: bounty.type, endsAt: bounty.endsAt, description: bounty.description, }, program: { name: bounty.program.name, slug: bounty.program.slug, }, }), headers: { "Idempotency-Key": `${bountyId}-page-${page}`, }, })), ); +if (error) { + throw new Error( + `Error sending bounty emails for bounty ${bountyId}: ${error.message}`, + ); +}
1-196
: PR objective vs implementation: Messages not persisted.The PR title/objective mentions persisting Bounties emails to Message + NotificationEmail. This route only writes NotificationEmail and never creates Message records.
I can propose a transactional insert to create Message per partner (with sender fallback to workspace owner or provided userId) and then NotificationEmail referencing those messageIds. Want me to draft it?
🧹 Nitpick comments (7)
packages/prisma/schema/notification.prisma (2)
10-16
: Consider making emailId optional or model provider info for SMTP.With SMTP fallback, sendBatchEmail returns data=null; current routes either skip persistence or throw. If you want consistent persistence, make emailId optional and add provider/providerMessageId fields, or persist records without provider message IDs.
Example schema adjustment (outside current diff context):
enum NotificationProvider { Resend SMTP } model NotificationEmail { id String @id @default(cuid()) provider NotificationProvider @default(Resend) emailId String? // provider message id (nullable for SMTP) // ... }
10-16
: Index new lookup fields for expected queries.If you’ll filter by bountyId/programId/partnerId/recipientUserId, add indexes.
Outside this hunk, append:
@@index([bountyId]) @@index([programId]) @@index([partnerId]) @@index([recipientUserId])apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts (3)
44-60
: Unneeded include increases query cost.workspace.owner userId is fetched but unused. Remove unless you’ll use it for sender attribution when persisting Messages.
156-168
: Guard against partial responses; skip duplicates.Prevent mis-associating email IDs if provider returns fewer items. Also enable skipDuplicates (add a unique constraint on emailId to make it effective).
Apply:
-if (data) { - await prisma.notificationEmail.createMany({ - data: programEnrollments.map(({ partner }, idx) => ({ - id: createId({ prefix: "em_" }), - type: NotificationEmailType.Bounty, - emailId: data.data[idx].id, - bountyId: bounty.id, - programId: bounty.programId, - partnerId: partner.id, - recipientUserId: partner.users[0].userId, // TODO: update this to use partnerUsersToNotify approach - })), - }); -} +if (data?.data?.length) { + if (data.data.length !== programEnrollments.length) { + console.warn( + `Partial email response for bounty ${bountyId}: expected ${programEnrollments.length}, got ${data.data.length}. Skipping persistence to avoid mis-association.`, + ); + } else { + await prisma.notificationEmail.createMany({ + data: programEnrollments.map(({ partner }, idx) => ({ + id: createId({ prefix: "em_" }), + type: NotificationEmailType.Bounty, + emailId: data.data[idx].id, + bountyId: bounty.id, + programId: bounty.programId, + partnerId: partner.id, + recipientUserId: partner.users[0].userId, // TODO: update this to use partnerUsersToNotify approach + })), + skipDuplicates: true, + }); + } +}
9-10
: Unify Prisma import path.Other routes import NotificationEmailType from @dub/prisma/client. Prefer consistency to avoid multiple client instances in bundlers.
-import { NotificationEmailType } from "@prisma/client"; +import { NotificationEmailType } from "@dub/prisma/client";apps/web/app/(ee)/api/cron/messages/notify-program/route.ts (1)
141-156
: Gracefully handle SMTP fallback and partial responses.Avoid throwing on data=null; persist only when provider returns IDs; guard for length mismatch; skip duplicates.
Apply:
-if (!data) - throw new Error( - `No data received from sending message emails to program ${programId} users`, - ); - -await prisma.notificationEmail.createMany({ - data: usersToNotify.map(({ id: userId }, idx) => ({ - id: createId({ prefix: "em_" }), - type: NotificationEmailType.Message, - emailId: data.data[idx].id, - messageId: lastMessageId, - programId, - partnerId, - recipientUserId: userId, - })), -}); +if (data?.data?.length) { + if (data.data.length !== usersToNotify.length) { + console.warn( + `Partial email response for program ${programId}: expected ${usersToNotify.length}, got ${data.data.length}. Skipping persistence to avoid mis-association.`, + ); + } else { + await prisma.notificationEmail.createMany({ + data: usersToNotify.map(({ id: userId }, idx) => ({ + id: createId({ prefix: "em_" }), + type: NotificationEmailType.Message, + emailId: data.data[idx].id, + messageId: lastMessageId, + programId, + partnerId, + recipientUserId: userId, + })), + skipDuplicates: true, + }); + } +}apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts (1)
140-156
: Gracefully handle SMTP fallback and partial responses.Don’t throw when data is null; persist only when provider returns IDs; guard for length mismatch; skip duplicates.
Apply:
-if (!data) - throw new Error( - `No data received from sending message emails to partner ${partnerId}`, - ); - -await prisma.notificationEmail.createMany({ - data: partnerUsersToNotify.map(({ id: userId }, idx) => ({ - id: createId({ prefix: "em_" }), - type: NotificationEmailType.Message, - emailId: data.data[idx].id, - messageId: lastMessageId, - programId, - partnerId, - recipientUserId: userId, - })), -}); +if (data?.data?.length) { + if (data.data.length !== partnerUsersToNotify.length) { + console.warn( + `Partial email response for partner ${partnerId}: expected ${partnerUsersToNotify.length}, got ${data.data.length}. Skipping persistence to avoid mis-association.`, + ); + } else { + await prisma.notificationEmail.createMany({ + data: partnerUsersToNotify.map(({ id: userId }, idx) => ({ + id: createId({ prefix: "em_" }), + type: NotificationEmailType.Message, + emailId: data.data[idx].id, + messageId: lastMessageId, + programId, + partnerId, + recipientUserId: userId, + })), + skipDuplicates: true, + }); + } +}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts
(7 hunks)apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts
(3 hunks)apps/web/app/(ee)/api/cron/messages/notify-program/route.ts
(3 hunks)apps/web/lib/api/create-id.ts
(1 hunks)packages/prisma/schema/notification.prisma
(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: TWilson023
PR: dubinc/dub#2736
File: apps/web/app/api/workspaces/[idOrSlug]/notification-preferences/route.ts:13-14
Timestamp: 2025-08-26T14:20:23.943Z
Learning: The updateNotificationPreference action in apps/web/lib/actions/update-notification-preference.ts already handles all notification preference types dynamically, including newBountySubmitted, through its schema validation using the notificationTypes enum and Prisma's dynamic field update pattern.
📚 Learning: 2025-08-26T14:20:23.943Z
Learnt from: TWilson023
PR: dubinc/dub#2736
File: apps/web/app/api/workspaces/[idOrSlug]/notification-preferences/route.ts:13-14
Timestamp: 2025-08-26T14:20:23.943Z
Learning: The updateNotificationPreference action in apps/web/lib/actions/update-notification-preference.ts already handles all notification preference types dynamically, including newBountySubmitted, through its schema validation using the notificationTypes enum and Prisma's dynamic field update pattern.
Applied to files:
apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts
packages/prisma/schema/notification.prisma
🧬 Code graph analysis (3)
apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts (3)
packages/email/src/index.ts (1)
sendBatchEmail
(32-70)apps/web/lib/api/create-id.ts (1)
createId
(63-68)packages/prisma/client.ts (1)
NotificationEmailType
(14-14)
apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts (4)
apps/web/app/(ee)/api/cron/utils.ts (1)
logAndRespond
(1-13)packages/email/src/index.ts (1)
sendBatchEmail
(32-70)apps/web/lib/api/create-id.ts (1)
createId
(63-68)packages/prisma/client.ts (1)
NotificationEmailType
(14-14)
apps/web/app/(ee)/api/cron/messages/notify-program/route.ts (3)
apps/web/app/(ee)/api/cron/utils.ts (1)
logAndRespond
(1-13)packages/email/src/index.ts (1)
sendBatchEmail
(32-70)apps/web/lib/api/create-id.ts (1)
createId
(63-68)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (4)
apps/web/lib/api/create-id.ts (1)
37-38
: Add "em_" prefix looks good.Prefix union stays type-safe and aligns with NotificationEmail IDs usage.
packages/prisma/schema/notification.prisma (1)
3-4
: Enum addition is fine.Adding Bounty to NotificationEmailType is backward-compatible for existing values.
apps/web/app/(ee)/api/cron/messages/notify-program/route.ts (1)
103-106
: User list derivation looks good.Maps to concrete user objects and filters nulls.
apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts (1)
69-74
: Match query filter to model field name
Search the Prisma schema for the exact field (notificationPreference
vsnotificationPreferences
) and update thewhere
clause in apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts (lines 69–74) to use the correct field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts
(4 hunks)apps/web/app/(ee)/api/cron/messages/notify-program/route.ts
(4 hunks)apps/web/app/api/resend/webhook/email-opened.ts
(1 hunks)apps/web/lib/actions/partners/mark-partner-messages-read.ts
(1 hunks)apps/web/lib/actions/partners/mark-program-messages-read.ts
(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-17T17:44:03.965Z
Learnt from: TWilson023
PR: dubinc/dub#2857
File: apps/web/lib/actions/partners/update-program.ts:96-0
Timestamp: 2025-09-17T17:44:03.965Z
Learning: In apps/web/lib/actions/partners/update-program.ts, the team prefers to keep the messagingEnabledAt update logic simple by allowing client-provided timestamps rather than implementing server-controlled timestamp logic to avoid added complexity.
Applied to files:
apps/web/lib/actions/partners/mark-program-messages-read.ts
apps/web/lib/actions/partners/mark-partner-messages-read.ts
🧬 Code graph analysis (2)
apps/web/app/(ee)/api/cron/messages/notify-program/route.ts (4)
apps/web/app/(ee)/api/cron/utils.ts (1)
logAndRespond
(1-13)packages/email/src/index.ts (1)
sendBatchEmail
(32-70)apps/web/lib/api/create-id.ts (1)
createId
(63-68)packages/prisma/client.ts (1)
NotificationEmailType
(16-16)
apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts (4)
apps/web/app/(ee)/api/cron/utils.ts (1)
logAndRespond
(1-13)packages/email/src/index.ts (1)
sendBatchEmail
(32-70)apps/web/lib/api/create-id.ts (1)
createId
(63-68)packages/prisma/client.ts (1)
NotificationEmailType
(16-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (4)
apps/web/lib/actions/partners/mark-partner-messages-read.ts (1)
24-33
: Good call on gating byreadInApp
.Filtering on
readInApp: null
keeps the update idempotent and avoids nuking existing read timestamps. Nice alignment with the program-side action.apps/web/lib/actions/partners/mark-program-messages-read.ts (1)
25-34
: Appreciate the symmetry with partner reads.Adding the
readInApp: null
guard keeps this action consistent with the partner variant and prevents re-stamping already read messages.apps/web/app/(ee)/api/cron/messages/notify-program/route.ts (1)
103-156
: Per-recipient records look solid.Love the shift to
usersToNotify
and the one-to-onenotificationEmail
inserts with generated IDs—this will make downstream correlation so much cleaner.apps/web/app/(ee)/api/cron/messages/notify-partner/route.ts (1)
94-155
: Nice mirror of the program-side flow.The per-user batching plus
createId
usage keeps the partner notifications consistent with the program cron and sets us up for reliable webhook handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts
(4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-26T14:20:23.943Z
Learnt from: TWilson023
PR: dubinc/dub#2736
File: apps/web/app/api/workspaces/[idOrSlug]/notification-preferences/route.ts:13-14
Timestamp: 2025-08-26T14:20:23.943Z
Learning: The updateNotificationPreference action in apps/web/lib/actions/update-notification-preference.ts already handles all notification preference types dynamically, including newBountySubmitted, through its schema validation using the notificationTypes enum and Prisma's dynamic field update pattern.
Applied to files:
apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts
🧬 Code graph analysis (1)
apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts (3)
packages/email/src/index.ts (1)
sendBatchEmail
(32-70)apps/web/lib/api/create-id.ts (1)
createId
(63-68)packages/prisma/client.ts (1)
NotificationEmailType
(16-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (1)
apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts (1)
112-114
: Remove raw partner email addresses from logs.Dumping individual partner emails into logs is PII leakage and violates our logging guidelines. Please log only aggregate counts/ids instead.
Apply this diff:
- console.log( - `Sending emails to ${programEnrollments.length} partners: ${programEnrollments.map(({ partner }) => partner.email).join(", ")}`, - ); + console.log( + `Sending emails to ${programEnrollments.length} partners.`, + );
if (data) { | ||
await prisma.notificationEmail.createMany({ | ||
data: programEnrollments.map(({ partner }, idx) => ({ | ||
id: createId({ prefix: "em_" }), | ||
type: NotificationEmailType.Bounty, | ||
emailId: data.data[idx].id, | ||
bountyId: bounty.id, | ||
programId: bounty.programId, | ||
partnerId: partner.id, | ||
recipientUserId: partner.users[0].userId, // TODO: update this to use partnerUsersToNotify approach | ||
})), | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against partial Resend responses before indexing data.data[idx]
.
Resend’s batch API can return fewer entries or items with { error }
instead of { id }
. When that happens, data.data[idx]
is undefined
, so the current loop throws (can't read .id
) and the whole cron crashes, skipping all remaining notifications. We need to skip errored recipients (and log them) before persisting NotificationEmail rows.
Apply this diff:
- if (data) {
- await prisma.notificationEmail.createMany({
- data: programEnrollments.map(({ partner }, idx) => ({
- id: createId({ prefix: "em_" }),
- type: NotificationEmailType.Bounty,
- emailId: data.data[idx].id,
- bountyId: bounty.id,
- programId: bounty.programId,
- partnerId: partner.id,
- recipientUserId: partner.users[0].userId, // TODO: update this to use partnerUsersToNotify approach
- })),
- });
- }
+ if (data?.data?.length) {
+ const notificationEmails = [];
+
+ for (const [idx, enrollment] of programEnrollments.entries()) {
+ const emailResponse = data.data[idx];
+
+ if (!emailResponse?.id) {
+ await log({
+ message: `Resend did not return an id for partner ${enrollment.partner.id}; skipping NotificationEmail persistence.`,
+ type: "errors",
+ });
+ continue;
+ }
+
+ notificationEmails.push({
+ id: createId({ prefix: "em_" }),
+ type: NotificationEmailType.Bounty,
+ emailId: emailResponse.id,
+ bountyId: bounty.id,
+ programId: bounty.programId,
+ partnerId: enrollment.partner.id,
+ recipientUserId: enrollment.partner.users[0].userId, // TODO: update this to use partnerUsersToNotify approach
+ });
+ }
+
+ if (notificationEmails.length) {
+ await prisma.notificationEmail.createMany({
+ data: notificationEmails,
+ });
+ }
+ }
🤖 Prompt for AI Agents
In apps/web/app/(ee)/api/cron/bounties/notify-partners/route.ts around lines 139
to 151, guard against partial or errored Resend batch responses by checking
data.data for a valid entry with an id before indexing data.data[idx];
filter/map programEnrollments to only those where data.data[idx] exists and has
an id, log any missing/errored entries with context (partner id, idx, and any
error payload) and then call prisma.notificationEmail.createMany with the
filtered list; ensure recipientUserId remains populated as before and that
skipped recipients are not persisted so the cron does not crash on undefined
entries.
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Chores