Skip to content

fix: deduplicate dashboard engagement usage#425

Merged
KMKoushik merged 1 commit into
mainfrom
codex/fix-dashboard-usage-counts
Jul 13, 2026
Merged

fix: deduplicate dashboard engagement usage#425
KMKoushik merged 1 commit into
mainfrom
codex/fix-dashboard-usage-counts

Conversation

@KMKoushik

@KMKoushik KMKoushik commented Jul 13, 2026

Copy link
Copy Markdown
Member

Summary

  • check existing email events before updating dashboard engagement usage
  • count only the first open and first click per email while preserving the full event history
  • reuse the same event lookup for campaign analytics
  • add regression coverage for duplicate open and click webhooks

Root cause

The SES webhook parser incremented DailyEmailUsage before checking whether an event with the same email and status already existed. Campaign analytics performed this deduplication, but dashboard usage did not, so repeated open and click webhooks inflated dashboard totals.

Verification

  • eslint src/server/service/ses-hook-parser.ts src/server/service/ses-hook-parser.unit.test.ts --max-warnings 0
  • focused unit tests: 2 passed
  • complete web unit suite: 113 passed
  • git diff --check

Migration notes

No database migration required.

Closes #101


Summary by cubic

Fixes inflated dashboard engagement metrics by deduplicating SES open/click events. Only the first open and first click per email increment usage; the same event lookup is reused for campaign analytics to keep counts consistent.

  • Bug Fixes
    • Check for an existing email event before updating DailyEmailUsage for OPENED/CLICKED.
    • Keep full event history but ignore duplicate opens/clicks for usage totals.
    • Add regression tests for duplicate open/click webhooks.

Written for commit 90f205c. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Prevented duplicate engagement events from inflating daily email usage counters.
    • Improved campaign analytics handling for repeated email opens and clicks.
  • Tests

    • Added coverage for repeated open and click events.
    • Verified that engagement usage is counted once while event records continue to be captured.

@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
unsend-marketing Ready Ready Preview, Comment Jul 13, 2026 12:20am

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 13, 2026

Copy link
Copy Markdown

Deploying usesend with  Cloudflare Pages  Cloudflare Pages

Latest commit: 90f205c
Status: ✅  Deploy successful!
Preview URL: https://99b8f676.usesend.pages.dev
Branch Preview URL: https://codex-fix-dashboard-usage-co.usesend.pages.dev

View logs

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

parseSesHook now detects Open and Click engagement events, checks for an existing matching email event, and prevents duplicate daily usage and campaign analytics processing. A new Vitest suite mocks parser dependencies and verifies lookup behavior, single daily usage updates, and repeated email event creation for both engagement types.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: deduplicating dashboard engagement usage.
Linked Issues check ✅ Passed The PR addresses #101 by preventing duplicate open and click dashboard usage counts as requested.
Out of Scope Changes check ✅ Passed The added test coverage and campaign-analytics reuse are aligned with the stated objective.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

apps/web/src/server/service/ses-hook-parser.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

apps/web/src/server/service/ses-hook-parser.unit.test.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/web/src/server/service/ses-hook-parser.unit.test.ts (1)

144-186: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No coverage for the reused campaign-analytics dedup path.

This PR also changes campaign-analytics dedup (Line 293 of the parser) to reuse existingMailEvent, but the suite only exercises non-campaign engagement events (campaignId: null). Adding a case with campaignId set and a duplicate status event would validate that updateCampaignAnalytics is skipped on the second call, mirroring the intent described in the PR objectives.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/server/service/ses-hook-parser.unit.test.ts` around lines 144 -
186, Extend the “parseSesHook dashboard engagement usage” tests to cover events
with a non-null campaignId and duplicate Open/Click statuses. Mock the reused
existingMailEvent path as needed, invoke parseSesHook twice, and assert
updateCampaignAnalytics is called only for the first event while
emailEvent.create still records both events.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/server/service/ses-hook-parser.ts`:
- Around line 104-116: Make deduplication in the email-event creation flow
atomic rather than relying on the `existingMailEvent` lookup. Add a database
uniqueness constraint for the `(emailId, status)` pair and use an upsert or
conflict-ignoring insert when creating `EmailEvent`; alternatively,
transactionally lock the record around the lookup and create. Preserve the
existing campaign and engagement-event eligibility behavior.

---

Nitpick comments:
In `@apps/web/src/server/service/ses-hook-parser.unit.test.ts`:
- Around line 144-186: Extend the “parseSesHook dashboard engagement usage”
tests to cover events with a non-null campaignId and duplicate Open/Click
statuses. Mock the reused existingMailEvent path as needed, invoke parseSesHook
twice, and assert updateCampaignAnalytics is called only for the first event
while emailEvent.create still records both events.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d35d5773-1c60-4336-8ecc-c9877a9c2c54

📥 Commits

Reviewing files that changed from the base of the PR and between c1f898c and 90f205c.

📒 Files selected for processing (2)
  • apps/web/src/server/service/ses-hook-parser.ts
  • apps/web/src/server/service/ses-hook-parser.unit.test.ts

Comment on lines +104 to +116
const isEngagementEvent = [EmailStatus.OPENED, EmailStatus.CLICKED].includes(
mailStatus,
);
const existingMailEvent =
email.campaignId || isEngagementEvent
? await db.emailEvent.findFirst({
where: {
emailId: email.id,
status: mailStatus,
},
})
: null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether EmailEvent has a uniqueness guarantee on (emailId, status)
# that would make this check-then-act pattern safe under concurrency.
fd -e prisma | xargs -I{} grep -n -A 15 "model EmailEvent" {}

Repository: usesend/useSend

Length of output: 634


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant parser flow around the lookup, duplicate checks, and create path.
file="apps/web/src/server/service/ses-hook-parser.ts"

sed -n '90,130p' "$file"
printf '\n---\n'
sed -n '198,220p' "$file"
printf '\n---\n'
sed -n '286,320p' "$file"

printf '\n=== Prisma model ===\n'
fd -e prisma | xargs -I{} sh -c 'grep -n -A 20 "model EmailEvent" "{}"'

Repository: usesend/useSend

Length of output: 3142


Dedup still races under concurrent deliveries
findFirst can return null in two parallel invocations before either reaches db.emailEvent.create(), and EmailEvent has no uniqueness constraint on (emailId, status) to block the duplicate insert. Move this guard to the DB layer with a unique constraint + upsert/ON CONFLICT DO NOTHING, or wrap the lookup and create in a transaction/lock.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/server/service/ses-hook-parser.ts` around lines 104 - 116, Make
deduplication in the email-event creation flow atomic rather than relying on the
`existingMailEvent` lookup. Add a database uniqueness constraint for the
`(emailId, status)` pair and use an upsert or conflict-ignoring insert when
creating `EmailEvent`; alternatively, transactionally lock the record around the
lookup and create. Preserve the existing campaign and engagement-event
eligibility behavior.

@KMKoushik
KMKoushik merged commit 2604a3b into main Jul 13, 2026
6 checks passed
@KMKoushik
KMKoushik deleted the codex/fix-dashboard-usage-counts branch July 13, 2026 00:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

clicks and opens are double counted for dashboard usage

1 participant