At volumes above 100,000 emails per day, a single sending IP becomes both a performance bottleneck and a single point of reputation failure. A block on one IP stops all sending until it is manually removed and replaced — a process that can take hours. IP pool management distributes sending load across multiple IPs and allows the system to automatically exclude a blocked or high-bounce IP and continue sending through healthy ones, without downtime or engineering intervention. Reputation-weighted or least-used selection also prevents one IP from absorbing a disproportionate share of sends while warm-up periods complete on others.
Low because single-IP architectures are acceptable at lower volumes, but above 50,000 emails per day a single IP block creates a total sending outage that requires manual intervention to resolve.
Store sending IPs in the database with per-IP health and warm-up metadata, then select from the pool based on health and current load:
export async function selectSendingIp(): Promise<string> {
const candidates = await db.sendingIp.findMany({
where: { active: true },
orderBy: { sentToday: 'asc' } // Prefer least-used
})
if (candidates.length === 0) {
throw new Error('No available sending IPs')
}
// Exclude IPs with bounce rates above 5% — they are reputation-damaged
const healthy = candidates.filter(ip => ip.hourlyBounceRate < 0.05)
// Fall back to any active IP if all are bouncing above threshold
return (healthy[0] ?? candidates[0]).ipAddress
}
Add an active boolean column to the SendingIp table so a damaged IP can be removed from rotation by setting active = false — no code deployment required. Pair with the warmup schedule check from deliverability-engineering.warmup-reputation.automated-warmup-schedule so IPs in early warm-up are not selected for full-volume sends.
ID: deliverability-engineering.domain-ip-strategy.ip-pool-management
Severity: low
What to look for: Count all sending IPs configured and enumerate the selection logic. For high-volume senders (>100k/day), check whether the codebase manages multiple sending IPs in a pool. Look for IP rotation logic, IP health tracking (per-IP bounce rate, per-IP reputation), and the ability to remove a damaged IP from the pool without downtime. Also check whether the code balances load across IPs in the pool using round-robin, least-used, or reputation-weighted selection.
Pass criteria: At least 2 sending IPs are configured and managed. The system selects IPs from the pool with awareness of each IP's health and warm-up status. An IP can be removed from rotation without code changes.
Fail criteria: A single sending IP is used with no pool management, or multiple IPs are used but selected randomly without regard to health or warm-up status.
Skip (N/A) when: Sending volume is below 50,000 emails/day where a single dedicated IP is sufficient, or the ESP manages IP pool selection transparently.
Detail on fail: "Single IP used for all sending — no IP pool management or rotation logic found" or "Multiple IPs configured but selected randomly without health checks — a damaged IP remains in rotation"
Remediation: Implement IP pool selection with health awareness:
export async function selectSendingIp(): Promise<string> {
const availableIps = await db.sendingIp.findMany({
where: {
active: true,
// Exclude IPs in warm-up phase that would exceed daily limit
// (Use the canSendMore function from warmup logic)
},
orderBy: { sentToday: 'asc' } // Round-robin: prefer least-used
})
if (availableIps.length === 0) {
throw new Error('No available sending IPs — all pools at capacity or in cool-down')
}
// Prefer IPs with lowest current bounce rate
const ranked = availableIps
.filter(ip => ip.hourlyBounceRate < 0.05) // Exclude high-bounce IPs
.sort((a, b) => a.sentToday - b.sentToday)
return ranked[0]?.ipAddress ?? availableIps[0].ipAddress
}