supabase
Handles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...
customerio-rate-limits
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".
Full skill instructions
Understand and implement proper rate limiting and backoff strategies for Customer.io API.
| 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 |
| Endpoint | Limit | Window |
|---|---|---|
| Transactional email | 100/second | Per workspace |
| Transactional push | 100/second | Per workspace |
| API queries | 10/second | Per workspace |
// lib/rate-limiter.ts
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.refillRate);
this.lastRefill = now;
}
async acquire(): Promise<void> {
this.refill();
if (this.tokens >= 1) {
this.tokens -= 1;
return;
}
// Wait for token to become available
const waitTime = ((1 - this.tokens) / this.refillRate) * 1000;
await new Promise(resolve => setTimeout(resolve, waitTime));
this.tokens = 0;
this.lastRefill = Date.now();
}
}
export const trackApiLimiter = new RateLimiter(100);
// lib/backoff.ts
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
): Promise<T> {
let lastError: Error | undefined;
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
try {
return await operation();
} catch (error: any) {
lastError = error;
// Don't retry on client errors (except 429)
if (error.statusCode >= 400 && error.statusCode < 500 && error.statusCode !== 429) {
throw error;
}
if (attempt < config.maxRetries) {
const delay = calculateDelay(attempt, config);
console.log(`Retry ${attempt + 1}/${config.maxRetries} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError;
}
// lib/customerio-rate-limited.ts
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: string, event: string, data?: Record<string, any>) {
await trackApiLimiter.acquire();
return withExponentialBackoff(() =>
this.client.track(userId, { name: event, data })
);
}
// Batch operations for high volume
async batchIdentify(users: Array<{ id: string; attributes: Record<string, any> }>) {
const results: Array<{ id: string; success: boolean; error?: string }> = [];
for (const user of users) {
await trackApiLimiter.acquire();
try {
await withExponentialBackoff(() =>
this.client.identify(user.id, user.attributes)
);
results.push({ id: user.id, success: true });
} catch (error: any) {
results.push({ id: user.id, success: false, error: error.message });
}
}
return results;
}
}
// lib/rate-limit-handler.ts
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.status === 429) {
const info = parseRateLimitHeaders(response.headers);
const waitTime = info?.retryAfter || 60;
console.warn(`Rate limited. Waiting ${waitTime}s before retry.`);
await new Promise(resolve => setTimeout(resolve, waitTime * 1000));
}
}
// lib/customerio-queue.ts
import PQueue from 'p-queue';
const queue = new PQueue({
concurrency: 10,
interval: 1000,
intervalCap: 100 // 100 requests per second
});
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, event: string, data?: Record<string, any>) {
return queue.add(() => this.client.track(userId, { name: event, data }));
}
// Get queue stats
getStats() {
return {
pending: queue.pending,
size: queue.size,
isPaused: queue.isPaused
};
}
}
| Scenario | Action |
|---|---|
| 429 received | Respect Retry-After header |
| Burst traffic | Use queue with concurrency limit |
| Sustained high volume | Implement sliding window |
After implementing rate limits, proceed to customerio-security-basics for security best practices.
Handles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...
Comprehensive guides and best practices for Neon Serverless Postgres, covering setup, connection methods, authentication, and platform APIs.
Guide for setting up and using Firebase Authentication. Use this skill when the user's app requires user sign-in, user management, or secure data access using auth rules.
A skill to evaluate how secure Firestore security rules are. Use this when Firestore security rules are updated to ensure that the generated rules are extremely secure and robust.
Official skill for integrating Firebase AI Logic (Gemini API) into web applications. Covers setup, multimodal inference, structured output, and security.
Complete Better Auth server and client setup with database adapters, session management, plugins, and security configuration.
Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".
Complete Cloudflare platform integration with decision trees for compute, storage, AI, networking, security, and infrastructure-as-code.
Run Azure compliance and security audits with azqr plus Key Vault expiration checks. Covers best-practice assessment, resource review, policy/compliance validation, and security posture checks. WHEN: compliance scan, security audit, BEFORE running azqr (compliance cli tool), Azure best practices, Key Vault expiration check, expired certificates, expiring secrets, orphaned resources, compliance assessment.
Send, read, and manage Gmail messages, drafts, labels, and account settings.
Comprehensive website auditing across 230+ rules in 21 categories including SEO, performance, security, and accessibility.
Shared authentication, CLI syntax, and output formatting patterns for gws Google Workspace commands.
Discover your celebrity doppelgänger with StarByFace!
GeoSpy: Pricing, Features, FAQs, and Alternatives for AI Teams
Detect AI-generated voices to protect against audio fraud.
Ensure Your Content's Originality with AI Plagiarism Checker
Upscale images by 400% without quality loss
Protect your social media from copyright disputes
Accurately Detect AI-Generated Content with TheChecker.AI
Chrome extension that detects and flags AI-generated content