Rate Limiting

Understanding and working with API rate limits.

Overview

The Transactional API uses rate limiting to ensure fair usage and platform stability. Rate limits vary by endpoint type and plan tier.


Rate Limit Headers

Every API response includes rate limit information in headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1704067200
HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the window
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when the window resets

Rate Limits by Plan

Starter Plan

Endpoint TypeLimit
Email sending50 req/sec
SMS sending10 req/sec
Read operations100 req/sec
Batch operations5 req/sec

Growth Plan

Endpoint TypeLimit
Email sending200 req/sec
SMS sending50 req/sec
Read operations500 req/sec
Batch operations20 req/sec

Scale Plan

Endpoint TypeLimit
Email sending1,000 req/sec
SMS sending200 req/sec
Read operations2,000 req/sec
Batch operations100 req/sec

Enterprise

Custom rate limits based on your needs. Contact sales for details.


Endpoint-Specific Limits

Email Endpoints

EndpointLimitWindow
POST /emails100 req/secPer second
POST /emails/batch10 req/secPer second
GET /emails/messages100 req/secPer second
GET /emails/stats10 req/secPer second

SMS Endpoints

EndpointLimitWindow
POST /sms100 req/secPer second
POST /sms/batch10 req/secPer second
GET /sms/messages100 req/secPer second

Forms Endpoints

EndpointLimitWindow
Management APIs100 req/minPer minute
Public submission10 req/minPer IP

Support Endpoints

EndpointLimitWindow
Management APIs100 req/minPer minute
Widget APIs1,000 req/minPer minute

Handling Rate Limits

Rate Limit Response

When you exceed the rate limit, you'll receive a 429 Too Many Requests response:

{
  "success": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded. Retry after 60 seconds",
    "details": {
      "retryAfter": 60
    }
  }
}

The response also includes:

Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704067260

SDK Implementations

TypeScript SDK

The SDK includes built-in rate limit handling with automatic retries:

import { Transactional } from 'transactional-sdk';
 
const client = new Transactional({
  apiKey: 'your-api-key',
  // Automatic retry on rate limits (default: enabled)
  retries: 3,
  // Custom retry delay multiplier
  retryDelay: 1000, // 1 second base
});
 
// The SDK automatically handles rate limits
try {
  const result = await client.emails.send({
    from: 'sender@example.com',
    to: 'recipient@example.com',
    subject: 'Hello',
    html: '<p>World</p>',
  });
} catch (error) {
  // Only throws after all retries exhausted
  if (error.code === 'RATE_LIMITED') {
    console.log(`Rate limited. Retry after ${error.details.retryAfter}s`);
  }
}

Manual Rate Limit Handling

import { Transactional, RateLimitError } from 'transactional-sdk';
 
const client = new Transactional({
  apiKey: 'your-api-key',
  retries: 0, // Disable automatic retries
});
 
async function sendWithRetry(payload, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await client.emails.send(payload);
    } catch (error) {
      if (error instanceof RateLimitError && attempt < maxRetries - 1) {
        const delay = error.retryAfter * 1000;
        console.log(`Rate limited, waiting ${delay}ms`);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      throw error;
    }
  }
}

Python SDK

from transactional import Transactional
from transactional.errors import RateLimitError
import time
 
client = Transactional(
    api_key="your-api-key",
    # Automatic retry on rate limits (default: enabled)
    retries=3,
)
 
# The SDK automatically handles rate limits
try:
    result = client.emails.send(
        from_email="sender@example.com",
        to="recipient@example.com",
        subject="Hello",
        html="<p>World</p>",
    )
except RateLimitError as error:
    # Only raises after all retries exhausted
    print(f"Rate limited. Retry after {error.retry_after}s")

Manual Rate Limit Handling

from transactional import Transactional
from transactional.errors import RateLimitError
import time
 
client = Transactional(
    api_key="your-api-key",
    retries=0,  # Disable automatic retries
)
 
 
def send_with_retry(payload, max_retries=3):
    for attempt in range(max_retries):
        try:
            return client.emails.send(**payload)
        except RateLimitError as error:
            if attempt < max_retries - 1:
                delay = error.retry_after
                print(f"Rate limited, waiting {delay}s")
                time.sleep(delay)
                continue
            raise
 
 
# Async version
import asyncio
 
async def send_with_retry_async(payload, max_retries=3):
    for attempt in range(max_retries):
        try:
            return await client.emails.send(**payload)
        except RateLimitError as error:
            if attempt < max_retries - 1:
                delay = error.retry_after
                print(f"Rate limited, waiting {delay}s")
                await asyncio.sleep(delay)
                continue
            raise

Best Practices

1. Implement Exponential Backoff

async function withExponentialBackoff<T>(
  fn: () => Promise<T>,
  maxRetries = 5
): Promise<T> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (error.code === 'RATE_LIMITED' && attempt < maxRetries - 1) {
        // Exponential backoff: 1s, 2s, 4s, 8s, 16s
        const delay = Math.pow(2, attempt) * 1000;
        // Add jitter to prevent thundering herd
        const jitter = Math.random() * 1000;
        await new Promise(resolve => setTimeout(resolve, delay + jitter));
        continue;
      }
      throw error;
    }
  }
  throw new Error('Max retries exceeded');
}

2. Use Batch Endpoints

Instead of many individual requests, use batch endpoints:

// Bad - 100 requests
for (const recipient of recipients) {
  await client.emails.send({ to: recipient, ... });
}
 
// Good - 1 request
await client.emails.sendBatch(
  recipients.map(to => ({ to, ... }))
);

3. Monitor Rate Limit Headers

Track remaining requests to proactively slow down:

class RateLimitAwareClient {
  private remaining = Infinity;
 
  async send(payload) {
    // Wait if we're close to the limit
    if (this.remaining < 10) {
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
 
    const response = await fetch('/api/emails', {
      method: 'POST',
      body: JSON.stringify(payload),
      headers: { 'Authorization': `Bearer ${this.apiKey}` },
    });
 
    // Update remaining from headers
    this.remaining = parseInt(
      response.headers.get('X-RateLimit-Remaining') || '100'
    );
 
    return response.json();
  }
}

4. Queue High-Volume Operations

For bulk operations, use a queue with controlled concurrency:

import PQueue from 'p-queue';
 
const queue = new PQueue({
  concurrency: 10,      // Max parallel requests
  intervalCap: 50,      // Max requests per interval
  interval: 1000,       // Interval in ms
});
 
const recipients = getRecipients(); // 10,000 recipients
 
const results = await Promise.all(
  recipients.map(recipient =>
    queue.add(() => client.emails.send({ to: recipient, ... }))
  )
);

5. Cache Read Results

Reduce read requests by caching responses:

import { LRUCache } from 'lru-cache';
 
const cache = new LRUCache<string, any>({
  max: 1000,
  ttl: 60 * 1000, // 1 minute
});
 
async function getMessage(messageId: string) {
  const cached = cache.get(messageId);
  if (cached) return cached;
 
  const message = await client.emails.messages.get(messageId);
  cache.set(messageId, message);
  return message;
}

Rate Limit Quotas

In addition to per-second rate limits, some operations have daily/monthly quotas:

ResourceStarterGrowthScale
Emails/month10,000100,000Unlimited
SMS/month1,00010,000100,000+
Form submissions/month1,00010,000Unlimited
API requests/day10,000100,000Unlimited

When you exceed a quota, you'll receive a 429 error with code QUOTA_EXCEEDED:

{
  "success": false,
  "error": {
    "code": "QUOTA_EXCEEDED",
    "message": "Monthly email quota exceeded",
    "details": {
      "quota": 10000,
      "used": 10000,
      "resetsAt": "2024-02-01T00:00:00Z"
    }
  }
}

Need Higher Limits?

If you consistently hit rate limits, consider:

  1. Upgrading your plan - Higher tiers have increased limits
  2. Using batch endpoints - Process multiple items per request
  3. Contacting sales - Enterprise plans offer custom limits

Contact support@usetransactional.com for rate limit increases.