Rate Limits
Per-endpoint rate limits, 429 responses, and Redis-backed headers
MisarMail enforces two types of limits:
- Rate limits — requests per short window (burst protection)
- 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 keyed by the authenticated user when available, otherwise by client IP. Because most /v1/* endpoints apply the limit before resolving your API key, they are effectively enforced per client IP. POST /v1/send additionally applies a per-API-key burst limit.
Limits by Endpoint
| Endpoint | Window | Limit | Notes |
|---|---|---|---|
POST /v1/send | 1 minute | 100 requests | Per IP |
POST /v1/send | 1 hour | 100 requests | Per API key (burst) |
POST /v1/ai/subject-lines | 1 minute | 10 requests | Per IP |
All other /v1/* (contacts, campaigns, import, validate, templates, …) | 1 minute | 100 requests | Per IP |
Both limits on POST /v1/send apply — a request must pass the 100/minute check and the 100/hour per-key check.
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 try again later.",
"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: 42Use 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: "..." });