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
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:
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user