Perfect SPF/DKIM/DMARC. Clean content. Verified domain. And still landing in spam.
The issue isn't what you're sending—it's how fast and when. Email providers track sending patterns, and sudden volume spikes, irregular cadence, and burst sending all trigger spam filters before your content is even analyzed.
Why send cadence matters for deliverability
Gmail, Outlook, and Yahoo don't just analyze individual emails—they analyze your sending behavior over time:
- Volume spikes: 10 emails/day → 10,000 emails/day triggers red flags
- Irregular timing: Silent for weeks, then burst sends on Monday morning
- Time-of-day clustering: All sends concentrated in 3am-5am windows
- Per-recipient frequency: Same user getting 5 emails in 10 minutes
ESPs track these patterns and apply throttling automatically—but inbox providers also watch, and they're less forgiving.
The 5 throttling patterns that protect reputation
1. Gradual volume ramps (for new domains or after breaks)
If you've been silent for weeks or just set up a new sending domain, don't blast 10,000 emails on day one. Ramp up gradually:
- Day 1-3: 50-100 emails/day (high-engagement recipients only)
- Day 4-7: 200-500 emails/day
- Week 2: 1,000-2,000 emails/day
- Week 3+: Full volume (if engagement + deliverability stay healthy)
import { db } from "@/lib/db";
// Track daily send volume and enforce gradual ramp
export async function canSendEmail(domainAge: number): Promise<boolean> {
const today = new Date().toISOString().split("T")[0];
const sentToday = await db.emailLog.count({
where: {
sentAt: { gte: new Date(today) },
},
});
// Gradual ramp-up based on domain age
const dailyLimits: Record<number, number> = {
1: 100,
2: 100,
3: 100,
4: 300,
5: 500,
6: 500,
7: 1000,
14: 2000,
};
const maxSends = dailyLimits[domainAge] || 5000; // Full volume after 2 weeks
if (sentToday >= maxSends) {
console.warn(`Daily limit reached: ${sentToday}/${maxSends}`);
return false;
}
return true;
}2. Per-recipient frequency caps (avoid inbox flooding)
Don't send multiple emails to the same user within short windows. Even transactional emails can trigger spam filters if you send too many in rapid succession.
import { db } from "@/lib/db";
// Enforce minimum gap between emails to same recipient
export async function canSendToRecipient(
email: string,
minGapMinutes: number = 5
): Promise<boolean> {
const cutoff = new Date(Date.now() - minGapMinutes * 60 * 1000);
const recentSend = await db.emailLog.findFirst({
where: {
recipientEmail: email,
sentAt: { gte: cutoff },
},
orderBy: { sentAt: "desc" },
});
if (recentSend) {
console.warn(`Throttled: sent to ${email} ${minGapMinutes}min ago`);
return false;
}
return true;
}3. Time-of-day distribution (spread sends across hours)
Avoid clustering all sends in narrow time windows (especially 3am-5am). Spread sends across business hours to mimic natural human sending patterns.
- Bad: Queue flushes at 4am daily (looks automated/spammy)
- Better: Distribute sends 8am-8pm in recipient's timezone
// Schedule sends during business hours in recipient timezone
export function getOptimalSendTime(recipientTimezone: string): Date {
const now = new Date();
const tz = recipientTimezone || "America/New_York";
// Target 9am-7pm in recipient's timezone
const targetHour = 9 + Math.floor(Math.random() * 10); // 9am-7pm
const targetMinute = Math.floor(Math.random() * 60);
const scheduledTime = new Date(now);
scheduledTime.setHours(targetHour, targetMinute, 0, 0);
// If target time already passed today, schedule for tomorrow
if (scheduledTime < now) {
scheduledTime.setDate(scheduledTime.getDate() + 1);
}
return scheduledTime;
}4. ESP-level rate limits (respect provider throttling)
Even if your domain reputation is perfect, ESPs enforce their own rate limits:
- Resend: 10 requests/second (burst), then throttles
- SendGrid: Varies by plan (typically 600 emails/minute)
- AWS SES: Starts at 1 email/second, increases with reputation
- Postmark: 10 requests/second
// Send with exponential backoff on rate limit errors
export async function sendWithRetry(
emailPayload: any,
maxRetries: number = 3
): Promise<void> {
let attempt = 0;
while (attempt < maxRetries) {
try {
await sendEmail(emailPayload);
return; // Success
} catch (error: any) {
if (error.statusCode === 429) {
// Rate limited
const delayMs = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
console.warn(`Rate limited. Retrying in ${delayMs}ms...`);
await new Promise((resolve) => setTimeout(resolve, delayMs));
attempt++;
} else {
throw error; // Non-retriable error
}
}
}
throw new Error("Max retries exceeded");
}5. Consistency over time (don't disappear then burst)
Irregular sending patterns trigger spam filters. If you typically send 1,000 emails/day, don't go silent for 3 weeks then send 10,000 on Monday.
- Maintain baseline volume: Send a minimum number of emails daily (even if just critical transactional)
- Resume gradually after breaks: If you pause sending for weeks, ramp back up slowly
- Track your own patterns: Log daily send volumes and watch for anomalies
Monitoring: How to know if your throttling is working
Track these metrics weekly to catch cadence issues before they tank deliverability:
- Daily send volume: Should increase gradually (not spike)
- Bounce rate by send hour: Spikes at certain times? Throttle those windows
- Complaint rate: If it jumps after volume increases, slow down
- Delivery latency: Are emails queuing for hours? You might be over-throttling
// Dashboard query: daily send volume and bounce rate trends
export async function getThrottleHealth() {
const last30Days = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const stats = await db.emailLog.groupBy({
by: ["sentDate"],
where: { sentAt: { gte: last30Days } },
_count: { id: true },
_sum: {
bounced: true,
complained: true,
},
});
return stats.map((day) => ({
date: day.sentDate,
sent: day._count.id,
bounceRate: (day._sum.bounced || 0) / day._count.id,
complaintRate: (day._sum.complained || 0) / day._count.id,
}));
}Common throttling mistakes that hurt deliverability
1. No throttling on password resets
Password reset endpoints are frequent abuse targets. Without per-recipient throttling, attackers can spam users (and tank your reputation).
2. Batch sends at midnight
Queueing up 10,000 promotional emails to send at midnight looks automated and spammy. Distribute sends across 12+ hours instead.
3. Ignoring ESP feedback
If your ESP is rate-limiting you, don't work around it—figure out why. You're likely sending too fast for your reputation level.
4. No monitoring after launch
Throttling isn't set-it-and-forget-it. As your volume grows, bounce/complaint rates change, and inbox providers adjust their filters. Review metrics monthly.
Implementation checklist
Here's what to add before your next deploy:
- ✅ Daily volume caps with gradual ramp-up for new domains
- ✅ Per-recipient frequency limits (5-10 minute minimum gap)
- ✅ Time-of-day distribution (spread sends across business hours)
- ✅ Exponential backoff when hitting ESP rate limits
- ✅ Monitoring dashboard for daily volume, bounce rate, complaint rate
For production-ready templates with proper error handling and retry logic, see:
- Password reset template — High-volume security email that needs throttling
- Email verification template — Critical activation email where throttling prevents abuse
- Transactional templates for Next.js — Complete set with built-in deliverability patterns
Related reading
- Domain warm-up strategy — The first 4 weeks of gradual volume increases
- Sender reputation monitoring — Track deliverability metrics that signal throttling issues
- Email rate limiting & throttling — Abuse prevention patterns that protect your sending reputation