fix: batch email sending in message system to avoid overloading SMTP
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m27s

Messages were sent in a tight for-loop with no throttling. Now uses
sendBatchNotifications (10/batch, 150ms per email, 500ms between
batches) and fires in the background so the admin gets instant response.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 16:07:52 +01:00
parent 461551b489
commit 67670472f7

View File

@@ -3,6 +3,8 @@ import { TRPCError } from '@trpc/server'
import { router, protectedProcedure, adminProcedure } from '../trpc'
import { logAudit } from '@/server/utils/audit'
import { sendStyledNotificationEmail, getEmailPreviewHtml } from '@/lib/email'
import { sendBatchNotifications } from '../services/notification-sender'
import type { NotificationItem } from '../services/notification-sender'
export const messageRouter = router({
/**
@@ -68,7 +70,7 @@ export const messageRouter = router({
},
})
// If not scheduled, deliver immediately for EMAIL channel
// If not scheduled, deliver immediately for EMAIL channel (batched to avoid overloading SMTP)
if (!isScheduled && input.deliveryChannels.includes('EMAIL')) {
const users = await ctx.prisma.user.findMany({
where: { id: { in: recipientUserIds } },
@@ -77,23 +79,25 @@ export const messageRouter = router({
const baseUrl = process.env.NEXTAUTH_URL || 'https://portal.monaco-opc.com'
for (const user of users) {
try {
await sendStyledNotificationEmail(
user.email,
user.name || '',
'MESSAGE',
{
name: user.name || undefined,
title: input.subject,
message: input.body,
linkUrl: `${baseUrl}/messages`,
}
)
} catch (error) {
console.error(`[Message] Failed to send email to ${user.email}:`, error)
}
}
const items: NotificationItem[] = users.map((user) => ({
email: user.email,
name: user.name || '',
type: 'MESSAGE',
userId: user.id,
context: {
name: user.name || undefined,
title: input.subject,
message: input.body,
linkUrl: `${baseUrl}/messages`,
},
}))
// Fire-and-forget: batch send in background so the mutation returns quickly
sendBatchNotifications(items).then((result) => {
console.log(`[Message] Batch ${result.batchId}: ${result.sent} sent, ${result.failed} failed`)
}).catch((err) => {
console.error('[Message] Batch send error:', err)
})
}
try {