Rate limits exist for a reason. They protect infrastructure, prevent API abuse, and make sure every customer on a platform gets fair access. But when your application hits a 429 Too Many Requests in production, none of that context matters much. What matters is knowing exactly what the limits are, why you're hitting them, and how to handle them without disrupting your users.
This guide takes a hybrid approach. It covers the API mechanics (HTTP 429, exponential backoff, batch sending) and the deliverability implications (sender reputation, deferred emails, throttling on the receiving side). The two are inseparable in practice. How you architect your sends determines both whether they go out and whether they land. Code examples throughout in Python, PHP, and raw HTTP.
Table of Contents
- What is API rate limiting?
- What is email throttling and how does it differ from rate limiting?
- How email throttling affects email deliverability
- Rate limiting algorithms: fixed window, sliding log, and token bucket
- Brevo's email API rate limits
- Handling HTTP 429 correctly
- Batch sending: the right way to handle volume
- SMTP rate limits and throttling
- How to prevent email throttling issues
- Proactive strategies to avoid hitting API rate limits
- Common mistakes that cause unnecessary 429s
- Rate limits at scale: planning your architecture
- Putting it together
- FAQ
What is API rate limiting?
API rate limiting is a control mechanism that restricts how many requests a client can make to an API within a given time window. When you exceed the limit, the server rejects your request with an HTTP 429 Too Many Requests status code instead of processing it.
For email APIs, rate limits operate on two dimensions. First, there's the request rate, which is how many API calls per second or per hour you can make to a given endpoint. Second, there's the send volume, which is how many emails per hour or per day your account can push out.
These are independent constraints. You can hit a per-second request rate limit even if you're well under your hourly email volume limit. That's why naive "send them all" loops fail even when you theoretically have enough quota.
Rate limiting also protects against DDoS attacks and API abuse. Without it, a single user or compromised API key could overwhelm a shared sending infrastructure, degrading service for everyone. Email service providers use rate limits to enforce their business model and keep server resources stable during high traffic periods.
What is email throttling and how does it differ from rate limiting?
Email throttling and API rate limiting are related but not identical, and conflating them leads to debugging in the wrong place.
API rate limiting is enforced by the email API on the sender's side. It controls how many requests your application can make, typically measured in emails per hour or API calls per second. Exceed the limit and you get an HTTP 429 immediately.
Email throttling, on the other hand, is a flow control mechanism applied by the receiving server (or by the sending server itself) to control the rate at which email messages are accepted. A receiving mail server may only accept a fixed number of messages from a particular sender's IP address within a specific timeframe. When your sending server tries to deliver mail too quickly, the receiving server temporarily defers the messages rather than rejecting them outright.
The distinction matters. A rate limit error is immediate and explicit (HTTP 429 or SMTP 421). A throttled email is typically deferred. The sending server queues the message and retries automatically. You may not notice throttling until you check delivery logs and find messages sitting in a deferred state hours after the initial delivery attempt.
How does email throttling work in practice?
When a sending server connects to a receiving mail server and submits messages faster than the receiver is willing to accept them, the receiving server responds with a 421 or 450 SMTP code, which is a soft, temporary rejection. The sending server is expected to back off and retry the delivery attempt after a delay.
From a developer's perspective, a throttled email behaves like a soft bounce in the short term. The message isn't delivered immediately, but it hasn't failed permanently either. A well-configured sending infrastructure (including Brevo's) handles these deferrals automatically and retries at appropriate intervals.
Where throttling becomes a problem is when it's persistent. If your IP address keeps sending too fast or triggering spam filters on the receiving side, temporary deferrals can become permanent failures, and email messages end up in the spam folder rather than the recipient's inbox.
How email throttling affects email deliverability
Understanding throttling matters beyond just queuing theory. It has direct implications for sender reputation and long-term email deliverability.
Sender reputation and IP address history
Mailbox providers like Gmail, Outlook, and Yahoo evaluate your sending behavior when deciding whether email messages reach the recipient's inbox or the spam folder. Sending too much volume too quickly, especially from a new IP address, is a strong spam signal.
A new IP has no sending history. Mailbox providers don't know whether it belongs to a legitimate business or a suspected spammer. If you send high volumes immediately from a new IP, even with clean lists, you'll trigger aggressive throttling on the receiving mail servers and damage your sender reputation before it's established. This is why IP warm-up matters. Gradually increasing sending volume over 4 to 8 weeks lets receiving servers build a trust history for your IP address.
Soft bounces, deferred emails, and bounce rates
When a receiving server throttles your messages, you'll see a mix of outcomes in your email logs. Deferred emails are messages queued for retry that haven't failed yet. Soft bounces are temporary delivery failures where the sending server will retry. Hard bounces are permanent failures from invalid addresses, and you should stop sending to those addresses immediately.
Elevated soft bounce and deferred email rates from throttling inflate your overall bounce rates. If bounce rates stay high, mailbox providers start treating your sending IP address as a problem sender, making throttling worse. It's a feedback loop that's hard to break without reducing volume and cleaning your list.
Transactional email traffic vs. marketing email campaigns
Throttling affects transactional email traffic and marketing email campaigns differently. For transactional emails (password resets, order confirmations, 2FA codes), throttling delay is directly user-visible. A user waiting 10 minutes for a password reset because your sending server is being throttled? That's a support ticket waiting to happen.
For email marketing campaigns, some throttling is acceptable. A newsletter can take a few hours to fully deliver. The problem comes when marketing email volume crowds out transactional email traffic during high traffic periods. Keeping these on separate sending streams (and ideally separate IP addresses or subdomains) isolates the risk.
Read more: The best transactional email services compared.
Rate limiting algorithms: fixed window, sliding log, and token bucket
Understanding how rate limits are actually implemented helps you design better retry and throttling logic on the client side.
Fixed window
This is the simplest approach. Allow a fixed number of requests per fixed time window (for example, 600 emails per hour, reset at the top of each hour). The problem is burst behavior. You can send all 600 emails in the first minute, then sit blocked for the remaining 59.
When you're hitting a fixed window limit, the best strategy is to spread your sends evenly rather than bursting. Sending 10 emails per minute is equivalent to 600 per hour in total throughput but dramatically less likely to hit the ceiling.
Sliding log
A sliding log tracks each request with a timestamp and checks whether the number of requests in the last N seconds exceeds the limit. This is stricter than a fixed window because there's no "reset at the top of the hour" to exploit, but it's more predictable. Implementing a client-side sliding log counter lets you proactively stay under the limit rather than reacting to 429s.
Token bucket
The token bucket pattern is the most flexible. A bucket holds a maximum number of tokens (emails you can send), tokens are added at a steady rate, and each send consumes one token. If the bucket is empty, the send is deferred until a token is available.
A common client-side pattern for implementing API rate limiting against Brevo's email API is the token bucket. It naturally smooths burst traffic, handles uneven load, and gives you a clean abstraction to build send queues on top of.
A simple token bucket in Python with ratelimit:
from ratelimit import limits, sleep_and_retry
CALLS_PER_HOUR = 550 # Stay under the 600/hr limit with headroom
PERIOD = 3600 # 1 hour in seconds
@sleep_and_retry
@limits(calls=CALLS_PER_HOUR, period=PERIOD)
def send_one_email(client, email_params):
return client.transactional_emails.send_transac_email(**email_params)
Brevo's email API rate limits
Brevo's rate limits documentation defines limits per endpoint category.
Transactional email sending (POST /v3/smtp/email):
Send limits vary by plan. Check your account's current limits via GET /v3/account or the Brevo dashboard. Lower limits apply on the free plan (300 emails/day). You can include a maximum of 2,000 total recipients per API request, and a maximum of 99 recipients per message version when using batch mode. The params object is capped at 100KB.
Log and stats retrieval:
GET /v3/smtp/emails allows 2 requests per second (7,200/hr) on self-service plans and 3 requests per second (10,800/hr) on Enterprise. GET /v3/smtp/statistics/reports has the same limits, with a date range capped at 90 days per query.
Batch sending throughput: Using messageVersions (sending up to 1,000 personalized versions in a single API call), the batch endpoint can be called up to 5 times per minute. That gives a maximum throughput of 30,000 emails per hour, because you're not making 1,000 API calls, you're making one. A maximum of 1,000 versions per call applies.
For SMTP relay (smtp-relay.brevo.com, port 587), the hourly send limits match the REST API, but batch sending isn't available. If volume is your concern, use the REST API. SMTP also has a daily sending quota that mirrors your account plan.
Keep in mind that there are different rate limits per endpoint type. Exhausting your log retrieval limit doesn't affect your sending limit, and vice versa.
Handling HTTP 429 correctly
Reading Brevo's rate limit headers
Brevo uses its own set of rate limit headers, not the generic Retry-After header. These headers are included in all API responses, not just 429s, so you can monitor your usage proactively before hitting the ceiling.
A typical 429 response looks like this:
HTTP/1.1 429 Too Many Requests
x-sib-ratelimit-limit: 600
x-sib-ratelimit-remaining: 0
x-sib-ratelimit-reset: 45
Content-Type: application/json
{
"code": "too_many_requests",
"message": "API call rate limit exceeded"
}
Don't hardcode a sleep value. Read x-sib-ratelimit-reset for the exact wait time in seconds.
Python (raw requests):
import requests
import time
response = requests.post(
"https://api.brevo.com/v3/smtp/email",
headers={"api-key": API_KEY, "Content-Type": "application/json"},
json=payload
)
if response.status_code == 429:
reset_in = int(response.headers.get("x-sib-ratelimit-reset", 60))
print(f"Rate limit exceeded. Waiting {reset_in}s...")
time.sleep(reset_in)
# retry the request
elif response.status_code == 201:
print("Sent:", response.json()["messageId"])
else:
print(f"Error {response.status_code}: {response.text}")
You can also check x-sib-ratelimit-remaining before a bulk send to confirm you have enough capacity. Log all error messages from 4xx and 5xx responses since they often contain actionable detail beyond the status code.
Quick reference for 429 troubleshooting
Exponential backoff with retry
For resilient production code, combine x-sib-ratelimit-reset with exponential backoff. The reset header gives you the minimum wait. Exponential backoff adds jitter to avoid thundering herd problems when multiple workers all retry at the same time.
Python (Brevo SDK v4):
import time
from brevo import Brevo
from brevo.core.api_error import ApiError
client = Brevo(api_key="YOUR_API_KEY")
def send_with_retry(email_params, max_retries=3):
for attempt in range(max_retries):
try:
return client.transactional_emails.send_transac_email(**email_params)
except ApiError as e:
if e.status_code == 429:
wait = 2 ** attempt * 10 # 10s, 20s, 40s
print(f"Rate limit hit. Waiting {wait}s (attempt {attempt+1}/{max_retries})...")
time.sleep(wait)
else:
raise # Re-raise non-rate-limit errors
raise Exception("Max retries exceeded")
The Brevo Python SDK v4 also includes built-in auto-retry with exponential backoff (default: 2 retries). You can configure this at the client level:
from brevo import Brevo
client = Brevo(
api_key="YOUR_API_KEY",
timeout=20.0,
)
# Override per request
client.transactional_emails.send_transac_email(
...,
request_options={"max_retries": 3, "timeout_in_seconds": 10}
)
PHP (Brevo SDK v4):
<?php
use Brevo\Client\Brevo;
use Brevo\Client\Exceptions\BrevoApiException;
function sendWithRetry(Brevo $client, $request, int $maxRetries = 3) {
for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
try {
return $client->transactionalEmails->sendTransacEmail($request);
} catch (BrevoApiException $e) {
if ($e->getCode() === 429) {
$wait = pow(2, $attempt) * 10; // 10s, 20s, 40s
echo "Rate limit hit. Waiting {$wait}s (attempt " . ($attempt+1) . "/{$maxRetries})...\n";
sleep($wait);
} else {
throw $e;
}
}
}
throw new RuntimeException("Max retries exceeded");
}
Batch sending: the right way to handle volume
If you need to send emails to hundreds or thousands of recipients (onboarding sequences, order confirmations, notifications), do not loop and call POST /v3/smtp/email once per recipient. That's the fastest way to exhaust your rate limit and the most common cause of unnecessary throttling for developers new to email APIs.
Use messageVersions to batch up to 1,000 personalized emails in a single API call.
import requests
payload = {
"sender": {"email": "[email protected]", "name": "My App"},
"subject": "Your order is confirmed",
"htmlContent": "Hi {{params.name}}, your order #{{params.orderId}} is confirmed.",
"messageVersions": [
{
"to": [{"email": "[email protected]", "name": "Alice"}],
"params": {"name": "Alice", "orderId": "1001"}
},
{
"to": [{"email": "[email protected]", "name": "Bob"}],
"params": {"name": "Bob", "orderId": "1002"}
}
# ... up to 1,000 versions per call
]
}
response = requests.post(
"https://api.brevo.com/v3/smtp/email",
headers={"api-key": API_KEY, "Content-Type": "application/json"},
json=payload
)
When using messageVersions, do not include a top-level to field. It conflicts with the versioned recipients. Each version defines its own recipients independently.
This approach reduces your rate limit exposure by up to 1,000x compared to individual sends. For a campaign of 10,000 emails, you go from 10,000 API calls to 10 calls. With the batch endpoint capped at 5 calls per minute, you can comfortably deliver 30,000 emails per hour without hammering the API.
SMTP rate limits and throttling
If you're using Brevo's SMTP relay (smtp-relay.brevo.com, port 587) rather than the REST API, the hourly send limits are the same. The differences worth knowing about:
There's no batch support. Each SMTP connection sends one message to the sending server, which forwards it onward. Each delivery attempt is independent. There's also no Retry-After header. SMTP uses a different error model where SMTP error codes (421, 450) signal temporary failures, and your mail client or SMTP library handles the retry schedule. You'll also want to watch connection limits. Avoid opening too many parallel SMTP connections. Use a single persistent connection with RSET between messages where your mail library supports it. Too many open ports or simultaneous connections from the same IP address can itself trigger throttling on the receiving side.
For high-volume transactional email traffic, the REST API with batch sending is generally the better fit. SMTP relay works better for low-volume application mail (contact forms, system alerts, CMS-generated messages) or legacy integrations where switching to an API isn't practical.
One credential note worth mentioning: the SMTP relay uses an SMTP key, not your API key. Both are generated under Settings, then SMTP & API, but they are separate credentials. Using the wrong one is a very common source of confusing error messages during initial setup.
Read more: The best free SMTP servers compared.
How to prevent email throttling issues
Preventing email throttling is partly about your API usage patterns and partly about your sending reputation over time. Here are a few recommendations that address both.
Warm up new IP addresses gradually
Sending high volumes from a new IP address immediately is one of the surest ways to trigger aggressive throttling from receiving mail servers. Start low, a few hundred emails per day, and increase volume gradually over 4 to 8 weeks. Brevo's shared IP infrastructure handles this for most users automatically, but if you're on a dedicated IP, warm-up is your responsibility.
Keep your list clean
Sending emails to invalid addresses generates hard bounces. Sending to unengaged users increases spam complaints. Both signals tell receiving servers that you're not a careful sender, and result in tighter rate limits being applied to your IP address and domain. Scrub your list before large email marketing campaigns and remove hard bounces immediately after each send.
Separate transactional and marketing traffic
Use distinct subdomains and, if possible, separate IP addresses for transactional email traffic and email marketing campaigns. This protects your transactional sender reputation from the effects of a poorly received marketing send. Recipients respond differently to promotional mail versus transactional mail, and mailbox providers account for this in their filtering.
Monitor delivery rates and deferred email counts
Track your delivery rate, soft bounce rate, and deferred email counts in Brevo's dashboard or via the GET /v3/smtp/statistics/reports endpoint. A rising deferred email count before you hit hard 429 errors is an early warning that a receiving server is starting to throttle you. Act on it before it compounds.
Contact support before large one-time sends
If you're planning a send to your entire list (an annual digest, a product launch, a re-engagement campaign), contact your support team before sending. Many email service providers can increase temporary limits or advise on the safest send schedule to avoid throttling issues on the receiving end.
Proactive strategies to avoid hitting API rate limits
Beyond throttling prevention, here are patterns specifically for staying under Brevo's API rate limits.
Check your quota before bulk sends
The GET /v3/account endpoint returns your current plan limits and API usage. Query it at the start of any bulk send job and gate accordingly:
import requests
response = requests.get(
"https://api.brevo.com/v3/account",
headers={"api-key": API_KEY}
)
account = response.json()
print(account["plan"]) # Current plan details and sending limits
Use webhooks instead of polling
Polling GET /v3/smtp/emails to check delivery status eats into your request rate limit with unnecessary requests. Brevo's webhook system sends real-time event notifications (opens, clicks, bounces, delivery) to your endpoint. No polling needed. Set up webhooks via POST /v3/webhooks and your log retrieval rate limit stays clean for when you actually need it.
Spread bulk sends across time windows
If you have 5,000 emails to send and a 600/hour limit, you're looking at roughly 9 hours. Schedule large jobs off-peak (overnight, early morning) to avoid competing with time-sensitive transactional mail during business hours.
Consider maintaining two separate queues. A high-priority queue for time-sensitive messages like password resets, 2FA codes, and order confirmations, and a low-priority queue for bulk notifications and email campaigns. The high-priority queue always gets first access to your rate limit budget. This makes a real difference in user experience when your system is under load.
Common mistakes that cause unnecessary 429s
Retrying on every error type, not just 429. A 401 Unauthorized or 402 Insufficient Credits won't resolve with retries. Your code will hammer the API until max_retries is exhausted. Only apply retry logic to 429 (and 5xx server errors). For 4xx client errors, fail fast and log the error messages clearly.
Using a fixed sleep instead of reading x-sib-ratelimit-reset. If the header says wait 5 seconds and you sleep for 60, you've wasted throughput. If the reset window is 120 seconds and you sleep for 60, your retry will fail again immediately. Always read the header.
Sending from multiple workers without shared rate limit state. If you have 5 worker processes each thinking they have a 600/hr budget, you'll hit throttling issues across workers before any single one reaches its apparent limit. Use a centralized rate limiter (a Redis-backed token bucket, for example) shared across all workers and individual users of the system.
Using the wrong API key type for your integration. The SMTP relay uses an SMTP key; the REST API uses an API key. Generate both under Settings, then SMTP & API. Using the wrong credential for a given integration returns a 401, not a rate limit error, but it's consistently one of the most common setup mistakes reported to support teams.
Rate limits at scale: planning your architecture
If you're sending at higher volumes and finding the default limits constraining, a few patterns help you scale more confidently.
Upgrade your plan. Brevo's pricing plans tier up the number of emails per month. Enterprise plans increase limits on log retrieval endpoints and unlock higher-tier support. Check GET /v3/account to see your current limits programmatically.
Use dedicated IPs for very high volumes. Dedicated IP addresses give you an isolated sending reputation, so your delivery rate isn't affected by other users on shared infrastructure. They tend to make sense at around 50,000+ emails per week sent consistently, depending on your traffic profile. Below that threshold, Brevo's optimized shared IPs deliver 99%+ inbox rates for most senders.
Separate transactional from marketing sends. Keep transactional email traffic and email marketing campaigns on separate sending streams with distinct API keys and subdomains. A large campaign that generates spam complaints shouldn't affect the delivery rate of your order confirmations and password resets.
Putting it together
Rate limiting isn't just an API constraint. It's part of your delivery architecture. Teams that treat it as a first-class design concern ship more reliable email systems. Teams that bolt it on after the first 429 in production spend their time firefighting instead.
Before you ship, make sure you've covered the basics:
- Read x-sib-ratelimit-reset on every 429. Never hardcode a sleep value
- Implement exponential backoff with a maximum retry count
- Use messageVersions batch sending for any bulk send over ~10 emails
- Set up webhooks for delivery events instead of polling log endpoints
- Maintain separate queues for high-priority transactional and low-priority marketing sends
- Centralise your rate limit state if running multiple workers
- Monitor deferred email and soft bounce rates as early warning signals. They surface throttling problems before they become 429s
Brevo's email API and the full rate limits reference document the current limits in detail. The free plan gives you 300 emails/day with full API access, no credit card required, which is enough to build and test your rate limit handling before going to production.
For deeper integration patterns, the Brevo developer documentation covers the full API reference, SDK changelogs, and batch sending guides. Questions about your specific plan limits are best directed to the Brevo support team.







