Rate Limits
The EmailSendX API enforces rate limits per API key to ensure fair usage and platform stability. Understanding how to read rate limit headers and handle 429 responses gracefully is key to building reliable integrations.
Rate Limit Tiers
Rate limiting applies per API key using a sliding window algorithm. The current limits are:
| Limit | Window | Applies to |
|---|---|---|
| 120 requests | Per minute (sliding window) | Per API key |
The sliding window means the limit is evaluated over the last 60 seconds, not a fixed clock-minute boundary. This prevents bursting at the start of each minute.
Bulk endpoints reduce requests
POST /contacts/bulk) to upsert up to 1,000 contacts in a single API call instead of one call per contact. Prefer bulk operations whenever you're processing large datasets.Rate Limit Headers
Every API response includes the following headers so you can monitor your current usage:
| Header | Example | Description |
|---|---|---|
| X-RateLimit-Limit | 120 | Maximum requests allowed per minute |
| X-RateLimit-Remaining | 87 | Requests remaining in the current window |
| X-RateLimit-Reset | 1745344800 | Unix timestamp when the window resets |
| Retry-After | 14 | Only on 429 — seconds to wait before retrying |
Example response headers:
HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1745344800Example 429 response headers and body:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1745344800
Retry-After: 14
{
"error": "Rate limit exceeded. Retry after 14 seconds."
}Handling 429 Responses
When you receive a 429 response, read the Retry-After header to know exactly how many seconds to wait before your next request. Do not retry immediately — you will receive another 429.
async function apiRequest(url, options = {}, retries = 3) {
const response = await fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${process.env.EMAILSENDX_API_KEY}`,
'Content-Type': 'application/json',
...options.headers,
},
});
if (response.status === 429 && retries > 0) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '10', 10);
console.log(`Rate limited. Waiting ${retryAfter}s before retry...`);
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
return apiRequest(url, options, retries - 1);
}
if (!response.ok) {
const err = await response.json();
throw new Error(err.error || `HTTP ${response.status}`);
}
return response.json();
}
// Usage
const { data } = await apiRequest(
'https://emailsendx.com/api/v1/contacts'
);Best Practices
Follow these guidelines to stay well within rate limits and build robust integrations:
Use bulk endpoints
The POST /contacts/bulk endpoint accepts up to 1,000 contacts per call. If you need to import 10,000 contacts, that is 10 API calls instead of 10,000.
Cache responses where possible
List endpoints (contacts, lists, segments) return data that changes infrequently. Cache the responses in your application layer and only re-fetch when data is likely stale.
Implement exponential backoff
For 5xx errors, use exponential backoff: wait 1s, then 2s, then 4s, then 8s before retrying. Combine with the Retry-After header for 429 responses.
Monitor X-RateLimit-Remaining proactively
Check the X-RateLimit-Remaining header. If it drops below a threshold (e.g., 10), introduce a delay between requests rather than waiting for a 429.
Use webhooks instead of polling
Instead of polling the campaigns or contacts API for changes, set up webhooks to receive real-time push notifications when events occur.
Need a higher rate limit?
Contact us if your integration requires higher throughput — we can discuss custom arrangements for high-volume use cases.