Home Products Blog Contact Privacy Terms Support
BulkForge · Reliability

How BulkForge Handles Email-Provider Rate Limits (and How We Tested It Without Sending a Single Real Email)

October 2026

What does an email provider's rate limit do to a bulk send?

An email provider's rate limit rejects any request that arrives after the allowance for that minute or hour is used up, so a bulk send can fail even though nothing is broken. For Azure Communication Services Email on a custom verified domain, the default limits per subscription are 30 emails per minute and 100 per hour. Azure-managed domains are tighter at 5 per minute and 10 per hour, with no increase available. Exceeding a limit returns HTTP 429 with a Retry-After. Status checks are limited separately, at 60 per minute and 200 per hour by default.

Higher limits are available by request through Microsoft's quota-increase process, and those requests are not automatic. The details are in Microsoft's Azure Communication Services service limits and email quota increase pages.

Why does a "429 Too Many Requests" fail whole jobs in some tools?

A tool that treats every non-success response as a failure will mark a job failed the moment the provider says 429, even though the provider did nothing wrong and the message could simply have waited. The worse variant is a tool that loses track of which recipients were actually accepted, which turns a limit into a silent drop.

We are not exempt from this. BulkForge has two parts: a control plane that AgeeBgee Solutions, LLC hosts, and an agent that runs in your own Azure subscription and does the sending (covered in Why BulkForge Deploys an Agent Into Your Subscription Instead of Just Asking for API Keys). Each part now handles limits differently, and the sections below take them in turn.

Stated plainly: until agent release v3.3.0, our own agent failed a whole Postmark or SendGrid job when the provider answered 429. That is the behavior this post describes fixing, and we would rather say so than imply it was never a problem.

How does BulkForge keep a bulk send inside the provider's limit?

BulkForge's control plane runs a send-budget governor that hands jobs to your agent only while the current hour has room, so jobs that do not fit wait and go out as budget frees up instead of failing. The budget is the configured cap, minus recipients already accepted in the window, minus recipients still outstanding on jobs already handed out.

A few rules keep this predictable:

  • Jobs are handed out in order, and a job goes out only if it fits whole.
  • If the provider still answers 429, the cap steps down 25% at a time, never below half the configured value, and an operator can reset it.
  • The governor is off unless it is configured.
  • Operators get an hourly view of sends and a 15-minute velocity view showing the rolling hour against the cap.

In production, with the cap set to 90 per hour, accepted sends stayed at or under 90 in every hour, with no rejections.

The governor works within a provider's limit; it does not raise it. Raising the limit is a request to Microsoft. The governor is server-side and runs on the hosted control plane.

Is it safe to retry a request the provider rejected?

It is safe to retry an HTTP 429, because 429 means the provider did not process the request, so waiting and retrying cannot send a duplicate. It is not safe to blindly retry an HTTP 5xx: that response means the message may or may not have been processed, so BulkForge never retries it automatically.

That distinction drives two behaviors.

Re-sending rejected recipients. Recipients the provider rejected with 429 were never sent, so BulkForge can re-send just those recipients as new low-priority jobs. These use only spare budget, behind normal traffic. Each failed job can be re-sent once, and the original job stays as the record.

Retrying in the agent. In agent release v3.3.0, published 2026-10-08, for Postmark and SendGrid the agent waits for the provider's own Retry-After (for SendGrid, X-RateLimit-Reset) and retries, up to 5 attempts and 300 seconds per batch. Anything still unsent is recorded per recipient, so it can be re-sent. Every batch also starts with a fresh throttle budget, so one throttling episode cannot make the next job fail without trying.

The agent changes apply when you install v3.3.0. Customers update the agent themselves, and new Azure Marketplace installs get it.

How does BulkForge show a message the provider accepted and then dropped?

For Azure Communication Services, the v3.3.0 agent checks the final state of each message shortly after the provider accepts it, and records a delivery failure the provider reports later, such as a suppressed recipient. Before this, those messages showed as accepted.

Separately, the agent now reports progress every 10 recipients instead of every 50. A job interrupted partway keeps a record of what was already sent, so an interruption is not a mystery.

How do you test rate limits without sending real email?

You run the real software against fake providers. BulkForge's test rig is a Docker setup that runs the real control plane and the real agent against fake Azure Communication Services, Postmark, and SendGrid providers. The fakes send nothing and record everything. They can add latency, enforce per-minute and per-hour caps, answer 429 with a Retry-After, fail or "suppress" messages, and hang calls.

After each run, the rig compares what BulkForge's database says with what the fake provider actually received, per job: completed, stranded, delivered-but-unrecorded, missing, duplicated, and whether BulkForge's accepted count agrees with the provider's.

Two results, from the same load and the same cap (4 batches of 5 jobs of 30 recipients, with the hour compressed to 5 minutes):

GovernorJobs completedJobs failedRecipients
Off10 of 2010, with nothing sent300 of 600 delivered
On20 of 200600 of 600 delivered once, no rejections

In the re-send test, every recipient of the failed jobs ended up delivered exactly once, and repeating the re-send created nothing.

The rig also caught something a unit test with a hand-written fake could not. The Azure SDK throws an exception, with HTTP status 200, when an operation itself ended Failed. Our first version of the delivery check, before it shipped, ignored every failure for that reason. Tests that drive the real SDK now guard against it. That is the argument for realistic fakes: they surface behavior you would not think to write into a mock.

What does this test not prove?

The rig proves BulkForge's logic against simulated providers. It does not prove real Azure latency or real Azure limits; those come from Microsoft's documentation linked above. We also do not claim "never fails" or "zero duplicates." We claim something narrower: a 429 means wait and retry, a 5xx is never auto-retried, and what could not be sent is recorded so it can be re-sent.

BulkForge orchestrates bulk campaigns from a control plane that never holds your delivery credentials, and paces sends to stay within a provider's rate limit. Find BulkForge on Azure Marketplace.

See how BulkForge works
← Back to Blog