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:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp (seconds) when the window resets |
When a rate limit is exceeded, the response also includes:
| Header | Description |
|---|---|
Retry-After | Seconds until the rate limit resets |
Plan limits
Rate limits vary by plan tier. All limits are per organization per minute.
| Plan | General API | Send API |
|---|---|---|
| Free | 100 req/min | 20 req/min |
| Starter | 500 req/min | 100 req/min |
| Growth | 2,000 req/min | 500 req/min |
| Business | 5,000 req/min | 1,000 req/min |
| Enterprise | 10,000 req/min | 2,000 req/min |
Send API limits apply to endpoints that trigger email delivery:
POST /send-- transactional emailPOST /campaigns/:id/send-- campaign sendPOST /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:
{
"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:
curl https://pushmail.dev/api/v1/rate-limit \
-H "Authorization: Bearer pm_live_YOUR_KEY"{
"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-Afterduration, 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
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;
}CAN-SPAM Compliance
PushMail automatically handles CAN-SPAM compliance with physical address insertion, List-Unsubscribe headers (RFC 8058), and one-click unsubscribe processing.
Webhooks
PushMail receives email events from your sending provider and stores them as structured event records. You can also configure webhooks to notify your own application when events occur.