-
Notifications
You must be signed in to change notification settings - Fork 0
Switched to time-based domain warmup #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: coderabbit_full_base_switched_to_time-based_domain_warmup_pr9
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -15,63 +15,23 @@ type EmailRecord = { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| get(field: string): unknown; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type WarmupScalingTable = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| base: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| limit: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| thresholds: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| limit: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scale: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }[]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| highVolume: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| threshold: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maxScale: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maxAbsoluteIncrease: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type WarmupVolumeOptions = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| start: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| totalDays: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Configuration for domain warming email volume scaling. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | Volume Range | Multiplier | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * |--------------|--------------------------------------------------| | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ≤100 (base) | 200 messages | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | 101 – 1k | 1.25× (conservative early ramp) | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | 1k – 5k | 1.5× (moderate increase) | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | 5k – 100k | 1.75× (faster ramp after proving deliverability) | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | 100k – 400k | 2× | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | 400k+ | min(1.2×, +75k) cap | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const WARMUP_SCALING_TABLE: WarmupScalingTable = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| base: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| limit: 100, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value: 200 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| thresholds: [{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| limit: 1_000, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scale: 1.25 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| limit: 5_000, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scale: 1.5 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| limit: 100_000, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scale: 1.75 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| limit: 400_000, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scale: 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| highVolume: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| threshold: 400_000, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maxScale: 1.2, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maxAbsoluteIncrease: 75_000 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const DefaultWarmupOptions: WarmupVolumeOptions = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| start: 200, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| end: 200000, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| totalDays: 42 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class DomainWarmingService { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #emailModel: EmailModel; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #labs: LabsService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #config: ConfigService; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #warmupConfig: WarmupVolumeOptions; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor(dependencies: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| models: {Email: EmailModel}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -81,6 +41,8 @@ export class DomainWarmingService { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.#emailModel = dependencies.models.Email; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.#labs = dependencies.labs; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.#config = dependencies.config; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.#warmupConfig = DefaultWarmupOptions; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -99,58 +61,39 @@ export class DomainWarmingService { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Boolean(fallbackDomain && fallbackAddress); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Get the maximum amount of emails that should be sent from the warming sending domain in today's newsletter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param emailCount The total number of emails to be sent in this newsletter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @returns The number of emails that should be sent from the warming sending domain (remaining emails to be sent from fallback domain) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async getWarmupLimit(emailCount: number): Promise<number> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const lastCount = await this.#getHighestCount(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Math.min(emailCount, this.#getTargetLimit(lastCount)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @returns The highest number of messages sent from the CSD in a single email (excluding today) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async #getHighestCount(): Promise<number> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const result = await this.#emailModel.findPage({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| filter: `created_at:<${new Date().toISOString().split('T')[0]}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| order: 'csd_email_count DESC', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async #getDaysSinceFirstEmail(): Promise<number> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const res = await this.#emailModel.findPage({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| filter: 'csd_email_count:-null', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| order: 'created_at ASC', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| limit: 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!result.data.length) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!res.data.length) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const count = result.data[0].get('csd_email_count'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return count || 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Math.ceil((Date.now() - new Date(res.data[0].get('created_at') as string).getTime()) / (1000 * 60 * 60 * 24)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param lastCount Highest number of messages sent from the CSD in a single email | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @returns The limit for sending from the warming sending domain for the next email | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Get the maximum amount of emails that should be sent from the warming sending domain in today's newsletter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param emailCount The total number of emails to be sent in this newsletter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @returns The number of emails that should be sent from the warming sending domain (remaining emails to be sent from fallback domain) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #getTargetLimit(lastCount: number): number { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (lastCount <= WARMUP_SCALING_TABLE.base.limit) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return WARMUP_SCALING_TABLE.base.value; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // For high volume senders (400k+), cap the increase at 20% or 75k absolute | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (lastCount > WARMUP_SCALING_TABLE.highVolume.threshold) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const scaledIncrease = Math.ceil(lastCount * WARMUP_SCALING_TABLE.highVolume.maxScale); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const absoluteIncrease = lastCount + WARMUP_SCALING_TABLE.highVolume.maxAbsoluteIncrease; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Math.min(scaledIncrease, absoluteIncrease); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async getWarmupLimit(emailCount: number): Promise<number> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const day = await this.#getDaysSinceFirstEmail() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (day > this.#warmupConfig.totalDays) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Infinity | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const threshold of WARMUP_SCALING_TABLE.thresholds.sort((a, b) => a.limit - b.limit)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (lastCount <= threshold.limit) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Math.ceil(lastCount * threshold.scale); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const limit = Math.floor( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.#warmupConfig.start * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Math.pow( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.#warmupConfig.end / this.#warmupConfig.start, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| day / (this.#warmupConfig.totalDays - 1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // This should not be reached given the thresholds cover all cases up to highVolume.threshold | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Math.ceil(lastCount * WARMUP_SCALING_TABLE.highVolume.maxScale); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Math.min(emailCount, limit) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+83
to
+97
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Warmup completion and rounding are off by one (and lint). 🛠️ Proposed fix- const day = await this.#getDaysSinceFirstEmail()
- if (day > this.#warmupConfig.totalDays) {
- return Infinity
+ const day = await this.#getDaysSinceFirstEmail();
+ if (day >= this.#warmupConfig.totalDays) {
+ return Infinity;
}
- const limit = Math.floor(
+ const limit = Math.round(
this.#warmupConfig.start *
Math.pow(
this.#warmupConfig.end / this.#warmupConfig.start,
day / (this.#warmupConfig.totalDays - 1)
)
- )
+ );
- return Math.min(emailCount, limit)
+ return Math.min(emailCount, limit);📝 Committable suggestion
Suggested change
🧰 Tools🪛 ESLint[error] 84-85: Missing semicolon. (semi) [error] 86-87: Missing semicolon. (semi) [error] 95-96: Missing semicolon. (semi) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Day calculation advances within the same day; use floor/clamp.
Using
Math.ceilmeans any positive diff becomes day 1, so a second send minutes later bumps the warmup. That contradicts same‑day stability and can overshoot early.🛠️ Proposed fix
🤖 Prompt for AI Agents