| name | customerio-rate-limits |
| description | Implement Customer.io rate limiting and backoff.
Use when handling high-volume API calls, implementing
retry logic, or optimizing API usage.
Trigger with phrases like "customer.io rate limit", "customer.io throttle",
"customer.io 429", "customer.io backoff".
|
| allowed-tools | Read, Grep, Bash(curl:*) |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Customer.io Rate Limits
Overview
Understand and implement proper rate limiting and backoff strategies for Customer.io API.
Rate Limit Details
Track API Limits
| Endpoint | Limit | Window |
|---|
| Identify | 100 requests/second | Per workspace |
| Track events | 100 requests/second | Per workspace |
| Batch operations | 100 requests/second | Per workspace |
| Page/screen | 100 requests/second | Per workspace |
App API Limits
| Endpoint | Limit | Window |
|---|
| Transactional email | 100/second | Per workspace |
| Transactional push | 100/second | Per workspace |
| API queries | 10/second | Per workspace |
Instructions
Step 1: Implement Rate Limiter
class RateLimiter {
private tokens: number;
private lastRefill: number;
private readonly maxTokens: number;
private readonly refillRate: number;
constructor(maxRequestsPerSecond: number = 100) {
this.maxTokens = maxRequestsPerSecond;
this.tokens = maxRequestsPerSecond;
this.refillRate = maxRequestsPerSecond;
this.lastRefill = Date.now();
}
private refill(): void {
const now = Date.now();
const elapsed = (now - this.lastRefill) / 1000;
this.tokens = Math.min(this.maxTokens, this.tokens + elapsed * this.);
. = now;
}
(): <> {
.();
(. >= ) {
. -= ;
;
}
waitTime = (( - .) / .) * ;
( (resolve, waitTime));
. = ;
. = .();
}
}
trackApiLimiter = ();
Step 2: Implement Exponential Backoff
interface BackoffConfig {
maxRetries: number;
baseDelay: number;
maxDelay: number;
jitterFactor: number;
}
const defaultConfig: BackoffConfig = {
maxRetries: 5,
baseDelay: 1000,
maxDelay: 32000,
jitterFactor: 0.1
};
function calculateDelay(attempt: number, config: BackoffConfig): number {
const exponentialDelay = config.baseDelay * Math.pow(2, attempt);
const cappedDelay = Math.min(exponentialDelay, config.maxDelay);
const jitter = cappedDelay * config.jitterFactor * Math.random();
return cappedDelay + jitter;
}
export async function withExponentialBackoff<T>(
operation: () => Promise<T>,
config: BackoffConfig = defaultConfig
): <T> {
: | ;
( attempt = ; attempt <= config.; attempt++) {
{
();
} (: ) {
lastError = error;
(error. >= && error. < && error. !== ) {
error;
}
(attempt < config.) {
delay = (attempt, config);
.();
( (resolve, delay));
}
}
}
lastError;
}
Step 3: Create Rate-Limited Client
import { TrackClient, RegionUS } from '@customerio/track';
import { trackApiLimiter } from './rate-limiter';
import { withExponentialBackoff } from './backoff';
export class RateLimitedCustomerIO {
private client: TrackClient;
constructor() {
this.client = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_API_KEY!,
{ region: RegionUS }
);
}
async identify(userId: string, attributes: Record<string, any>) {
await trackApiLimiter.acquire();
return withExponentialBackoff(() =>
this.client.identify(userId, attributes)
);
}
async track(userId: , : , ?: <, >) {
trackApiLimiter.();
(
..(userId, { : event, data })
);
}
() {
: <{ : ; : ; ?: }> = [];
( user users) {
trackApiLimiter.();
{
(
..(user., user.)
);
results.({ : user., : });
} (: ) {
results.({ : user., : , : error. });
}
}
results;
}
}
Step 4: Handle 429 Response Headers
interface RateLimitInfo {
remaining: number;
resetTime: Date;
retryAfter?: number;
}
function parseRateLimitHeaders(headers: Headers): RateLimitInfo | null {
const remaining = headers.get('X-RateLimit-Remaining');
const reset = headers.get('X-RateLimit-Reset');
const retryAfter = headers.get('Retry-After');
if (!remaining || !reset) return null;
return {
remaining: parseInt(remaining, 10),
resetTime: new Date(parseInt(reset, 10) * 1000),
retryAfter: retryAfter ? parseInt(retryAfter, 10) : undefined
};
}
async function handleRateLimitResponse(response: Response): Promise<void> {
if (response. === ) {
info = (response.);
waitTime = info?. || ;
.();
( (resolve, waitTime * ));
}
}
Step 5: Queue-Based Rate Limiting
import PQueue from 'p-queue';
const queue = new PQueue({
concurrency: 10,
interval: 1000,
intervalCap: 100
});
export class QueuedCustomerIO {
private client: TrackClient;
constructor() {
this.client = new TrackClient(
process.env.CUSTOMERIO_SITE_ID!,
process.env.CUSTOMERIO_API_KEY!,
{ region: RegionUS }
);
}
async identify(userId: string, attributes: Record<string, any>) {
return queue.add(() => this.client.identify(userId, attributes));
}
async track(userId: string, : , ?: <, >) {
queue.( ..(userId, { : event, data }));
}
() {
{
: queue.,
: queue.,
: queue.
};
}
}
Output
- Token bucket rate limiter
- Exponential backoff with jitter
- Rate-limited Customer.io client
- Queue-based rate limiting
Error Handling
| Scenario | Action |
|---|
| 429 received | Respect Retry-After header |
| Burst traffic | Use queue with concurrency limit |
| Sustained high volume | Implement sliding window |
Resources
Next Steps
After implementing rate limits, proceed to customerio-security-basics for security best practices.