Rate Limits

Understanding API rate limits, headers, and best practices for handling rate-limited requests

Lumail enforces rate limits to ensure fair usage and protect service quality for all users. This page explains how rate limiting works and how to handle rate-limited requests.

Rate Limits by Plan

API requests are rate-limited per organization based on your subscription plan:

PlanRequests per Minute
Free100
Premium700
Enterprise2,000

Additionally, all requests are subject to an IP-based rate limit of 500 requests per second to prevent abuse.

Rate Limit Headers

Authenticated REST API responses include the current organization quota. Authentication failures still advertise the minimum API policy so clients can plan before presenting a token.

HeaderDescription
RateLimit-PolicyNamed quota and 60-second window
RateLimitRemaining requests and seconds until reset
RateLimit-LimitMaximum requests allowed per minute
RateLimit-RemainingRemaining requests in the current window
RateLimit-ResetSeconds until the current window resets
X-RateLimit-*Legacy compatibility values for existing clients

Example headers:

RateLimit-Policy: "lumail-api";q=100;w=60
RateLimit: "lumail-api";r=95;t=42
RateLimit-Limit: 100
RateLimit-Remaining: 95
RateLimit-Reset: 42

Rate Limit Exceeded Response

When you exceed the rate limit, the API returns a 429 Too Many Requests response:

{
  "message": "Too many requests",
  "docs": "https://lumail.io/docs/api-reference/api-limits"
}

Additional headers on 429 responses:

HeaderDescription
Retry-AfterSeconds to wait before retrying

Handling Rate Limits

Best Practices

  1. Monitor headers - Track RateLimit-Remaining to know when you're approaching the limit
  2. Implement backoff - When rate limited, wait for the Retry-After duration before retrying
  3. Batch requests - Combine multiple operations into fewer API calls where possible
  4. Cache responses - Store frequently accessed data locally to reduce API calls

Exponential Backoff Example

async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url, options);

    if (response.ok) {
      return response;
    }

    if (response.status === 429) {
      // Get retry delay from header or calculate exponentially
      const retryAfter = response.headers.get("Retry-After");
      const waitTime = retryAfter
        ? parseInt(retryAfter, 10) * 1000
        : Math.pow(2, i) * 1000;

      console.log(`Rate limited. Waiting ${waitTime}ms before retry...`);
      await new Promise((resolve) => setTimeout(resolve, waitTime));
      continue;
    }

    throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  }

  throw new Error("Max retries exceeded");
}

Checking Rate Limit Status

Before making requests, you can check your remaining quota:

const response = await fetch("https://lumail.io/api/v1/tags", {
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
  },
});

const limit = response.headers.get("RateLimit-Limit");
const remaining = response.headers.get("RateLimit-Remaining");
const resetAfter = response.headers.get("RateLimit-Reset");

console.log(`${remaining}/${limit} requests remaining`);
console.log(`Resets in ${resetAfter} seconds`);

Increasing Your Rate Limit

If you need higher rate limits:

  1. Upgrade your plan - Premium and Enterprise plans include higher limits
  2. Contact support - For custom enterprise needs, we can discuss tailored solutions

Common Scenarios

Bulk Operations

For bulk imports or updates, consider:

  • Using the POST /api/v1/subscribers endpoint which handles upserts
  • Spreading requests over time instead of bursting
  • Processing in batches with delays between batches
async function bulkImport(subscribers, batchSize = 50, delayMs = 1000) {
  for (let i = 0; i < subscribers.length; i += batchSize) {
    const batch = subscribers.slice(i, i + batchSize);

    // Process batch in parallel
    await Promise.all(
      batch.map((sub) =>
        fetch("https://lumail.io/api/v1/subscribers", {
          method: "POST",
          headers: {
            Authorization: "Bearer YOUR_API_TOKEN",
            "Content-Type": "application/json",
          },
          body: JSON.stringify(sub),
        }),
      ),
    );

    // Wait between batches to stay under rate limit
    if (i + batchSize < subscribers.length) {
      await new Promise((resolve) => setTimeout(resolve, delayMs));
    }
  }
}

Webhooks

When receiving webhooks and calling the API:

  • Process webhook events asynchronously
  • Implement a queue to control request rate
  • Use triggerWorkflows: false when updating subscribers to avoid cascading API calls