Docs/API Reference/Rate Limits

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:

LimitWindowApplies to
120 requestsPer 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

Use the bulk contact import endpoint (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:

HeaderExampleDescription
X-RateLimit-Limit120Maximum requests allowed per minute
X-RateLimit-Remaining87Requests remaining in the current window
X-RateLimit-Reset1745344800Unix timestamp when the window resets
Retry-After14Only on 429 — seconds to wait before retrying

Example response headers:

bash
HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1745344800

Example 429 response headers and body:

bash
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.