Launch-Free 3 months Builder plan-
Pixel art lobster mascot illustration for email infrastructure — lobstermail rate limit 429 error retry strategy

lobstermail 429 rate limit errors: retry strategies that actually work

Hit a 429 from LobsterMail's API? Here's how to implement exponential backoff, read rate-limit headers, and queue sends so your agent never drops an email.

9 min read
Ian Bussières
Ian BussièresCTO & Co-founder

Your agent just tried to send 50 emails in a loop. The first 30 went through. Then the API started returning 429 Too Many Requests, and your agent's pipeline stalled. Those last 20 emails? Gone, unless your retry logic knows how to handle the rejection.

A 429 is not a failure. It's the API telling your agent to slow down. The request was valid, the credentials were fine, and the message was well-formed. You just sent too many requests in too short a window. But agents don't process speed bumps the way humans do. An agent with no retry strategy will either give up immediately or hammer the endpoint until the problem gets worse. Neither is what you want.

If you're building on LobsterMail and running into 429s, and then wire up the retry patterns below to keep your sends flowing without interruption.

LobsterMail 429 error: retry strategies at a glance#

  1. Read the Retry-After header from the 429 response and wait exactly that long before retrying.
  2. Apply exponential backoff with jitter: start at 1 second, double each attempt, add random jitter, and cap at 32 seconds.
  3. Set a maximum retry count (3-5 attempts) so your agent fails gracefully instead of looping forever.
  4. Pre-throttle outbound sends with a token-bucket queue so you stay under the limit before the 429 ever fires.
  5. Monitor X-RateLimit-Remaining and X-RateLimit-Reset headers on every response to see how close you are to the ceiling.
  6. Separate transactional sends (OTPs, verifications) from bulk sends so time-sensitive messages always get priority.

Those six patterns will handle almost every 429 scenario your agent encounters. The rest of this article walks through each one and shows how to wire them into an agentic email pipeline.

What a 429 actually means#

HTTP 429 Too Many Requests is the server's way of saying "you've exceeded your allowed request rate." Unlike a permanent rejection like a 550 denied by policy, a 429 is temporary. The request was valid. You just need to wait and try again.

There's a distinction worth noting between rate limiting and throttling. Rate limiting is a hard cap: once you hit the threshold, requests are rejected outright. Throttling slows requests down rather than rejecting them. LobsterMail's API uses rate limiting. When you exceed the allowed request rate, the API returns a 429 with headers indicating when your quota resets. Your agent's job is to respect those headers.

How long should you wait before retrying? That depends on the response. If the Retry-After header is present, use the exact value it gives you. If it's absent, start with a 1-second delay and double from there. We'll get into the specifics shortly.

LobsterMail sending quotas by plan#

Every LobsterMail plan enforces two types of limits.

Per-period send quotas control how many emails your agent can send over a given window. On the Free plan, that's 1,000 emails per month with no credit card required. The Builder plan ($9/mo) raises it to 5,000 per month and 500 per day, with up to 10 inboxes and 3 custom domains.

API request rate limits, separately, control how fast you can call the API itself regardless of what you're doing. These protect the infrastructure from burst traffic. When your agent fires off a batch of send() calls in a tight loop, you'll hit the request rate limit long before you exhaust your monthly quota.

The 429 response applies to both scenarios. If your agent maxes out the daily send quota, the API returns a 429. If your agent calls the send endpoint 100 times in one second, it also returns a 429. The fix is different for each case. Quota exhaustion means you need to wait for the window to reset (or upgrade your plan). Request-rate exhaustion means you need to space out your calls. For a full reference on send behavior, see the sending emails guide.

Reading rate-limit headers before you hit the wall#

Most developers only look at rate-limit headers after a 429 arrives. That's reactive. A better approach is to inspect them on every response.

LobsterMail includes rate-limit metadata in response headers:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 12
X-RateLimit-Reset: 1711699200
`X-RateLimit-Limit` is your total allowed requests per window. `X-RateLimit-Remaining` tells you how many you have left. `X-RateLimit-Reset` is the Unix timestamp when the window resets.

If your agent sees X-RateLimit-Remaining drop below 5, it should start spacing out requests voluntarily. Don't wait for the 429. Slow down before it happens.

async function sendWithAwareness(inbox, message) {
  const response = await inbox.send(message);

  const remaining = parseInt(response.headers['x-ratelimit-remaining'], 10);
  const resetAt = parseInt(response.headers['x-ratelimit-reset'], 10);

  if (remaining < 5) {
    const waitMs = (resetAt * 1000) - Date.now();
    if (waitMs > 0) {
      console.log(`Approaching rate limit. Pausing ${waitMs}ms.`);
      await new Promise(r => setTimeout(r, waitMs));
    }
  }

  return response;
}

This pattern prevents 429s instead of recovering from them. It's the difference between hitting the brakes before the wall and rebuilding the car after.

Exponential backoff with jitter#

When a 429 does arrive, exponential backoff is the standard recovery pattern. The idea: wait 1 second, then 2, then 4, then 8, then 16. Each failed attempt doubles the delay.

But pure exponential backoff has a problem. If 10 agents all get rate-limited at the same moment, they all retry at 1 second, then all retry at 2 seconds, then all retry at 4 seconds. They stay synchronized, creating repeated bursts. This is the thundering herd problem, and it's common in agentic pipelines where multiple agents share the same account.

Jitter fixes it. Adding a random component to each delay spreads retries across time so they don't collide.

async function sendWithRetry(inbox, message, maxRetries = 4) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await inbox.send(message);
    } catch (error) {
      if (error.status !== 429 || attempt === maxRetries) {
        throw error;
      }

      const retryAfter = error.headers?.['retry-after'];
      let delay;

      if (retryAfter) {
        delay = parseInt(retryAfter, 10) * 1000;
      } else {
        const base = Math.min(1000 * Math.pow(2, attempt), 32000);
        delay = base + Math.random() * base;
      }

      console.log(`429 received. Retry in ${Math.round(delay)}ms (attempt ${attempt + 1})`);
      await new Promise(r => setTimeout(r, delay));
    }
  }
}

A few things to note. The Retry-After header takes priority when present. It tells you exactly how long to wait, so trust it over your own calculation. The cap at 32 seconds (this is what makes it "truncated" exponential backoff) prevents absurdly long waits. And 4 retries is usually enough for rate-limit recovery. If the API is still returning 429 after the fourth attempt, something bigger is going on. Log the failure and investigate rather than continuing to loop.

Token-bucket queuing for burst sends#

Retries handle the 429 after it happens. A token bucket prevents it from happening in the first place.

The concept: you have a bucket that holds tokens, and it refills at a steady rate. Each API call costs one token. If the bucket is empty, the call waits until a token appears. This converts bursty traffic into a steady stream that stays under the rate limit naturally.

class TokenBucket {
  constructor(capacity, refillRate) {
    this.tokens = capacity;
    this.capacity = capacity;
    this.refillRate = refillRate;
    this.lastRefill = Date.now();
  }

  async acquire() {
    this.refill();
    if (this.tokens > 0) {
      this.tokens--;
      return;
    }
    const waitMs = (1 / this.refillRate) * 1000;
    await new Promise(r => setTimeout(r, waitMs));
    this.refill();
    this.tokens--;
  }

  refill() {
    const now = Date.now();
    const elapsed = (now - this.lastRefill) / 1000;
    this.tokens = Math.min(
      this.capacity,
      this.tokens + elapsed * this.refillRate
    );
    this.lastRefill = now;
  }
}

const bucket = new TokenBucket(10, 2);

async function throttledSend(inbox, message) {
  await bucket.acquire();
  return inbox.send(message);
}

This keeps your agent under the rate limit even when it needs to send 200 emails in a batch. The sends trickle out at a controlled rate instead of slamming the endpoint all at once. Combine the token bucket with the retry logic from the previous section, and you've got a pipeline that both prevents 429s and recovers gracefully when they do occur.

Handling 429s in agentic pipelines#

Single-threaded scripts are straightforward. Agent pipelines are harder.

When your agent runs a multi-step workflow (provision inbox, sign up for a service, wait for verification email, reply), a 429 on any step stalls the entire chain. If the agent has no retry logic, the workflow fails silently. If it retries too aggressively, it makes the congestion worse.

The key design principle: separate your send queue from your agent's decision logic. The agent decides what to send. A queue manager decides when. This means the agent never blocks on a 429 directly. It pushes messages into a queue, and the queue handles pacing and retries in the background.

For time-sensitive sends (OTP codes, verification links, password resets), assign priority levels. A verification email that needs to arrive within 30 seconds should jump ahead of a batch of newsletter sends. Your token bucket can support this by reserving a portion of its capacity for high-priority messages.

Can 429 errors cause emails to fail entirely? Yes, if your retry logic gives up after all attempts. For transactional messages, that means a user never gets their verification code, and the agent's workflow breaks. That's why the retry count matters: use 5-6 retries with shorter initial delays for transactional sends, and 3 retries for bulk sends where timing is less sensitive.

Is it safe to automatically retry a failed send? In most cases, yes. A 429 means the send was rejected before processing, so retrying creates a new attempt, not a duplicate delivery. Log every retry so you can audit the pipeline later. If you're also seeing deliverability issues alongside rate limits, our guide to agent email setup mistakes covers the common configuration problems that compound the issue.

Where to go from here#

If you're hitting 429s, the fix is almost always one of two things: smooth your request rate before the limit (token bucket), or handle the rejection gracefully after (exponential backoff with jitter). Combine both, and your agent's email pipeline stays steady even at high volume. The code examples above work directly with the LobsterMail SDK. If you haven't set up your agent's inbox yet, and build with the patterns from this article.

Frequently asked questions

What does a 429 error mean when calling LobsterMail's sending API?

It means your agent has exceeded the allowed request rate or sending quota for the current window. The request itself was valid but rejected due to volume. Wait for the duration specified in the Retry-After header and try again.

Does LobsterMail include a Retry-After header in its 429 responses?

Yes. When LobsterMail returns a 429, the response includes a Retry-After header with the number of seconds to wait. Always honor this value over your own backoff calculation.

What are the sending limits on LobsterMail's Free and Builder plans?

The Free plan allows 1,000 emails per month. The Builder plan ($9/mo) allows 5,000 emails per month and 500 per day, with up to 10 inboxes and 3 custom domains.

Should I retry immediately after receiving a 429 from LobsterMail?

No. Immediate retries make congestion worse. Wait at least 1 second, use exponential backoff with jitter, and always check the Retry-After header first.

What is truncated exponential backoff and when should I use it?

Standard exponential backoff doubles the delay indefinitely (1s, 2s, 4s, 8s, 16s, 32s, 64s...). Truncated exponential backoff caps the maximum delay at a ceiling like 32 seconds, preventing your agent from waiting minutes between retries.

How does adding jitter to retry delays prevent thundering-herd congestion?

Without jitter, multiple agents rate-limited at the same moment all retry at identical intervals, creating synchronized bursts. Jitter adds a random component to each delay so retries spread across time and don't collide.

How many retry attempts should I configure before marking a send as failed?

For transactional emails (OTPs, verifications), use 5-6 retries with shorter initial delays. For bulk sends where timing is less sensitive, 3-4 retries is sufficient. If the API still returns 429 after your max attempts, log the failure and investigate.

Can repeated 429 errors cause LobsterMail to throttle my account beyond the standard limit?

Using proper backoff and respecting rate-limit headers won't trigger extra restrictions. However, ignoring 429 responses and aggressively retrying without delays could be flagged as abusive behavior.

What is a token bucket algorithm and how does it prevent 429 errors?

A token bucket holds a fixed number of tokens that refill at a steady rate. Each API call consumes one token. When the bucket is empty, the call waits. This smooths burst traffic into a steady stream that stays under the rate limit before a 429 ever fires.

Does the LobsterMail SDK handle 429 retries automatically?

The SDK includes basic retry handling for transient errors. For production agentic pipelines with high concurrency or mixed-priority sends, implement your own retry and queuing logic to match your throughput needs.

How do I read LobsterMail's rate-limit headers to monitor quota usage in real time?

Check X-RateLimit-Remaining and X-RateLimit-Reset on every API response. When Remaining drops below 5, start spacing out requests voluntarily instead of waiting for a 429.

Is it safe to retry a 429'd send, or could retries cause duplicate emails?

It's safe. A 429 means the send was rejected before processing, so retrying creates a new attempt rather than a duplicate delivery. Log every retry for auditability.

How should retry logic differ for transactional emails versus bulk sends?

Transactional emails (verification codes, receipts) need shorter initial delays and more retry attempts because they're time-sensitive. Bulk sends can tolerate longer delays and fewer retries since a few seconds of latency won't affect the outcome.

What happens to emails queued in my agent's pipeline when LobsterMail returns a 429?

If your agent has no retry logic, those emails are dropped. With a proper send queue, they wait in the queue and are retried when the rate-limit window resets. Separate your send queue from your agent's decision logic so a 429 doesn't stall the entire workflow.

What is the difference between rate limiting and throttling?

Rate limiting is a hard cap that rejects requests outright once you exceed the threshold. Throttling slows requests down rather than rejecting them. LobsterMail's API uses rate limiting, returning a 429 when the limit is exceeded.

Related posts