From ee00eb34811270473d1f79729853eaada883a477 Mon Sep 17 00:00:00 2001 From: Dries Augustyns Date: Sat, 3 Jan 2026 11:18:44 +0100 Subject: [PATCH] fix: Update sentCount on campaign sent for correct overview stats --- apps/api/src/jobs/email-processor.ts | 1 + apps/api/src/services/CampaignService.ts | 1 + apps/api/src/services/EmailService.ts | 36 ++++++++++++++++++++++++ apps/web/src/pages/campaigns/index.tsx | 31 ++++++++++++++++++-- 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/apps/api/src/jobs/email-processor.ts b/apps/api/src/jobs/email-processor.ts index 537dfc8..22af89a 100644 --- a/apps/api/src/jobs/email-processor.ts +++ b/apps/api/src/jobs/email-processor.ts @@ -204,6 +204,7 @@ export async function createEmailWorker() { where: {id: email.campaignId}, data: { status: CampaignStatus.SENT, + sentCount, }, }); diff --git a/apps/api/src/services/CampaignService.ts b/apps/api/src/services/CampaignService.ts index 077e0c9..75a1e03 100644 --- a/apps/api/src/services/CampaignService.ts +++ b/apps/api/src/services/CampaignService.ts @@ -564,6 +564,7 @@ export class CampaignService { await prisma.campaign.update({ where: {id: campaignId}, data: { + sentCount: sentEmails, deliveredCount: deliveredEmails, openedCount: openedEmails, clickedCount: clickedEmails, diff --git a/apps/api/src/services/EmailService.ts b/apps/api/src/services/EmailService.ts index 24ce916..486d854 100644 --- a/apps/api/src/services/EmailService.ts +++ b/apps/api/src/services/EmailService.ts @@ -503,6 +503,42 @@ export class EmailService { data: updateData, }); + // Update campaign stats if applicable + if (email.campaignId) { + const campaignUpdate: Prisma.CampaignUpdateInput = {}; + + switch (eventType) { + case 'delivered': + campaignUpdate.deliveredCount = {increment: 1}; + break; + + case 'opened': + // Only increment unique opens to match getStats logic + if (!email.openedAt) { + campaignUpdate.openedCount = {increment: 1}; + } + break; + + case 'clicked': + // Only increment unique clicks to match getStats logic + if (!email.clickedAt) { + campaignUpdate.clickedCount = {increment: 1}; + } + break; + + case 'bounced': + campaignUpdate.bouncedCount = {increment: 1}; + break; + } + + if (Object.keys(campaignUpdate).length > 0) { + await prisma.campaign.update({ + where: {id: email.campaignId}, + data: campaignUpdate, + }); + } + } + // Track event await prisma.event.create({ data: { diff --git a/apps/web/src/pages/campaigns/index.tsx b/apps/web/src/pages/campaigns/index.tsx index ae1b17a..8c2443d 100644 --- a/apps/web/src/pages/campaigns/index.tsx +++ b/apps/web/src/pages/campaigns/index.tsx @@ -25,7 +25,20 @@ import {TemplateSelectionDialog} from '../../components/TemplateSelectionDialog' import {CampaignSelectionDialog} from '../../components/CampaignSelectionDialog'; import {network} from '../../lib/network'; import {formatRelativeTime} from '../../lib/dateUtils'; -import {Calendar, ChevronDown, Copy, FileText, Info, Mail, Plus, RefreshCw, Trash2, Users} from 'lucide-react'; +import { + AlertCircle, + Calendar, + ChevronDown, + Copy, + FileText, + Info, + Mail, + MousePointerClick, + Plus, + RefreshCw, + Trash2, + Users, +} from 'lucide-react'; import {NextSeo} from 'next-seo'; import Link from 'next/link'; import {useRouter} from 'next/router'; @@ -426,7 +439,7 @@ export default function CampaignsPage() { {campaign.clickedCount > 0 && (
- + Clicks

{clickRate.toFixed(1)}%

@@ -436,6 +449,20 @@ export default function CampaignsPage() {
)} + {/* Bounced Rate */} + {campaign.bouncedCount > 0 && campaign.sentCount > 0 && ( +
+
+ + Bounced +
+

+ {((campaign.bouncedCount / campaign.sentCount) * 100).toFixed(1)}% +

+

{campaign.bouncedCount.toLocaleString()} bounced

+
+ )} + {/* Scheduled For */} {campaign.scheduledFor && (