Skip to main content

How rate limiting works

Gorillaa Mail enforces rate limits per API key and per organization to ensure fair usage and platform stability. When you exceed a limit, the API returns 429 Too Many Requests. All authenticated responses include rate limit headers:
HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUTC epoch timestamp when the window resets

Per-endpoint limits

EndpointLimitWindow
POST /v1/emails100 requests1 hour
POST /v1/emails/batch10 requests1 minute
GET /v1/emails1,000 requests1 hour
GET /v1/emails/:id1,000 requests1 hour
POST /v1/domains10 requests1 hour
GET /v1/domains100 requests1 hour
POST /v1/domains/:id/verify10 requests1 hour
POST /v1/domains/:id/rotate-dkim10 requests1 hour
GET /v1/stats/*100 requests1 hour
POST /v1/api-keys10 requests1 hour
GET /v1/api-keys100 requests1 hour
GET /v1/privacy/export3 requests24 hours
POST /v1/privacy/delete1 request24 hours
POST /v1/gdpr/export3 requests24 hours
POST /v1/gdpr/delete-request1 request24 hours
POST /v1/suppressions100 requests1 hour
GET /v1/suppressions100 requests1 hour
GET /v1/webhooks100 requests1 hour
POST /v1/webhooks10 requests1 hour
Default read endpoints1,000 requests1 hour
Default write endpoints100 requests1 hour

Organization-wide limit

In addition to per-endpoint limits, there is a global organization-wide cap:
LimitWindow
All endpoints combined10,000 requests1 hour

Test mode limits

API keys prefixed with grl_test_ have reduced limits:
LimitValue
Global rate limit500 requests/hour
Per-key send limit20 emails/hour
Daily send limit (per domain)100 emails/day
Lifetime send limit (per domain)300 emails total
Test mode daily/lifetime limits count each recipient separately. An email to 5 recipients counts as 5 against the limit.

Handling rate limits

When you receive a 429 response:
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Try again later.",
    "suggestion": "Wait until X-RateLimit-Reset and retry"
  }
}
  1. Read the X-RateLimit-Reset header to know when the window resets
  2. Use exponential backoff with jitter for retries
  3. Include an X-Idempotency-Key for send requests to prevent duplicates on retry
async function handleRateLimit(response) {
  if (response.status === 429) {
    const resetEpoch = parseInt(response.headers.get("X-RateLimit-Reset"));
    const waitMs = (resetEpoch * 1000) - Date.now();

    if (waitMs > 0) {
      console.log(`Rate limited. Retrying in ${Math.ceil(waitMs / 1000)}s`);
      await new Promise((r) => setTimeout(r, waitMs));
    }

    // Retry the request
    return true;
  }
  return false;
}

Monitoring your usage

Check remaining quota before making requests:
# The response headers on any authenticated request show your limits:
curl -I https://api.mail.gorillaa.one/v1/emails \
  -H "Authorization: Bearer YOUR_API_KEY"

# Response headers:
# X-RateLimit-Limit: 1000
# X-RateLimit-Remaining: 847
# X-RateLimit-Reset: 1739095200

Tips for staying within limits

Instead of calling POST /v1/emails 100 times, use POST /v1/emails/batch with up to 100 emails in a single request.
If you poll GET /v1/emails/:id for delivery status, cache results for terminal states (delivered, bounced, failed).
For aggregate delivery metrics, use GET /v1/stats instead of fetching individual email statuses.
Avoid burst patterns. Distribute API calls evenly throughout the hour window.