Files
MOPC-Portal/src/server/services/notification-sender.ts

98 lines
2.8 KiB
TypeScript
Raw Normal View History

import { randomUUID } from 'crypto'
import { prisma } from '@/lib/prisma'
import { sendStyledNotificationEmail, emailDelay } from '@/lib/email'
import type { NotificationEmailContext } from '@/lib/email'
export type NotificationItem = {
email: string
name: string
type: string // ADVANCEMENT_NOTIFICATION, REJECTION_NOTIFICATION, etc.
context: NotificationEmailContext
projectId?: string
userId?: string
roundId?: string
}
export type BatchResult = {
sent: number
failed: number
batchId: string
errors: Array<{ email: string; error: string }>
}
/**
* Send notifications in batches with throttling and per-email logging.
* Each email is logged to NotificationLog with SENT or FAILED status.
*/
export async function sendBatchNotifications(
items: NotificationItem[],
options?: { batchSize?: number; batchDelayMs?: number }
): Promise<BatchResult> {
const batchId = randomUUID()
const batchSize = options?.batchSize ?? 10
const batchDelayMs = options?.batchDelayMs ?? 500
let sent = 0
let failed = 0
const errors: Array<{ email: string; error: string }> = []
for (let i = 0; i < items.length; i += batchSize) {
const chunk = items.slice(i, i + batchSize)
for (const item of chunk) {
try {
await sendStyledNotificationEmail(
item.email,
item.name,
item.type,
item.context,
)
sent++
// Log success (fire-and-forget)
prisma.notificationLog.create({
data: {
userId: item.userId || null,
channel: 'EMAIL',
type: item.type,
status: 'SENT',
email: item.email,
roundId: item.roundId || null,
projectId: item.projectId || null,
batchId,
},
}).catch((err) => console.error('[notification-sender] Log write failed:', err))
} catch (err) {
const errorMsg = err instanceof Error ? err.message : String(err)
failed++
errors.push({ email: item.email, error: errorMsg })
console.error(`[notification-sender] Failed for ${item.email}:`, err)
// Log failure (fire-and-forget)
prisma.notificationLog.create({
data: {
userId: item.userId || null,
channel: 'EMAIL',
type: item.type,
status: 'FAILED',
email: item.email,
roundId: item.roundId || null,
projectId: item.projectId || null,
batchId,
errorMsg,
},
}).catch((logErr) => console.error('[notification-sender] Log write failed:', logErr))
}
await emailDelay()
}
// Delay between chunks to avoid overwhelming SMTP
if (i + batchSize < items.length) {
await new Promise((resolve) => setTimeout(resolve, batchDelayMs))
}
}
return { sent, failed, batchId, errors }
}