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.
| Limite | Conteggiato per | Si applica a |
|---|---|---|
| 100 richieste / secondo | chiave API | ogni endpoint autenticato |
| 500 richieste / minuto | IP client | ogni endpoint, autenticato o no |
| 10 richieste batch / secondo | chiave API | POST /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: 47x-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:
| Piano | Email al mese | Domini |
|---|---|---|
| Free | 3.000 | 3 |
| Starter | 75.000 | 10 |
| Growth | 300.000 | 100 |
| Scale | 1.500.000 | 250 |
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