PushMail.dev

Rate Limits

PushMail enforces per-organization rate limits to protect platform stability. Every API response includes rate limit headers so you can monitor your usage in real time.

Overview

All authenticated API requests are rate-limited per organization. Limits are based on your plan tier and use a fixed-window counter (per minute). Every API response includes headers that tell you your current usage.

Rate limit headers

Every authenticated API response includes these headers:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp (seconds) when the window resets

When a rate limit is exceeded, the response also includes:

HeaderDescription
Retry-AfterSeconds until the rate limit resets

Plan limits

Rate limits vary by plan tier. All limits are per organization per minute.

PlanGeneral APISend API
Free100 req/min20 req/min
Starter500 req/min100 req/min
Growth2,000 req/min500 req/min
Business5,000 req/min1,000 req/min
Enterprise10,000 req/min2,000 req/min

Send API limits apply to endpoints that trigger email delivery:

  • POST /send -- transactional email
  • POST /campaigns/:id/send -- campaign send
  • POST /campaigns/:id/schedule -- campaign schedule

All other endpoints use the General API limit.

429 Too Many Requests

When you exceed your rate limit, the API returns a 429 status code:

429 response
{
  "error": "Rate limit exceeded. Please retry after the reset time.",
  "retryAfter": 42,
  "limit": 500,
  "resetAt": 1709312520
}

The Retry-After header tells you exactly how many seconds to wait before retrying.

Checking your rate limit status

You can check your current rate limit usage without counting against your limit by calling:

Check rate limit status
curl https://pushmail.dev/api/v1/rate-limit \
  -H "Authorization: Bearer pm_live_YOUR_KEY"
Response
{
  "data": {
    "plan": "starter",
    "general": {
      "limit": 500,
      "used": 23,
      "remaining": 477,
      "resetAt": 1709312520
    },
    "send": {
      "limit": 100,
      "used": 5,
      "remaining": 95,
      "resetAt": 1709312520
    },
    "windowSeconds": 60
  }
}

Best practices

  • Read the headers -- Every response includes rate limit headers. Use them to throttle your requests proactively.
  • Implement exponential backoff -- When you receive a 429, wait for the Retry-After duration, then retry with increasing delays.
  • Batch operations -- Use bulk endpoints (like POST /contacts/bulk) to reduce the number of requests.
  • Cache responses -- Cache GET responses client-side to avoid unnecessary API calls.
  • Monitor in the dashboard -- The API Keys page shows real-time rate limit usage for your organization.

Example: handling rate limits in code

Handling rate limits
async function apiCall(url, options) {
  const response = await fetch(url, options);

  // Check rate limit headers
  const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
  const resetAt = parseInt(response.headers.get('X-RateLimit-Reset') || '0');

  if (response.status === 429) {
    const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
    console.log(`Rate limited. Retrying in ${retryAfter}s`);
    await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
    return apiCall(url, options); // Retry
  }

  // Proactive throttling: slow down when approaching the limit
  if (remaining < 10) {
    const waitMs = (resetAt - Math.floor(Date.now() / 1000)) * 1000;
    console.log(`Low on requests (${remaining} left). Waiting ${waitMs}ms`);
    await new Promise(resolve => setTimeout(resolve, waitMs));
  }

  return response;
}

On this page