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 { router, protectedProcedure, adminProcedure } from '../trpc'
import { logAudit } from '@/server/utils/audit' import { logAudit } from '@/server/utils/audit'
import { sendStyledNotificationEmail, getEmailPreviewHtml } from '@/lib/email' import { sendStyledNotificationEmail, getEmailPreviewHtml } from '@/lib/email'
import { sendBatchNotifications } from '../services/notification-sender'
import type { NotificationItem } from '../services/notification-sender'
export const messageRouter = router({ 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')) { if (!isScheduled && input.deliveryChannels.includes('EMAIL')) {
const users = await ctx.prisma.user.findMany({ const users = await ctx.prisma.user.findMany({
where: { id: { in: recipientUserIds } }, where: { id: { in: recipientUserIds } },
@@ -77,23 +79,25 @@ export const messageRouter = router({
const baseUrl = process.env.NEXTAUTH_URL || 'https://portal.monaco-opc.com' const baseUrl = process.env.NEXTAUTH_URL || 'https://portal.monaco-opc.com'
for (const user of users) { const items: NotificationItem[] = users.map((user) => ({
try { email: user.email,
await sendStyledNotificationEmail( name: user.name || '',
user.email, type: 'MESSAGE',
user.name || '', userId: user.id,
'MESSAGE', context: {
{ name: user.name || undefined,
name: user.name || undefined, title: input.subject,
title: input.subject, message: input.body,
message: input.body, linkUrl: `${baseUrl}/messages`,
linkUrl: `${baseUrl}/messages`, },
} }))
)
} catch (error) { // Fire-and-forget: batch send in background so the mutation returns quickly
console.error(`[Message] Failed to send email to ${user.email}:`, error) 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 { try {