Performance optimization: high throughput on the Sent API
This guide shows you how to get maximum throughput from a Sent integration: work within the documented rate limits, use batching to raise your effective ceiling, stop spending requests on avoidable reads and polling, and structure sustained volume around a queue. It assumes you can already send messages.
Stay under the rate limits
All throughput planning starts from two numbers, both applied per customer account:
| Tier | Limit | Window | Applies to |
|---|---|---|---|
| Standard | 200 requests per minute | Sliding 60-second window | All endpoints, including POST /v3/messages |
| Sensitive | 10 requests per minute | Fixed 60-second window | POST /v3/webhooks/{id}/rotate-secret and POST /v3/webhooks/{id}/test only |
Three properties of the limits shape everything else in this guide:
- The pool is shared per account. Every API key on the account draws from the same 200 requests per minute, and requests scoped to a profile with the
x-profile-idheader count against the organization's pool. Budget the limit across your workers rather than letting each one assume it has 200. - There is no quota readout on successful responses. The
X-RateLimit-*headers andRetry-Afterappear only on429responses, so treat each429as your pressure signal: honorRetry-Afterbefore retrying, and count429s in your metrics. - Failed validation still costs budget. Requests rejected with
400or422count toward the limit (only401/403authentication rejections do not), so an oversized or malformed batch consumes a request without sending anything.
Refer to the Rate Limits reference for the per-endpoint table and the 429 response format, and to How to handle Sent API rate limits for ready-made backoff, monitoring, and throttling implementations in TypeScript, Python, and Go.
Batch recipients to raise the ceiling
Limits count per request, not per recipient, and a single POST /v3/messages accepts up to 1,000 recipients. That makes batching the most effective optimization available: 200 requests per minute at 1,000 recipients each is a ceiling of 200,000 accepted messages per minute from one account, a thousand times what per-recipient requests would allow.
Chunk larger lists into slices of 1,000 (a request with more recipients fails validation with 400 and still counts against the limit), and give each batch a deterministic idempotency key so retries cannot double-send:
const BATCH_SIZE = 1000; // API maximum per request
for (let i = 0; i < recipients.length; i += BATCH_SIZE) {
await client.messages.send(
{
to: recipients.slice(i, i + BATCH_SIZE),
template: { id: templateId }
},
{ idempotencyKey: `campaign_${campaignId}_batch_${i / BATCH_SIZE}` }
);
}If this loop runs alongside other API traffic, pace it so combined throughput stays under 200 requests per minute. The full campaign pattern, covering pacing, partial-failure handling, and reconciling accepted messages against webhook events, is in Batch Operations.
Reuse one client instance
Create the SDK client once, at startup or in your dependency container, and share it across requests and jobs. A shared client keeps HTTP connections alive and pooled, so high-volume sending does not pay a TLS handshake per request:
import SentDm from '@sentdm/sentdm';
// Create once at startup — not inside a request handler or per job
export const sent = new SentDm({ apiKey: process.env.SENT_API_KEY });Cut requests with caching and webhooks
The cheapest request is the one you never send, and with a shared 200-per-minute pool, every read you avoid is send capacity you keep:
- Cache repeated reads. Contact and template lookups (
GET /v3/contacts/{id},GET /v3/templates/{id}) that your send path repeats are cache candidates. Use a short TTL and remember that message and contact state changes server-side, so expire entries when webhook events tell you the underlying data moved. A worked caching example is in the rate limits guide. - Never poll for delivery status. Polling
GET /v3/messages/{id}for every message in a campaign consumes the same pool your sends need. Subscribe to the webhook eventsmessage.delivered,message.failed,message.filtered, andmessage.blockedinstead: delivery outcomes arrive as they happen and cost you zero requests. See Message status tracking.
Queue sends for sustained volume
For continuous high volume, as opposed to one-off campaigns, run sends through a persistent job queue (BullMQ, Sidekiq, Celery, or similar). The queue mechanics belong to your infrastructure; the Sent-specific requirements are:
- One job per batch of up to 1,000 recipients, so each job makes exactly one
POST /v3/messagescall. - Worker concurrency capped so combined throughput stays under 200 requests per minute.
- An idempotency key per job, so queue retries replay the original response instead of double-sending. See How to retry Sent API requests safely.
- The
message_idvalues from each202response persisted, so webhook events can be matched back to jobs.
The worked version of this pattern, including queue-depth monitoring, is in Batch Operations.
A 202 response means the batch was accepted, not delivered. Individual messages can still end FAILED, FILTERED, or BLOCKED asynchronously. Reconcile accepted IDs against webhook events rather than treating the 202 as delivery confirmation.
Verify your throughput
Your integration is performing correctly when:
- A campaign of N recipients completes in roughly N / 1,000 requests. If your request count is near N, you are not batching.
- Steady-state traffic produces no
429responses, and your metrics would show a spike if it started to. - Delivery outcomes arrive through webhooks, and your request log shows no per-message status polling.
If you hit sustained 429s after batching, caching, and pacing, your legitimate volume exceeds the account limit. Contact support@sent.dm about an increase, as described in the Rate Limits reference.
Related pages
- Rate Limits: limit values, window semantics, headers, and the
429body - How to handle Sent API rate limits: backoff, monitoring, and throttling implementations
- Batch Operations: campaign loops, bulk imports, and queue-based processing
- How to retry Sent API requests safely: idempotency keys for retried mutations
Advanced Guides Overview
Advanced guides for platforms and enterprises building on Sent: performance optimization, 10DLC registration, compliance, v2 to v3 migration, and multi-tenancy.
10DLC Registration Guide
How to register your brand and messaging campaigns with TCR through the Sent dashboard, including the form inputs, opt-in rules, and autoresponses required