When traffic spikes on our Next.js App Router app on Vercel, API routes sending emails hit random TypeError: fetch failed or ECONNRESET errors. How should we handle high concurrency without dropping user emails?
Understanding Connection Reset Errors in Serverless
When Vercel scales out serverless function instances during traffic spikes, hundreds of concurrent function executions open simultaneous TCP connections to the mail API. If the mail endpoint or underlying socket pool gets saturated, socket reuse causes ECONNRESET or fetch failed.
The Solution: Retry Wrapper with Exponential Backoff + Connection Reuse
Wrap outbound fetch calls in an exponential backoff helper, or push email dispatch to an async queue.
// lib/fetch-with-retry.ts
export async function sendEmailWithRetry(payload: object, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const res = await fetch('https://api.autosend.com/v1/mails/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.AUTOSEND_API_KEY}`,
'Content-Type': 'application/json',
'Connection': 'keep-alive'
},
body: JSON.stringify(payload)
});
if (res.ok) return await res.json();
if (res.status !== 429 && res.status < 500) throw new Error(`API error ${res.status}`);
} catch (err) {
if (attempt === maxRetries) throw err;
const delay = Math.pow(2, attempt) * 200 + Math.random() * 100;
await new Promise(r => setTimeout(r, delay));
}
}
}
Production Best Practices:
- Use HTTP Keep-Alive: Include
Connection: keep-alivein headers to reuse underlying TLS sockets across warm lambda executions. - Asynchronous Queues: For non-blocking requests (like welcome sequences), offload to Upstash QStash or Redis/BullMQ so background workers handle throttling without blocking web responses.