Skip to content

Comments

Updated the scaling factors for domain warmup#41

Open
tomerqodo wants to merge 2 commits intocoderabbit_full_base_updated_the_scaling_factors_for_domain_warmup_pr3from
coderabbit_full_head_updated_the_scaling_factors_for_domain_warmup_pr3
Open

Updated the scaling factors for domain warmup#41
tomerqodo wants to merge 2 commits intocoderabbit_full_base_updated_the_scaling_factors_for_domain_warmup_pr3from
coderabbit_full_head_updated_the_scaling_factors_for_domain_warmup_pr3

Conversation

@tomerqodo
Copy link

@tomerqodo tomerqodo commented Jan 28, 2026

Benchmark PR from agentic-review-benchmarks#3

Summary by CodeRabbit

  • Refactor

    • Updated email domain warming scaling strategy with refined progression thresholds and improved high-volume capacity handling.
    • Restructured internal data access patterns for improved query efficiency.
  • Tests

    • Updated test suite to align with revised scaling model and new data access patterns.

✏️ Tip: You can customize this high-level summary in your review settings.

sam-lord and others added 2 commits January 25, 2026 11:56
closes
[GVA-605](https://linear.app/ghost/issue/GVA-605/adjust-the-warmup-numbers-to-reflect-joeys-suggestions)

Our initial scaling factors were based on some preliminary research, and
were designed to be as simple as possible. After further discussions,
we've decided to avoid having such a fast ramp-up at the early stage,
and limit the growth at the later stages to 75k+ per send. That should
still prevent any large sites from going back into warm-up between
sends, but is more likely to avoid damaging the reputation by scaling
too aggressively.
@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

Walkthrough

The changes refactor the domain warming service's email data access from single-record findOne lookups to paginated findPage queries, and introduce a new high-volume scaling configuration that caps email send growth above 400,000 records using scaled and absolute increase calculations. Integration and unit tests are updated to align with the new data access pattern and scaling model.

Changes

Cohort / File(s) Summary
Core Service Implementation
ghost/core/core/server/services/email-service/DomainWarmingService.ts
Updated EmailModel interface to use findPage with limit instead of findOne. Replaced defaultScale with highVolume configuration (threshold: 400,000, maxScale: 1.2, maxAbsoluteIncrease: 75,000). Expanded WARMUP_SCALING_TABLE thresholds (1K→1.25×, 5K→1.5×, 100K→1.75×, 400K→2×). Updated #getHighestCount() to read from paged results. Enhanced #getTargetLimit() with high-volume early cap logic using min(scaled, absolute) increase. Changed threshold comparison from <= to <.
Integration Tests
ghost/core/test/integration/services/email-service/domain-warming.test.js
Fixed base date and fake timer for deterministic testing. Replaced doubling behavior with 1.25× scaling increments. Adjusted daily expectations (Day 1: 200, Day 2: 250, Day 3: 313). Introduced getExpectedScale() helper for threshold calculations. Updated assertions to use new scaling model while preserving gap-handling logic.
Unit Tests
ghost/core/test/unit/server/services/email-service/domain-warming-service.test.ts
Migrated Email model mock from findOne to findPage interface. Replaced stubs referencing single records with paged-result stubs. Updated test data and assertions to reflect new scaling progression. Added explicit assertions for limit: 1, DESC ordering, and created_at filtering in page queries.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A warming service hops with grace,
From one record to a paced embrace,
At 400K the caps align,
With 1.25× scaling so fine—
Growth now graceful, limits divine! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: modifying the scaling factors used in the domain warmup service, as evidenced by updates to WARMUP_SCALING_TABLE and scaling logic in DomainWarmingService.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@ghost/core/core/server/services/email-service/DomainWarmingService.ts`:
- Around line 101-104: The filter used in DomainWarmingService when calling
this.#emailModel.findPage currently uses `created_at:<=${new
Date().toISOString().split('T')[0]}` which can include today's emails; change
the comparator to a strict less-than so today is excluded (e.g., use
`created_at:<${...}` or equivalent) so the method's "excluding today" contract
is respected when querying via this.#emailModel.findPage.
- Around line 111-112: The null check using the != operator in
DomainWarmingService.ts should be replaced with nullish coalescing to satisfy
eqeqeq; change the return logic around the variable count (from
result.data[0].get('csd_email_count')) to use count ?? 0 (or inline the ?? when
assigning) so that null/undefined fall back to 0 without using !=.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 91d9694 and 71f5648.

📒 Files selected for processing (3)
  • ghost/core/core/server/services/email-service/DomainWarmingService.ts
  • ghost/core/test/integration/services/email-service/domain-warming.test.js
  • ghost/core/test/unit/server/services/email-service/domain-warming-service.test.ts
🧰 Additional context used
🧬 Code graph analysis (2)
ghost/core/test/integration/services/email-service/domain-warming.test.js (1)
ghost/core/test/unit/server/services/email-service/batch-sending-service.test.js (12)
  • clock (901-901)
  • clock (935-935)
  • clock (1002-1002)
  • clock (1051-1051)
  • clock (1083-1083)
  • clock (1729-1729)
  • clock (1745-1745)
  • clock (1761-1761)
  • clock (1781-1781)
  • clock (1867-1867)
  • clock (1896-1896)
  • sinon (3-3)
ghost/core/test/unit/server/services/email-service/domain-warming-service.test.ts (1)
ghost/core/core/server/services/email-service/DomainWarmingService.ts (1)
  • DomainWarmingService (67-140)
🪛 ESLint
ghost/core/core/server/services/email-service/DomainWarmingService.ts

[error] 112-112: Expected '!==' and instead saw '!='.

(eqeqeq)

🔇 Additional comments (11)
ghost/core/core/server/services/email-service/DomainWarmingService.ts (3)

5-27: Type updates for paged email access look consistent.
The Email model and scaling table typing changes align with the new paging API and high-volume config.


30-65: Scaling table updates look coherent and well documented.
The new thresholds and high‑volume cap are clearly captured in the table and comments.


124-138: High‑volume cap + threshold progression logic looks good.
The early high‑volume clamp and ordered thresholds cover the intended ramp behavior.

ghost/core/test/unit/server/services/email-service/domain-warming-service.test.ts (4)

10-21: Mock shape update matches the new paging API.
Using findPage and findAll keeps the unit tests aligned with the service.


66-161: Base-case and null/undefined handling tests look solid.
These cases provide good coverage for the updated paging data shape and edge counts.


163-184: Query-argument assertions are precise.
The test validates filter, ordering, and limit for the paging lookup.


186-226: Progression test cases reflect the new scaling model well.
The table-driven checks give clear coverage for each threshold.

ghost/core/test/integration/services/email-service/domain-warming.test.js (4)

52-67: Deterministic time setup is a good test stabilization step.
The fixed base date with midday normalization should reduce flaky date transitions.


192-199: Updated expected-limit calculation matches the new 1.25× ramp.
The conditional assertions align with the revised scaling behavior.


220-241: Multi‑day progression assertions look consistent.
Day‑by‑day expectations correctly mirror the new scaling steps.


290-322: Helper-based scaling checks improve clarity.
The expected-scale helper makes the max‑limit test easier to reason about.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +101 to +104
const result = await this.#emailModel.findPage({
filter: `created_at:<=${new Date().toISOString().split('T')[0]}`,
order: 'csd_email_count DESC',
limit: 1
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Exclude today’s emails per the method contract.
The docstring says “excluding today”, but created_at:<=${today} can include today’s records, which may allow same‑day ramping. Use a strict < (or equivalent) to exclude today.

🔧 Suggested fix
-            filter: `created_at:<=${new Date().toISOString().split('T')[0]}`,
+            filter: `created_at:<${new Date().toISOString().split('T')[0]}`,
🤖 Prompt for AI Agents
In `@ghost/core/core/server/services/email-service/DomainWarmingService.ts` around
lines 101 - 104, The filter used in DomainWarmingService when calling
this.#emailModel.findPage currently uses `created_at:<=${new
Date().toISOString().split('T')[0]}` which can include today's emails; change
the comparator to a strict less-than so today is excluded (e.g., use
`created_at:<${...}` or equivalent) so the method's "excluding today" contract
is respected when querying via this.#emailModel.findPage.

Comment on lines +111 to +112
const count = result.data[0].get('csd_email_count');
return count != null ? count : 0;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "DomainWarmingService.ts" | head -20

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 155


🏁 Script executed:

# Check if file exists and read lines 111-112
file_path=$(find . -type f -name "DomainWarmingService.ts" | head -1)
if [ -n "$file_path" ]; then
  echo "File found at: $file_path"
  sed -n '105,115p' "$file_path"
fi

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 369


🏁 Script executed:

# Search for eslint configuration files
find . -type f \( -name ".eslintrc*" -o -name "eslint.config.*" \) | head -10

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 455


🏁 Script executed:

# Check package.json for eslint configuration
cat package.json | grep -A 20 "eslintConfig" || echo "No eslintConfig found in package.json"

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 121


🏁 Script executed:

# Search for eslint config in ghost/core directory
find ./ghost/core -type f \( -name ".eslintrc*" -o -name "eslint.config.*" \) | head -20

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 140


🏁 Script executed:

# Look at the ghost/core directory structure
ls -la ./ghost/core/ | grep -E "eslint|\.eslintrc"

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 212


🏁 Script executed:

# Check if there's a root level eslint config that applies to ghost/core
ls -la .eslintrc* 2>/dev/null || echo "No .eslintrc files in root"
cat .eslintignore 2>/dev/null | head -20 || echo "No .eslintignore"

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 110


🏁 Script executed:

# Read the root eslint config
cat .eslintrc.js | head -100

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 128


🏁 Script executed:

# Read ghost/core eslint config
cat ./ghost/core/.eslintrc.js | head -100

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 4069


🏁 Script executed:

# Search for eqeqeq rule in the ghost/core eslint config
grep -n "eqeqeq" ./ghost/core/.eslintrc.js

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 66


🏁 Script executed:

# Check if there's a ghost eslint plugin package
find . -type f -path "*ghost*eslint*" -name "package.json" | head -5

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 66


🏁 Script executed:

# Search for eqeqeq rule in the entire codebase
rg "eqeqeq" --type js --type json | head -20

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 66


🏁 Script executed:

# Check if there's node_modules and look for ghost eslint plugin
find . -type f -path "*/node_modules/*ghost*eslint*" -name "index.js" | head -5

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 66


🏁 Script executed:

# Check the actual eslint dependencies in ghost/core
cat ./ghost/core/package.json | grep -A 5 -B 5 "eslint"

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 1637


🏁 Script executed:

# Search more broadly for eqeqeq mentions
rg "eqeqeq" --type-list | head -5
rg "eqeqeq" -t typescript -t javascript -t json 2>/dev/null | head -20

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 473


🏁 Script executed:

# Look for eslint plugins in ghost workspace
find ./ghost -type d -name "eslint-plugin*" | head -10

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 66


🏁 Script executed:

# Check if there are any ghost related eslint configs/packages
find ./ghost -type f -name "*.js" -path "*/eslint*" | head -20

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 66


🏁 Script executed:

# Search for where plugin:ghost/ts is defined
rg "plugin:ghost" --type javascript --type json | head -10

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 118


🏁 Script executed:

# Check root package.json for eslint plugin
cat ./package.json | grep -i eslint | head -20

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 221


🏁 Script executed:

# Look for eslint-plugin-ghost in node_modules
find . -type f -path "*/node_modules/eslint-plugin-ghost*" -name "*.js" | head -20

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 1391


🏁 Script executed:

# Check if eslint-plugin-ghost package is available
ls -la ./node_modules/eslint-plugin-ghost/ 2>/dev/null || echo "eslint-plugin-ghost not in node_modules"

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 804


🏁 Script executed:

# Search for the configs provided by eslint-plugin-ghost
rg "plugin:ghost" . --type json | head -10

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 269


🏁 Script executed:

# Let's check what the actual eqeqeq rule behavior is - search for any === or != in the file
sed -n '100,120p' ./ghost/core/core/server/services/email-service/DomainWarmingService.ts

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 822


🏁 Script executed:

# Check the ts.js config from eslint-plugin-ghost to see if eqeqeq is configured
cat ./node_modules/eslint-plugin-ghost/lib/config/ts.js

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 529


🏁 Script executed:

# Also check shared-rules.js which might have eqeqeq
cat ./node_modules/eslint-plugin-ghost/lib/config/shared-rules.js

Repository: qodo-benchmark/Ghost-combined-coderabbit

Length of output: 3345


Fix eqeqeq lint error on null check.
The != operator violates the active eqeqeq rule (set to 'always'). Use nullish coalescing instead to maintain behavior and satisfy the linter.

🔧 Suggested fix
-        const count = result.data[0].get('csd_email_count');
-        return count != null ? count : 0;
+        const count = result.data[0].get('csd_email_count');
+        return count ?? 0;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const count = result.data[0].get('csd_email_count');
return count != null ? count : 0;
const count = result.data[0].get('csd_email_count');
return count ?? 0;
🧰 Tools
🪛 ESLint

[error] 112-112: Expected '!==' and instead saw '!='.

(eqeqeq)

🤖 Prompt for AI Agents
In `@ghost/core/core/server/services/email-service/DomainWarmingService.ts` around
lines 111 - 112, The null check using the != operator in DomainWarmingService.ts
should be replaced with nullish coalescing to satisfy eqeqeq; change the return
logic around the variable count (from result.data[0].get('csd_email_count')) to
use count ?? 0 (or inline the ?? when assigning) so that null/undefined fall
back to 0 without using !=.

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.

2 participants