fix(mentor): defer in-app-notification emails when mentoring round is draft
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m14s
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m14s
Mentor-assignment flows (mentor.assign, autoAssign, bulkAssign, bulkAutoAssign, autoAssignBulkForRound) call createNotification and notifyProjectTeam for MENTEE_ASSIGNED / MENTOR_ASSIGNED. Both notification types have NotificationEmailSetting.sendEmail = true, so the notification system fires its own styled email in addition to the explicit mentor-team / coalesced emails on the same code path. The earlier defer-emails-until-round-open fix only gated the explicit sendMentorBulkAssignmentEmail / sendMentorTeamAssignmentEmail calls; this parallel email path kept firing immediately at every assignment. Result on prod 2026-05-26: Camille Lopez (assigned to 9 projects via two bulk_assigns) received 7 emails at 15:04 + 1 at 15:32 from the notification-system path during draft, plus 1 coalesced email at the 18:20 round activation = 9 sends instead of 1. Every PEARL team member (and equivalents on other teams) received 3 emails for the same reason. Fix - Add `skipEmail?: boolean` to CreateNotificationParams, createNotification, createBulkNotifications, and (via spread) notifyProjectTeam. When true the in-app notification row still fires but the parallel email send is suppressed; the coalesced mentor email and team intro at activateRound time remain the single source of email truth. - Wire it up in every mentor-assignment site: compute the existing shouldDeferEmailsForProject gate once before the createNotification / notifyProjectTeam calls and pass `skipEmail: deferThisEmail`. bulkAssign precomputes draftProjectIds for the whole batch. autoAssignBulkForRound uses the round's status directly. - New regression suite (mentor-email-deferral.test.ts, 3 cases): vi.mocks @/lib/email, asserts zero outbound sends when round is ROUND_DRAFT, confirms in-app notification rows still get written, and re-verifies the ACTIVE-round path still emails. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -169,6 +169,14 @@ interface CreateNotificationParams {
|
||||
metadata?: Record<string, unknown>
|
||||
groupKey?: string
|
||||
expiresAt?: Date
|
||||
/**
|
||||
* When true, the in-app notification still fires but the parallel email
|
||||
* send (via NotificationEmailSetting) is suppressed. Callers use this when
|
||||
* the email belongs to a coalesced/deferred flow that will fire later
|
||||
* (e.g. mentor assignments staged while a MENTORING round is ROUND_DRAFT —
|
||||
* the round-open hook sends a single combined email instead).
|
||||
*/
|
||||
skipEmail?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,6 +197,7 @@ export async function createNotification(
|
||||
metadata,
|
||||
groupKey,
|
||||
expiresAt,
|
||||
skipEmail,
|
||||
} = params
|
||||
|
||||
// Determine icon and priority if not provided
|
||||
@@ -241,8 +250,11 @@ export async function createNotification(
|
||||
},
|
||||
})
|
||||
|
||||
// Check if we should also send an email
|
||||
await maybeSendEmail(userId, type, title, message, linkUrl, metadata)
|
||||
// Check if we should also send an email (suppressed when the caller is
|
||||
// deferring the email to a coalesced flow).
|
||||
if (!skipEmail) {
|
||||
await maybeSendEmail(userId, type, title, message, linkUrl, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,6 +270,8 @@ export async function createBulkNotifications(params: {
|
||||
icon?: string
|
||||
priority?: NotificationPriority
|
||||
metadata?: Record<string, unknown>
|
||||
/** See {@link CreateNotificationParams.skipEmail}. */
|
||||
skipEmail?: boolean
|
||||
}): Promise<void> {
|
||||
const {
|
||||
userIds,
|
||||
@@ -269,6 +283,7 @@ export async function createBulkNotifications(params: {
|
||||
icon,
|
||||
priority,
|
||||
metadata,
|
||||
skipEmail,
|
||||
} = params
|
||||
|
||||
const finalIcon = icon || NotificationIcons[type] || 'Bell'
|
||||
@@ -289,6 +304,8 @@ export async function createBulkNotifications(params: {
|
||||
})),
|
||||
})
|
||||
|
||||
if (skipEmail) return
|
||||
|
||||
// Check email settings once, then send emails only if enabled
|
||||
const emailSetting = await prisma.notificationEmailSetting.findUnique({
|
||||
where: { notificationType: type },
|
||||
|
||||
Reference in New Issue
Block a user