Misar IO Docs

Rate Limits

Per-endpoint rate limits, 429 responses, and Redis-backed headers

Rate Limits

MisarMail enforces two types of limits:

  1. Rate limits — requests per minute per API key (short-term burst protection)
  2. Plan limits — total emails / contacts / etc. per day or month (long-term usage enforcement)

This page covers rate limits. See Plan Limits for usage quotas.

Rate limits are enforced per API key, not per IP address.


Limits by Endpoint

| Endpoint | Window | Limit | Notes | |----------|--------|-------|-------| | POST /v1/send | 1 minute | 100 requests | Per API key | | GET /v1/contacts | 1 minute | 100 requests | Per API key | | POST /v1/contacts | 1 minute | 60 requests | Per API key | | POST /v1/contacts/import | 1 minute | 10 requests | Per API key | | POST /v1/campaigns | 1 minute | 30 requests | Per API key | | POST /v1/ai/subject-lines | 1 minute | 10 requests | Per API key | | POST /v1/validate | 1 minute | 30 requests | Per API key | | POST /v1/channels/* | 1 minute | 50 requests | Per API key | | All other /v1/* | 1 minute | 60 requests | Per API key (default) |


429 Response

When a rate limit is exceeded, the API returns:

HTTP/1.1 429 Too Many Requests
{
  "success": false,
  "error": "Too many requests. Please slow down.",
  "retryAfter": 42
}

retryAfter is the number of seconds until the window resets.

Response Headers

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1708790460
Retry-After: 42

Use the Retry-After header value to schedule your next request rather than polling.


Handling Rate Limits

Always check for 429 and respect Retry-After:

async function sendWithRateLimit(payload: object) {
  const res = await fetch("https://api.misar.io/mail/v1/send", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.MISARMAIL_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  });

  if (res.status === 429) {
    const data = await res.json();
    const retryAfter = data.retryAfter || 60;
    await new Promise(r => setTimeout(r, retryAfter * 1000));
    return sendWithRateLimit(payload); // retry once
  }

  return res.json();
}

Bulk Operations

For bulk sending (e.g., campaigns to thousands of contacts), use the campaign API instead of calling POST /v1/send in a loop. Campaigns are processed asynchronously by the MisarMail queue, which handles rate limiting, retry logic, and ISP throttling automatically.

// ❌ Don't do this for bulk
for (const contact of contacts) {
  await sendEmail({ to: [contact], ... });
}

// ✅ Use campaigns for bulk
await createAndSendCampaign({ segment_id: "...", template_id: "..." });