fix: Enhance email bounce notification with latest bounce details
This commit is contained in:
@@ -229,7 +229,7 @@ export class NtfyService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify about email bounce - LOW priority (high volume)
|
* Notify about email bounce - LOW priority (high volume)
|
||||||
* Rate-limited to only send notification every 20 bounces with per-project breakdown
|
* Rate-limited to only send notification every 20 bounces with latest 20 bounce details
|
||||||
*/
|
*/
|
||||||
public static async notifyEmailBounce(
|
public static async notifyEmailBounce(
|
||||||
projectName: string,
|
projectName: string,
|
||||||
@@ -240,63 +240,51 @@ export class NtfyService {
|
|||||||
// Import redis at runtime to avoid circular dependencies
|
// Import redis at runtime to avoid circular dependencies
|
||||||
const {redis} = await import('../database/redis.js');
|
const {redis} = await import('../database/redis.js');
|
||||||
|
|
||||||
// Use Redis counters to track bounce count globally and per-project
|
// Use Redis counters to track bounce count globally
|
||||||
const globalCountKey = `ntfy:bounce:count`;
|
const globalCountKey = `ntfy:bounce:count`;
|
||||||
const projectCountKey = `ntfy:bounce:count:${projectId}`;
|
const bounceListKey = `ntfy:bounce:latest`;
|
||||||
const projectListKey = `ntfy:bounce:projects`;
|
|
||||||
|
|
||||||
// Increment global counter
|
// Increment global counter
|
||||||
const globalCount = await redis.incr(globalCountKey);
|
const globalCount = await redis.incr(globalCountKey);
|
||||||
|
|
||||||
// Increment project-specific counter
|
// Store this bounce event in a list (keep latest 20)
|
||||||
await redis.incr(projectCountKey);
|
const bounceEvent = JSON.stringify({
|
||||||
|
projectName,
|
||||||
// Add project to the set of projects with bounces (for tracking)
|
projectId,
|
||||||
await redis.sadd(projectListKey, projectId);
|
recipientEmail,
|
||||||
|
bounceType: bounceType || 'Unknown',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
await redis.lpush(bounceListKey, bounceEvent);
|
||||||
|
await redis.ltrim(bounceListKey, 0, 19); // Keep only latest 20
|
||||||
|
|
||||||
// Set expiry on first increment (24 hour rolling window)
|
// Set expiry on first increment (24 hour rolling window)
|
||||||
if (globalCount === 1) {
|
if (globalCount === 1) {
|
||||||
await redis.expire(globalCountKey, 86400);
|
await redis.expire(globalCountKey, 86400);
|
||||||
await redis.expire(projectListKey, 86400);
|
await redis.expire(bounceListKey, 86400);
|
||||||
}
|
}
|
||||||
// Always refresh project counter expiry to match global window
|
|
||||||
await redis.expire(projectCountKey, 86400);
|
|
||||||
|
|
||||||
// Only send notification every 20 bounces
|
// Only send notification every 20 bounces
|
||||||
if (globalCount % 20 === 0) {
|
if (globalCount % 20 === 0) {
|
||||||
// Get all projects with bounces
|
// Get the latest 20 bounces
|
||||||
const projectIds = await redis.smembers(projectListKey);
|
const latestBounces = await redis.lrange(bounceListKey, 0, 19);
|
||||||
|
|
||||||
// Get bounce counts for each project
|
// Parse and format the bounce events
|
||||||
const projectCounts: Array<{projectId: string; count: number; name: string}> = [];
|
const bounceDetails = latestBounces
|
||||||
for (const pid of projectIds) {
|
.map(bounce => {
|
||||||
const count = await redis.get(`ntfy:bounce:count:${pid}`);
|
try {
|
||||||
if (count) {
|
const parsed = JSON.parse(bounce);
|
||||||
// Fetch project name from database
|
return ` • ${parsed.recipientEmail} (${parsed.bounceType}) - ${parsed.projectName}`;
|
||||||
const {prisma} = await import('../database/prisma.js');
|
} catch {
|
||||||
const project = await prisma.project.findUnique({
|
return null;
|
||||||
where: {id: pid},
|
}
|
||||||
select: {name: true},
|
})
|
||||||
});
|
.filter(Boolean)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
projectCounts.push({
|
|
||||||
projectId: pid,
|
|
||||||
count: parseInt(count, 10),
|
|
||||||
name: project?.name || 'Unknown',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by count descending
|
|
||||||
projectCounts.sort((a, b) => b.count - a.count);
|
|
||||||
|
|
||||||
// Build breakdown message
|
|
||||||
const breakdown = projectCounts.map(p => ` • ${p.name} (${p.projectId}): ${p.count}`).join('\n');
|
|
||||||
|
|
||||||
const bounceInfo = bounceType ? ` (${bounceType})` : '';
|
|
||||||
await this.send({
|
await this.send({
|
||||||
title: 'Email Bounces',
|
title: 'Email Bounces',
|
||||||
message: `20 email bounces detected (total: ${globalCount})\n\nBreakdown by project:\n${breakdown}\n\nLatest: ${recipientEmail}${bounceInfo} in "${projectName}"`,
|
message: `20 email bounces detected (total: ${globalCount})\n\nLatest 20 bounces:\n${bounceDetails}`,
|
||||||
priority: NtfyPriority.LOW,
|
priority: NtfyPriority.LOW,
|
||||||
tags: [NtfyTag.WARNING],
|
tags: [NtfyTag.WARNING],
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user