Not all campaigns carry the same deliverability risk. A reactivation campaign to a cold list should send at a fraction of the rate appropriate for an active engaged segment. When all campaigns share a single global rate, a high-risk campaign — cold list, broad reach, lower expected engagement — runs at the same throughput as a tight re-engagement campaign, exposing the IP's reputation to the full complaint rate of the worst-performing send. Per-campaign configurable limits let operators deliberately throttle risky campaigns without touching the global settings or pausing the system.
Low because the absence of per-campaign limits does not cause immediate delivery failure, but it removes a key operational lever for managing deliverability risk on a per-send basis without global system changes.
Add rate configuration fields to the campaign schema so the dispatch worker can read per-campaign limits at runtime:
// In prisma/schema.prisma
model Campaign {
id String @id @default(uuid())
name String
subject String
maxPerHour Int? // null = use system default
maxDailyVolume Int? // null = no campaign-level cap
sendWindow Json? // { start: "09:00", end: "17:00", tz: "America/New_York" }
status CampaignStatus
scheduledAt DateTime?
createdAt DateTime @default(now())
}
// In campaign dispatch worker:
export async function getCampaignRateLimit(campaignId: string): Promise<number> {
const campaign = await db.campaign.findUnique({ where: { id: campaignId } })
return campaign?.maxPerHour ?? DEFAULT_CAMPAIGN_RATE
}
Read maxPerHour and maxDailyVolume before creating the limiter for each campaign run. Expose these fields in the campaign creation UI so marketing can self-serve conservative limits without an engineering change.
ID: deliverability-engineering.sending-throttling.campaign-rate-limits
Severity: low
What to look for: Count all campaign rate limit fields in the schema. Check whether individual campaigns can be configured with their own sending rate or maximum daily volume, separate from the global system limits. Look for a maxPerHour, sendingRate, or dailyCap field on campaign or batch job schemas. Check if campaign scheduling logic respects these per-campaign limits.
Pass criteria: Campaign records include at least 1 configurable rate or volume field. The dispatching system reads these fields and enforces them per campaign.
Fail criteria: All campaigns share identical global rate limits with no per-campaign configuration. Individual campaigns cannot be throttled more conservatively than the system default.
Skip (N/A) when: The project sends only transactional emails with no concept of campaigns.
Detail on fail: "No per-campaign rate limit configuration found — all campaigns share global system limits with no ability to throttle specific campaigns more conservatively" or "Campaign schema has no rate limit fields"
Remediation: Add rate configuration to campaign schema:
// Campaign schema with configurable rate
model Campaign {
id String @id @default(uuid())
name String
subject String
maxPerHour Int? // null = use system default
maxDailyVolume Int? // null = no campaign-level cap
sendWindow Json? // { start: "09:00", end: "17:00", tz: "America/New_York" }
status CampaignStatus
scheduledAt DateTime?
createdAt DateTime @default(now())
}
// In campaign dispatch worker:
export async function getCampaignRateLimit(campaignId: string): Promise<number> {
const campaign = await db.campaign.findUnique({ where: { id: campaignId } })
return campaign?.maxPerHour ?? DEFAULT_CAMPAIGN_RATE
}