Skip to content

Fixed security scanner opens counting in email analytics - #30625

Open
Kru5hna wants to merge 1 commit into
TryGhost:mainfrom
Kru5hna:fix/email-analytics-bot-detection
Open

Kru5hna wants to merge 1 commit into
TryGhost:mainfrom
Kru5hna:fix/email-analytics-bot-detection

Conversation

@Kru5hna

@Kru5hna Kru5hna commented Sep 9, 2026 •

Copy link
Copy Markdown

ref #30388

On hold — needs a UI/rollout plan before merging so open rates don't shift overnight: https://ghost.slack.com/archives/C07HTEJMR2R/p1791357131855319

Firewall and anti-virus scanners fetch the tracking pixel when an email arrives, inflating open rates. Mailgun flags these opens with client-info.bot: generic; newsletter and automation analytics now skip them. apple (Mail Privacy Protection) and gmail (image proxy) opens are still counted, since dropping them would erase most Apple Mail/Gmail opens. Unflagged opens are unchanged; guard tests cover all three cases.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 906f2a4 and a0a0a8c.

Files selected for processing: 6
  • ghost/core/server/lib/mailgun/mailgun-client.js
  • ghost/core/server/services/email-analytics/automation-email-analytics-batch-processor.ts
  • ghost/core/server/services/email-analytics/newsletter-email-analytics-batch-processor.js
  • ghost/test/unit/server/lib/mailgun/mailgun-client.test.js
  • ghost/test/unit/server/services/email-analytics/automation-email-analytics-batch-processor.test.ts
  • ghost/test/unit/server/services/email-analytics/newsletter-email-analytics-batch-processor.test.js

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.

Recent review details
Context from checks skipped due to timeout: 10
  • GitHub Check: Ghost-CLI tests (clean-install, Node 22.23.4)
  • GitHub Check: Ghost-CLI tests (clean-install, Node 24.20.0)
  • GitHub Check: Ghost-CLI tests (latest-release, Node 22.23.4)
  • GitHub Check: Acceptance tests (Node 22.23.4, mysql8)
  • GitHub Check: Acceptance tests (Node 24.20.0, mysql8)
  • GitHub Check: Unit tests (Node 22.23.4)
  • GitHub Check: Legacy tests (Node 22.23.4, mysql8)
  • GitHub Check: Build Docker Images
  • GitHub Check: Legacy tests (Node 24.20.0, mysql8)
  • GitHub Check: CodeRabbit
Additional context used
Code guidelines: 4
docs/practices/error-handling.md — configured
docs/codebase/jobs.md — configured
docs/codebase/monorepo-structure.md — configured
tools/README.md — configured
Path-based instructions: 9
Review new or changed service boundaries for explicit dependency ownership, deterministic/idempotent initialisation, boot ordering, transaction and event semantics, cache coherence, and restart/multi-instance safety.

CodeRabbit configuration file

Files:

  • ghost/core/server/services/email-analytics/newsletter-email-analytics-batch-processor.js
  • ghost/core/server/services/email-analytics/automation-email-analytics-batch-processor.ts
Review whether tests prove changed behaviour, meaningful error/edge paths, and externally observable contracts without coupling to implementation details.

CodeRabbit configuration file

Files:

  • ghost/test/unit/server/services/email-analytics/newsletter-email-analytics-batch-processor.test.js
  • ghost/test/unit/server/services/email-analytics/automation-email-analytics-batch-processor.test.ts
  • ghost/test/unit/server/lib/mailgun/mailgun-client.test.js
New source files must be TypeScript: flag new JS files as a required change unless exempt (DB migrations, tool/config files, tools/scripts/, docker/, generated code).

CodeRabbit configuration file

Files:

  • ghost/core/server/services/email-analytics/newsletter-email-analytics-batch-processor.js
  • ghost/test/unit/server/services/email-analytics/newsletter-email-analytics-batch-processor.test.js
  • ghost/core/server/lib/mailgun/mailgun-client.js
  • ghost/test/unit/server/lib/mailgun/mailgun-client.test.js
Review lens: "where does this data become trusted?" Boundary data (HTTP input, external API/SDK responses, env/config, DB/filesystem reads, queue/webhook/event payloads) is `unknown` until validated — Zod by default.

CodeRabbit configuration file

Files:

  • ghost/test/unit/server/services/email-analytics/automation-email-analytics-batch-processor.test.ts
  • ghost/core/server/services/email-analytics/automation-email-analytics-batch-processor.ts
Prioritise concrete correctness, security, data-integrity, compatibility, and regression risks.

CodeRabbit configuration file

Files:

  • ghost/core/server/services/email-analytics/newsletter-email-analytics-batch-processor.js
  • ghost/test/unit/server/services/email-analytics/newsletter-email-analytics-batch-processor.test.js
  • ghost/test/unit/server/services/email-analytics/automation-email-analytics-batch-processor.test.ts
  • ghost/core/server/lib/mailgun/mailgun-client.js
  • ghost/test/unit/server/lib/mailgun/mailgun-client.test.js
  • ghost/core/server/services/email-analytics/automation-email-analytics-batch-processor.ts
Source excerpt: Use that fallback when there is no safe, useful message.

CodeRabbit inference engine (docs/practices/error-handling.md)

Files:

  • ghost/core/server/services/email-analytics/newsletter-email-analytics-batch-processor.js
  • ghost/core/server/lib/mailgun/mailgun-client.js
  • ghost/core/server/services/email-analytics/automation-email-analytics-batch-processor.ts
Source excerpt: Jobs run in-process and share the main process's initialized services.

CodeRabbit inference engine (docs/codebase/jobs.md)

Files:

  • ghost/core/server/services/email-analytics/newsletter-email-analytics-batch-processor.js
  • ghost/core/server/services/email-analytics/automation-email-analytics-batch-processor.ts
Applicability reference: AGENTS.md: # AGENTS.md Start with: [Monorepo structure](docs/codebase/monorepo-structure.md) AGENTS.md: # AGENTS.md **Ghost Core:** use the [server map](docs/codebase/monorepo-structure.md#ghost-core) Source excerpt...

CodeRabbit inference engine (docs/codebase/monorepo-structure.md)

Files:

  • ghost/core/server/services/email-analytics/newsletter-email-analytics-batch-processor.js
  • ghost/test/unit/server/services/email-analytics/newsletter-email-analytics-batch-processor.test.js
  • ghost/test/unit/server/services/email-analytics/automation-email-analytics-batch-processor.test.ts
  • ghost/core/server/lib/mailgun/mailgun-client.js
  • ghost/test/unit/server/lib/mailgun/mailgun-client.test.js
  • ghost/core/server/services/email-analytics/automation-email-analytics-batch-processor.ts
Applicability reference: AGENTS.md: # AGENTS.md **Repository tooling:** read the [tooling ownership guide](tools/README.md) Source excerpt: | Job | Home | How it runs | | ---------------------------------------------------...

CodeRabbit inference engine (tools/README.md)

Files:

  • ghost/core/server/services/email-analytics/newsletter-email-analytics-batch-processor.js
  • ghost/test/unit/server/services/email-analytics/newsletter-email-analytics-batch-processor.test.js
  • ghost/test/unit/server/services/email-analytics/automation-email-analytics-batch-processor.test.ts
  • ghost/core/server/lib/mailgun/mailgun-client.js
  • ghost/test/unit/server/lib/mailgun/mailgun-client.test.js
  • ghost/core/server/services/email-analytics/automation-email-analytics-batch-processor.ts
Additional comments: 6
ghost/core/server/lib/mailgun/mailgun-client.js: 1

404-409: LGTM!

ghost/test/unit/server/lib/mailgun/mailgun-client.test.js: 1

1203-1203: LGTM!

Also applies to: 1266-1266, 1340-1340, 1350-1394

ghost/core/server/services/email-analytics/automation-email-analytics-batch-processor.ts: 1

19-19: LGTM!

Also applies to: 117-118

ghost/core/server/services/email-analytics/newsletter-email-analytics-batch-processor.js: 1

118-118: LGTM!

Also applies to: 143-146

ghost/test/unit/server/services/email-analytics/automation-email-analytics-batch-processor.test.ts: 1

94-117: LGTM!

Also applies to: 119-158

ghost/test/unit/server/services/email-analytics/newsletter-email-analytics-batch-processor.test.js: 1

187-224: LGTM!

Also applies to: 226-270

Walkthrough

Mailgun event normalization now includes a trimmed bot value or null. The automation and newsletter email analytics processors classify opened events marked generic as unprocessable. Other bot values continue through the existing open-tracking paths. Tests cover normalization, tracking results, event counts, and timestamp updates.

Suggested reviewers: troyciesco, cmraible

Priority: Normal

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: Minimal · up to a0a0a

The filtering change is ready for normal checks. Coordinate the planned UI rollout because reported open rates may shift.

Pre-merge checks: Passed 6 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Type-Safe Boundaries Warning The PR adds a new Mailgun boundary read without parsing the response. #fetchEventsFromDomain maps page.items directly to normalizeEvent at mailgun-client.js:283 and :332, and the changed cod… Add a Zod schema for the Mailgun event data used by this pipeline, including optional nullable client-info.bot, and parse or safely parse each external event before normalizeEvent reads it. Reject or skip invalid events without accessin…
Passed Passed checks: 6 passed
Check name Status Explanation
Linked Issues check Passed Issue #30388 is closed and supplies historical context only. No active directly linked issue supplies coding requirements for this pull request.
Out of Scope Changes check Passed The changes stay within the described email-analytics scope. They normalize Mailgun bot data, filter only generic opened events in newsletter and automation processors, and add focused unit tests. N…
New Files Are Typescript Passed The authoritative PR diff contains no added files. All six changed paths have status M, including the JavaScript files, so the check's failure condition for adding a new .js/.jsx/.cjs/.mjs source …
Tooling Ownership Passed PASS. The PR changes only Ghost Core Mailgun/email-analytics implementation files and their adjacent unit tests. No scripts, tools, shared configuration, entrypoints, or moved paths changed. The servi…
Title check Passed The title clearly identifies the main change: excluding security-scanner opens from email analytics counts.
Description check Passed The description accurately explains Mailgun bot detection, filtering of generic bot opens, retained handling for Apple and Gmail opens, and test coverage.
Full details: Out of Scope Changes check

Explanation

The changes stay within the described email-analytics scope. They normalize Mailgun bot data, filter only generic opened events in newsletter and automation processors, and add focused unit tests. No unrelated change is shown.

Full details: New Files Are Typescript

Explanation

The authoritative PR diff contains no added files. All six changed paths have status M, including the JavaScript files, so the check's failure condition for adding a new .js/.jsx/.cjs/.mjs source file is not met.

Full details: Tooling Ownership

Explanation

PASS. The PR changes only Ghost Core Mailgun/email-analytics implementation files and their adjacent unit tests. No scripts, tools, shared configuration, entrypoints, or moved paths changed. The service README assigns domain logic to Core services, and the tests remain beside their owners. No tooling ownership or execution problem is introduced.

Full details: Type-Safe Boundaries

Explanation

The PR adds a new Mailgun boundary read without parsing the response. #fetchEventsFromDomain maps page.items directly to normalizeEvent at mailgun-client.js:283 and :332, and the changed code reads event['client-info'].bot at :405-409. The typeof check validates only the leaf value. No Zod schema or boundary parser validates the Mailgun event or client-info before this read. The downstream bot checks consume the resulting unvalidated field.

Resolution

Add a Zod schema for the Mailgun event data used by this pipeline, including optional nullable client-info.bot, and parse or safely parse each external event before normalizeEvent reads it. Reject or skip invalid events without accessing unvalidated fields. Use the parsed result or a z.infer type for valid events, and preserve the current bot normalization for valid strings.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@9larsons

Copy link
Copy Markdown
Contributor

Thanks for this suggestion. I’ve narrowed the change to only skip opens flagged generic (firewall / anti-virus scanners).

It’s ready code-wise, but I'm marking it blocked for now because changing how opens are counted shifts open rates for some publishers overnight and given that we want a UI/rollout plan alongside it before merging. We’ll pick it back up once the team working on email analytics has bandwidth!

ref TryGhost#30388

Corporate firewalls and anti-virus scanners fetch every link and the tracking pixel as soon as an email arrives, inflating open rates for newsletters and automated emails. Mailgun flags these opens with `client-info.bot: generic`, so both analytics processors now skip them. Opens flagged `apple` (Mail Privacy Protection) and `gmail` (image proxy) are still counted as before, because dropping them would silently erase most opens from Apple Mail and Gmail readers.
@9larsons
9larsons force-pushed the fix/email-analytics-bot-detection branch from 906f2a4 to a0a0a8c Compare October 27, 2026 19:58
@9larsons 9larsons changed the title Fixed bot open event filtering in email analytics Fixed security scanner opens counting in email analytics Oct 27, 2026
@nx-cloud

nx-cloud Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit a0a0a8c

Command Status Duration Result
nx run ghost:test:integration ✅ Succeeded 1m 53s View ↗
nx run ghost:test:ci:integration ✅ Succeeded 1m 50s View ↗
nx run ghost:test:ci:e2e ✅ Succeeded 3m 44s View ↗
nx run ghost:test:e2e ✅ Succeeded 3m 41s View ↗
nx run ghost:test:legacy ✅ Succeeded 1m 46s View ↗
nx run ghost:test:ci:unit ✅ Succeeded 1m 39s View ↗
nx run ghost-monorepo:lint:boundaries ✅ Succeeded 21s View ↗
nx run-many -t test:unit -p ghost ✅ Succeeded 40s View ↗
Additional runs (6) ✅ Succeeded ... View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-10-27 20:07:42 UTC

@codecov

codecov Bot commented Oct 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.66667% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 85.56%. Comparing base (96f9de5) to head (a0a0a8c).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
...tics/newsletter-email-analytics-batch-processor.js 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #30625      +/-   ##
==========================================
+ Coverage   85.45%   85.56%   +0.10%     
==========================================
  Files        1404     1404              
  Lines       52544    52546       +2     
  Branches     9022     9023       +1     
==========================================
+ Hits        44903    44962      +59     
+ Misses       6540     6499      -41     
+ Partials     1101     1085      -16     
Flag Coverage Δ
e2e-tests 72.49% <33.33%> (-0.15%) ⬇️
unit-tests 68.11% <66.66%> (+0.20%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants