Mailchimp API rate limits: batching + backoff guide
Mailchimp API rate limits work on concurrent connections, not requests per minute. See the batching, backoff, and idempotency patterns that prevent 429s at scale.
Mailchimp’s Marketing API doesn’t use a “requests per minute” cap as its primary throttle — it enforces a concurrent connection limit. That’s why bulk audience imports often fail with HTTP 429 Too Many Requests when you run too many requests in parallel.
This guide shows a practical playbook to import or update large audiences (e.g., 5,000-contact batches) while staying under Mailchimp’s connection limits, using concurrency limits, exponential backoff, idempotency, and monitoring.
import pLimit from "p-limit";
const limit = pLimit(6); // start 2–6, increase only after stable
async function upsertMember(member) {
// Make your Mailchimp request here
// Prefer upserts (PUT) over “create then update”
}
await Promise.all(
members.map((m) => limit(() => upsertMember(m)))
);
Tip: If you’re already using an 8-thread pool with a small per-request delay (like 0.1s), you’re on the right track — but concurrency should still be the first lever to tune.
Step 3: Add exponential backoff (with jitter) on 429 and transient 5xx
Backoff is for the residual failures you still see after limiting concurrency.
Node.js example: exponential backoff wrapper
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function withBackoff(fn, { maxRetries = 5, baseMs = 500 } = {}) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (err) {
const status = err?.response?.status;
// retry 429 and typical transient server errors
const retryable = status === 429 || (status >= 500 && status <= 599);
if (!retryable || attempt === maxRetries) throw err;
const exp = baseMs * Math.pow(2, attempt);
const jitter = Math.floor(Math.random() * 250);
await sleep(exp + jitter);
}
}
}
Operational advice
Start with 3–5 retries.
Use jitter to avoid “thundering herd” retries.
Step 4: Make your writes idempotent (so retries don’t corrupt data)
For bulk imports, retries are guaranteed. Design so that retries are safe:
Use upsert patterns (e.g., PUT member by subscriber hash) rather than “create then update”.
Treat every operation as “set desired state” not “apply a delta”.
Persist a checkpoint (batch index + last processed contact) so you can resume.
If you don’t have a durable checkpoint, a function that crashes at 80% forces you to either:
start over (wasteful), or
guess what’s missing (risky)
Step 5: Reduce requests with the Batch endpoint (when appropriate)
If your workload is “many operations that can be queued asynchronously,” Mailchimp’s Batch endpoint can be a better fit than lots of parallel per-contact calls:
it reduces your need to maintain many concurrent connections
it moves long-running work onto Mailchimp’s infrastructure
it’s easier to monitor batch completion
Use this especially when:
you are regularly syncing thousands of contacts
you’re hitting timeouts or concurrency limits frequently
If Mailchimp calls are triggered by automation platforms, your risk is unexpected concurrency.
Patterns that help:
Delay by queue (single queue key for “Mailchimp”) so events are serialized
A “gatekeeper” step that checks whether a job is already running
One “import worker” endpoint that processes jobs from a queue instead of running imports directly from each zap/scenario
Step 7: Monitoring + alerting (keep it boring in production)
Track these at minimum:
last successful run time
batches attempted / succeeded / failed
count of 429s and 5xx errors
average request latency
how many contacts were skipped due to validation issues
Quality-of-life metric: record a “last successful batch timestamp” you can view quickly (dashboard, spreadsheet, or log query) without digging through raw logs.
Common gotchas (learned the hard way)
Client timeouts don’t mean the server stopped working. Mailchimp may still be processing requests, which keeps your concurrent connections “occupied.”
Empty or invalid fields can fail a whole operation in some update flows. Validate inputs before sending (especially required merge fields).
When you scale from a test list to a real list (tens of thousands), concurrency interactions are what change most dramatically — not your code logic.
Quick checklist
Batch into 1,000–5,000 contact chunks
Set a strict concurrency limit (start small)
Add exponential backoff + jitter for 429/5xx
Use idempotent upserts
Add checkpoints + resume logic
Queue automation triggers (Zapier/Make) to prevent burst traffic
Monitor: last success, failures, 429 rate, latency
Get help building this
Building a bulletproof Mailchimp bulk import usually breaks at the concurrency layer — too many parallel requests slip through, Mailchimp returns 429s, and a naive retry loop makes it worse. If you've hit that wall, book a ZoomFlow session — one of our consultants can debug your batching and backoff strategy live and ship the working version in the same call.
A practical Airtable to Supabase sync plan for new rows, updates, and backfill, using stable record IDs, unique constraints, and UPSERT to prevent duplicates.
RingCentral call queue concurrency limits: find the cap (queue settings, transfer behavior, SIP trunk) and fix it with this step-by-step load-test checklist.