Rate Limits
Understand API rate limits, email sending limits, and how to handle rate-limited responses
MailLand enforces rate limits at multiple levels to ensure fair usage and platform stability. Limits vary by stream type (transactional vs marketing), authentication method, and plan tier.
API Request Limits
IP-Based Limits
Every request is subject to IP-based rate limiting, applied before authentication. These limits protect against abuse and DDoS attacks.
| Endpoint | Limit | Window |
|---|---|---|
| General API | 300 requests | Per minute |
| Admin login | 10 requests | Per minute |
| Team invitations | 10 requests | Per minute |
| SSO domain checks | 15 requests | Per minute |
User (non-admin) login is not governed by this IP window: see Login Rate Limiting below for the 5-attempts/15-minute lockout that applies to it.
IP-based limits apply per source IP address. If you operate behind a shared proxy, all clients share the same limit.
Login Rate Limiting
Failed login attempts are tracked per IP + email combination:
| Parameter | Value |
|---|---|
| Max failed attempts | 5 |
| Tracking window | 15 minutes |
| Lockout duration | 15 minutes |
After 5 failed login attempts within 15 minutes, the IP + email pair is locked out for 15 minutes. Successful login resets the counter.
API Key Authentication
Failed API key authentication attempts are tracked in two dimensions:
| Dimension | Max Failures | Window | Block Duration |
|---|---|---|---|
| Per key prefix | 10 attempts | 15 minutes | 15 minutes |
| Per IP address | 20 attempts | 15 minutes | 15 minutes |
If either dimension exceeds its threshold, authentication is temporarily blocked.
Email Sending Limits
Email sending limits are enforced per workspace and vary by stream type. Transactional emails (receipts, password resets) get higher limits than marketing emails (newsletters, campaigns). Per-minute is the value that actually comes from your plan (see Policy Summary); per-second and per-hour are derived from it, not set independently, and only day/month use fixed platform defaults.
Transactional Stream
| Window | How it's derived |
|---|---|
| Per second | Plan's per-minute rate ÷ 60 (minimum 1); one request of up to 5 recipients is accepted in a second with no other accepted request, see below |
| Per minute | From your plan (TransactionalRatePerMin) |
| Per hour | Plan's per-minute rate × 60 |
| Per day | 1,000,000 (platform default) |
| Per month | 10,000,000 (platform default) |
Requests with several recipients. Every recipient counts against every window. A transactional request (at most 5 recipients) whose recipient count is above your per-second allowance is still accepted if no other request was accepted in that second; another request in the same second gets 429 with Retry-After: 1. For example, on a plan of 60 per minute (1 per second), one request to 3 recipients is accepted, and another request in that second is refused until the next second. The per-minute, per-hour, per-day and per-month limits, warm-up and your monthly quota still count all 3 recipients. SMTP applies the same rule to each message, counting its unique RCPT TO addresses.
Marketing Stream
| Window | How it's derived |
|---|---|
| Per second | Not checked: marketing sends always skip the per-second check, batch or not |
| Per minute | From your plan (MarketingRatePerMin) |
| Per hour | Plan's per-minute rate × 60 |
| Per day | 100,000 (platform default) |
| Per month | 1,000,000 (platform default) |
Per-minute rates are plan-specific: check your current limits via the Billing page, which surfaces the GET /v1/account/subscription/policy-summary API route.
Stream Priority
Transactional emails have higher priority (priority 1) than marketing emails (priority 2). When the system is under load, transactional emails are processed first.
Batch Operations
Batch sending operations (e.g. POST /v1/transactional/send-batch) are marketing-only and skip per-second rate checks the same way every marketing send does, but they are still subject to per-minute, per-hour, per-day, and per-month limits.
Workspace Sending Limits
Verification level does not set a volume cap: it gates which features are available. Your daily/monthly send volume comes from your plan (see Policy Summary), not from your verification level.
| Verification Level | Transactional | Marketing | Inbound |
|---|---|---|---|
| Sandbox | Allowlisted recipients only | No | No |
| BasicVerified / SenderVerified | Yes | No | Yes |
| BusinessVerified | Yes | Yes | Yes |
Workspace limits are checked before stream rate limits. You can view your current limits and usage on the Billing page, which is backed by GET /v1/account/subscription/policy-summary.
IP Warmup
Warmup is per plan family, not a fixed schedule. Free plans run 6 stages of 5 days each (50 → 100 → 250 → 500 → 750 → 1,000/day) for 30 days total; Pro plans run 500 → 1,000 → 2,500 → 5,000 → 10,000 → 25,000/day over 14 days; Max plans have no warmup. See your plan's warmup schedule via the Billing page above.
Rate Limit Headers
Most API responses include:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Requests remaining in the current window |
An IP-based 429 additionally includes:
| Header | Description |
|---|---|
X-RateLimit-Reset | ISO-8601 UTC timestamp when the current window resets |
Retry-After | Number of seconds to wait before retrying |
Handling 429 Responses
When you receive a 429 Too Many Requests response, the body contains details about which limit was exceeded:
{
"code": "IP_RATE_LIMIT_EXCEEDED",
"message": "Too many requests from this IP. Retry after 45 seconds.",
"details": {
"limit": 300,
"current": 301,
"retryAfter": 45,
"resetAt": "2026-02-18T12:35:00Z"
}
}For email sending rate limits:
{
"code": "RATE_LIMIT_EXCEEDED",
"message": "Per-minute rate limit exceeded (transactional)"
}The message text above is an example: it's generated from the policy engine's decision reason and is not a guaranteed literal string. Match on code, not message.
Retry Strategy
#!/bin/bash
MAX_RETRIES=3
ATTEMPT=0
while [ $ATTEMPT -le $MAX_RETRIES ]; do
RESPONSE=$(curl -s -D - -o /tmp/body -w "%{http_code}" -X POST https://api.mail.land/v1/transactional/send \
-H "X-API-Key: ml_pet_Ab3Kx9mN2pQ7rT4wL8yD..." \
-H "Content-Type: application/json" \
-d '{ "from": {"email": "[email protected]"}, "to": [{"email": "[email protected]"}], "subject": "Hello", "htmlContent": "<p>Hi</p>" }')
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
if [ "$HTTP_CODE" -ne 429 ]; then
cat /tmp/body
exit 0
fi
# Read the Retry-After header, not the response body - not every 429 has a `details` block.
RETRY_AFTER=$(echo "$RESPONSE" | grep -i '^Retry-After:' | tr -d '\r' | cut -d' ' -f2)
RETRY_AFTER=${RETRY_AFTER:-60}
ATTEMPT=$((ATTEMPT + 1))
echo "Rate limited. Retrying in ${RETRY_AFTER}s (attempt ${ATTEMPT}/${MAX_RETRIES})"
sleep "$RETRY_AFTER"
done
echo "Max retries exceeded"
exit 1Best Practices
Monitor your usage. Check the X-RateLimit-Remaining header proactively to throttle requests before hitting limits.
Use exponential backoff. When retrying after a 429, always respect the Retry-After header. If not present, use exponential backoff starting at 1 second.
Prefer batch endpoints. For high-volume sending, use POST /v1/transactional/send-batch instead of calling POST /v1/transactional/send in a loop. Batch endpoints skip per-second checks and are more efficient.
Separate transactional and marketing. Use separate API credentials for transactional and marketing emails. This ensures marketing sending never impacts transactional delivery.
Distribute sending over time. For large campaigns, MailLand automatically paces delivery. For API-driven bulk sends, spread requests evenly rather than sending in bursts.
Password Reset Limits
Password reset requests have separate rate limits:
| Parameter | Value |
|---|---|
| Cooldown between requests | 60 seconds |
| Max requests per day | 5 |
Next Steps
- Error Codes: complete error code reference
- API Keys: create and manage API credentials
- Billing: view your current usage and limits