Rate Limit e Quote

Comprendi i rate limit dell'API e pianifica la tua integrazione

Due Rate Limit

Ogni richiesta viene contata due volte, su due limiti indipendenti. Entrambi restituiscono 429 RATE_LIMITED. Un terzo caso usa lo stesso codice: troppe richieste sullo stesso account nello stesso momento, vedi Richieste simultanee sullo stesso account.

LimiteConteggiato perSi applica a
100 richieste / secondochiave APIogni endpoint autenticato
500 richieste / minutoIP clientogni endpoint, autenticato o no
10 richieste batch / secondochiave APIPOST /v1/emails/batch, in aggiunta alle 100/secondo; fino a 100 email per chiamata

Il limite per chiave è quello che un'integrazione incontra per primo: un ciclo che invia senza pause tocca le 100/s molto prima delle 500/min. Il messaggio d'errore nomina il limite — Rate limit exceeded: 100 requests/second per API key.

Il limite al minuto è per IP client, non per tenant. Due workspace che chiamano dallo stesso IP di uscita lo condividono; un workspace che chiama da più IP ha un'allowance per ciascun IP. Viene contato prima dell'autenticazione, quindi anche le richieste respinte con un 401 — chiave API sbagliata, revocata o assente — consumano l'allowance.

POST /v1/templates/render-preview ha un proprio limite per IP di 60 richieste / minuto, che si somma alle 500/minuto invece di sostituirle: una richiesta di preview consuma un'unità di entrambi i budget, e il 429 arriva da quello che si esaurisce per primo. Il limite per chiave vale anche lì.

Un invio in batch conta come una richiesta sul limite di 100/secondo, qualunque sia il numero di email che contiene, e conta anche sul suo limite di 10 chiamate batch al secondo per chiave. Oltre, il 429 ha Retry-After: 1 e il messaggio Rate limit exceeded: 10 batch requests/second per API key. Sulla quota mensile di email conta ogni email del batch, un'email per destinatario distinto.

Header di Rate Limit

Le risposte riportano lo stato del limite per IP:

x-ratelimit-limit: 500
x-ratelimit-remaining: 245
x-ratelimit-reset: 47

x-ratelimit-reset è il numero di secondi rimanenti nella finestra corrente, non un timestamp Unix. Quando superi il limite per IP, il 429 include anche l'header retry-after, in secondi.

Questi header descrivono soltanto il limite per IP: 500/minuto, o 60/minuto su render-preview. Il limite per chiave, 100/secondo, non vi compare: puoi essere a x-ratelimit-remaining: 400 e ricevere comunque un 429 per aver mandato 100 richieste nello stesso secondo. Un 429 per chiave questi header li porta — il contatore per IP gira prima dell'autenticazione — ma descrivono il limite per IP, non quello che hai superato, e retry-after non c'è mai.

Verificare lo Stato del Rate Limit

Prima di raggiungere il limite, controlla gli header:

curl -i https://api.tratto.email/v1/emails \
  -H "Authorization: Bearer tratto_live_..."

Restituisce gli header con le richieste rimanenti e l'orario di reset.

Rate Limit Superato (429)

Entrambi i limiti rispondono con lo stesso codice. Oltre il limite per IP:

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded, retry in 47s.",
    "docs": "https://docs.tratto.email/en/docs/error-codes"
  }
}

Oltre il limite per chiave, message è Rate limit exceeded: 100 requests/second per API key.

Il numero nel messaggio sono i secondi che restano nella finestra corrente, quindi varia. Il body non contiene alcun campo retryAfter: dopo un 429 per IP, attendi i secondi indicati da retry-after. Dopo un 429 per chiave non c'è nessun retry-after da leggere, ma la finestra è di un secondo: basta un breve backoff.

Strategia di Retry

Usa un backoff esponenziale con jitter, rispetta retry-after quando è presente e non riprovare QUOTA_EXCEEDED, che condivide lo status 429:

async function sendWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(url, options);
    if (res.status !== 429 || attempt === maxRetries) return res;

    const { error } = await res.clone().json();
    if (error?.code === 'QUOTA_EXCEEDED') return res; // riprovare non serve

    const retryAfter = Number(res.headers.get('retry-after'));
    const delay = retryAfter
      ? retryAfter * 1000
      : 2 ** attempt * 1000 + Math.random() * 1000;
    await new Promise((r) => setTimeout(r, delay));
  }
}

Limiti di Piano

Ogni piano ha due tetti:

PianoEmail al meseDomini
Free3.0003
Starter75.00010
Growth300.000100
Scale1.500.000250

La quota mensile è in email, e ogni destinatario distinto conta come un'email (richiede l'API 0.21.0 o successiva): ogni indirizzo distinto in to, cc e bcc di un'email conta uno, ignorando maiuscole e minuscole, e un indirizzo ripetuto nella stessa email conta una volta sola. Un'email a una persona conta come un'email, un'email a tre persone distinte conta come tre. Una campagna o un flusso inviano un'email per destinatario, quindi per loro il conteggio è il numero di email inviate. Un'email può avere al massimo 50 destinatari distinti: vedi Destinatari.

La generazione di template con l'AI nella dashboard ha una dotazione a sé, 50 generazioni al mese su ogni piano, azzerata alle 00:00 UTC del primo giorno del mese.

I test-send di template e campagne sono gratuiti e hanno un tetto giornaliero a sé, contato per workspace e azzerato a mezzanotte UTC (richiede l'API 0.24.0 o successiva): 20 al giorno su Free, 100 al giorno su Starter e Growth, 200 al giorno su Scale. Vedi Test-send.

I webhook sono inclusi da Starter in su (Starter, Growth e Scale), non nel Free: vedi Piani che includono i webhook.

Anche gli allegati sono inclusi da Starter in su (richiede l'API 0.29.0 o successiva). Ogni workspace può tenere 1 GB di allegati alla volta, e i caricamenti sono limitati a uno al secondo per chiave API: vedi Allegati.

Un IP dedicato è incluso solo nel piano Scale, uno per workspace, attivato su richiesta quando il workspace invia almeno 3.000 email al giorno (ogni destinatario conta uno, come la quota mensile). Sugli altri piani non è disponibile: vedi Se hai un IP dedicato.

I tetti sono dell'account, non del workspace

Il piano appartiene all'account, la persona proprietaria del workspace, e i suoi tetti sono condivisi da tutti i workspace di quell'account, attivi o sospesi. Conta tutto, chiunque lo faccia: un invio, una generazione AI o un dominio nuovo in uno qualsiasi dei workspace del proprietario, fatti dal proprietario o da un membro, consumano la stessa dotazione. Un QUOTA_EXCEEDED può quindi arrivare da un consumo che non hai fatto tu, in un altro workspace dello stesso account o da un altro membro.

Non esiste un tetto giornaliero legato al piano. L'unico limite giornaliero del prodotto è quello del test mode, 100 email di test al giorno (ogni destinatario conta uno), vedi Test Mode.

Quando un'email ha più destinatari distinti delle email che restano all'account, la richiesta fallisce con QUOTA_EXCEEDED, anch'esso 429, e non parte niente: il controllo è tutto o niente. A differenza di RATE_LIMITED non si risolve riprovando: vedi Codici di Errore.

Un'email programmata conta nel mese UTC per cui è programmata, non in quello in cui l'hai creata. Il contatore di un mese, quello che viene confrontato col limite e che la dashboard mostra come Email usate questo mese, include le email live programmate per quel mese, una per destinatario distinto, già partite o ancora in attesa. Annullare un'email programmata restituisce la sua quota, e riprogrammarla in un altro mese sposta la quota: vedi Quota delle email programmate.

Il limite di domini conta tutti i domini di tutti i workspace dell'account, qualunque sia il loro stato: pending (in verifica), verified e failed occupano tutti un posto. Viene controllato solo quando ne aggiungi uno (POST /v1/domains). Eliminare un dominio (DELETE /v1/domains/{id}) libera il suo posto, quindi un dominio con verifica fallita che non serve più è il primo da eliminare. Oltre il tetto l'API risponde 429 QUOTA_EXCEEDED:

{
  "error": {
    "code": "QUOTA_EXCEEDED",
    "message": "Domain limit reached for the free plan: 3 domains across all the workspaces of this account, counting those still being verified and those that failed.",
    "docs": "https://docs.tratto.email/en/docs/error-codes",
    "suggestion": "Delete a domain that failed or is no longer used, or ask the account owner to upgrade the plan at https://app.tratto.email/settings/billing."
  }
}

Leggi i tuoi limiti dall'API

Non scrivere a mano i numeri qui sopra. GET /v1/workspace restituisce i limiti realmente applicati, dove null significa nessun limite. limits.emailsPerMonth (email per mese UTC, dove ogni indirizzo distinto in to, cc e bcc conta come un'email) e limits.domains sono i tetti dell'account proprietario del workspace, condivisi con i suoi altri workspace, non una dotazione di questo solo workspace. limits.membersPerAccount è il numero di membri del team dell'account, condivisi da tutti i workspace che possiede, dove una persona in più workspace conta una volta, inviti in attesa compresi (richiede l'API 0.28.0 o successiva; limits.membersPerWorkspace viene ancora restituito con lo stesso numero, mantenuto per compatibilità; vedi Team). limits.webhooks dice se il piano del workspace include i webhook (richiede l'API 0.23.0 o successiva, vedi Webhook). limits.testSendsPerDay è il tetto giornaliero di test-send di questo workspace, fra template e campagne insieme, azzerato a mezzanotte UTC (richiede l'API 0.24.0 o successiva):

curl https://api.tratto.email/v1/workspace \
  -H "Authorization: Bearer tratto_live_..."
{
  "data": {
    "plan": "free",
    "limits": { "domains": 3, "emailsPerMonth": 3000, "membersPerAccount": 1, "membersPerWorkspace": 1, "workspaces": 1, "webhooks": false, "testSendsPerDay": 20 }
  }
}

È la stessa tabella che legge l'enforcement, quindi non può discordare da ciò che ti è davvero permesso inviare.

Il mese è il mese di calendario UTC

La quota non è una finestra mobile di 30 giorni. Il contatore è indicizzato sul mese di calendario UTC (2026-09) e rotola al primo invio del mese nuovo: non c'è nessun job schedulato di reset. Un account che a ottobre non invia nulla riparte comunque da zero a novembre, al primo invio di novembre.

Non esiste un piano "Pro", né un piano senza tetto mensile di email. I piani sono quelli della tabella qui sopra, e GET /v1/workspace riporta sempre uno di questi.

Richieste simultanee sullo stesso account

Poiché i tetti si contano per account, le richieste che arrivano sullo stesso account nello stesso momento vengono contate una dopo l'altra. Quando ne arrivano troppe insieme, da uno qualsiasi dei workspace o delle chiavi dell'account, l'API risponde 429 RATE_LIMITED con l'header Retry-After: 1:

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many concurrent requests on this account. Retry after 1 second.",
    "docs": "https://docs.tratto.email/en/docs/error-codes"
  }
}

Attendi i secondi di Retry-After e rimanda la stessa richiesta. La funzione di retry qui sopra lo fa già. Distribuire le richieste nel tempo, invece di lanciarle in parallelo, rende questo 429 più raro.

Un account regge circa cinque richieste al secondo che consumano la sua quota mensile (invii, lotti di campagna, passi delle automazioni, generazioni AI), sommando tutti i suoi workspace e tutte le sue chiavi. Oltre questo ritmo le risposte prima rallentano, poi questo 429 diventa frequente.

L'SDK Node @tratto/email non ritenta da solo un 429, e il suo TrattoError non riporta gli header della risposta, quindi attendi un secondo prima di riprovare:

import { TrattoError } from '@tratto/email';

async function sendWithRetry(tratto, email, maxRetries = 3) {
  for (let attempt = 0; ; attempt++) {
    try {
      return await tratto.emails.send(email);
    } catch (error) {
      const retryable = error instanceof TrattoError
        && error.code === 'RATE_LIMITED' && attempt < maxRetries;
      if (!retryable) throw error; // compreso QUOTA_EXCEEDED
      await new Promise((r) => setTimeout(r, 1000 * (attempt + 1)));
    }
  }
}

Comportamento nei Picchi

Nessuno dei due limiti fa una media, e nessuno ammette picchi oltre la soglia. Il limite per chiave conta le richieste in ogni secondo di orologio. Il limite per IP apre una finestra di un minuto alla tua prima richiesta e si azzera quando la finestra finisce. Distribuisci le richieste invece di inviarle a raffiche.


Prossimo: Codici di Errore


Modifica questa pagina su GitHub

Ultimo aggiornamento