Rate limits
To keep the platform fast and fair, Drin limits how quickly a project can call the API. When you exceed a limit you get a 429 with a Retry-After header telling you exactly how long to wait.
Limits are enforced per project and scale with your plan. Sending also has a separate volume ceiling (daily quota and a per-tenant trust tier) that's independent of request rate — exceeding that surfaces as a permission_error, not a rate_limited one. This page is about request rate.
01 The 429 response
Over the limit, you get a 429 with a rate_limited error envelope and a Retry-After header (seconds). Wait that long, then retry.
HTTP/1.1 429 Too Many Requests
Retry-After: 2
Content-Type: application/json
{
"error": {
"type": "rate_limited",
"message": "Too many requests. Retry after 2 seconds."
}
}02 Backing off
Read Retry-After and sleep for that many seconds before retrying. The SDK does this for you — it reads the header and retries with exponential backoff and full jitter (default: 2 retries).
import { DrinClient, DrinRateLimitError } from "@drin00/sdk";
const drin = new DrinClient({ apiKey: process.env.DRIN_API_KEY });
try {
await drin.emails.send(message);
} catch (err) {
if (err instanceof DrinRateLimitError) {
// err.retryAfter is the Retry-After value (seconds)
await new Promise((r) => setTimeout(r, (err.retryAfter ?? 1) * 1000));
await drin.emails.send(message);
} else {
throw err;
}
}Retry-After, only extends the limit window. Always wait the full interval; add jitter so concurrent workers don't retry in lockstep.03 Staying under the limit
- Collapse many sends into one request with
POST /v1/emails/batch— up to 100 messages count as a single call. - Use a worker pool with bounded concurrency rather than firing every request at once.
- Schedule non-urgent sends with
scheduledAtinstead of blasting them in real time. - Treat
429as backpressure: slow down, don't spin.