Invio in Batch
Invia fino a 100 email in una sola chiamata con POST /v1/emails/batch
POST /v1/emails/batch invia fino a 100 email in una sola richiesta. Ogni
email è indipendente: ha il suo mittente, i suoi destinatari, il suo oggetto e
il suo contenuto, con gli stessi campi di POST /v1/emails.
Richiede l'API 0.22.0 o successiva. Anche gli SDK hanno un metodo per il batch: vedi Con gli SDK.
Richiesta
Il body è un oggetto con un solo campo, emails: un array da 1 a 100 email.
curl -X POST https://api.tratto.email/v1/emails/batch \
-H "Authorization: Bearer tratto_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2e0a-batch-2026-10-20" \
-d '{
"emails": [
{
"from": "hello@tuodominio.it",
"to": "alice@example.com",
"subject": "La tua ricevuta",
"text": "Grazie per il tuo ordine, Alice."
},
{
"from": "hello@tuodominio.it",
"to": ["bob@example.com"],
"cc": ["billing@example.com"],
"subject": "La tua ricevuta",
"templateId": "tmpl_abc123",
"variables": { "firstName": "Bob" }
}
]
}'import { randomUUID } from 'node:crypto';
const response = await fetch('https://api.tratto.email/v1/emails/batch', {
method: 'POST',
headers: {
'Authorization': 'Bearer tratto_live_...',
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
emails: [
{ from: 'hello@tuodominio.it', to: 'alice@example.com', subject: 'La tua ricevuta', text: 'Grazie, Alice.' },
{ from: 'hello@tuodominio.it', to: 'bob@example.com', subject: 'La tua ricevuta', text: 'Grazie, Bob.' },
],
}),
});
const { data } = await response.json();
console.log(`${data.accepted} in coda, ${data.failed} fallite`);Regole per ogni email del batch:
- Accetta tutti i campi di
POST /v1/emailstrannescheduledAt. Per programmare un invio, manda l'email da sola conPOST /v1/emails. attachmentsnon è accettato: risponde 422.- Un campo sconosciuto risponde 422: sia il body sia ogni email sono
controllati in modo stretto.
POST /v1/emailsfa lo stesso con l'API 0.24.0 o successiva, vedi Campi sconosciuti. - Al massimo 50 destinatari distinti fra
to,ccebcc, come per un invio singolo. Vedi Destinatari.
Il body della richiesta può essere al massimo di 5 MiB. Dopo il render, ogni email può essere al massimo di 900 KB e l'intero batch al massimo di 9 MiB.
La validazione è tutto o niente. Se un'email non è valida, l'intero batch
viene respinto e non si scrive né si addebita niente. details[].path indica
l'email con la sua posizione nell'array, a partire da 0:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed.",
"docs": "https://docs.tratto.email/en/docs/error-codes",
"details": [{ "path": "/emails/3/to", "message": "An email can have at most 50 recipients across to, cc and bcc (got 51). Split the send, or use a campaign for a larger audience." }]
}
}Un dominio mittente non verificato risponde 403 FORBIDDEN, e il messaggio
comincia con la posizione dell'email, per esempio emails[3]: Domain 'mail.acme.com' is not verified for this tenant.
Risposta
Un batch che supera la validazione risponde 202 Accepted, sempre con
questa forma:
{
"data": {
"batchId": "batch_V1StGXR8Z5jdHi6BmyT2a",
"livemode": true,
"accepted": 2,
"failed": 1,
"emails": [
{ "id": "email_abc123xyz", "status": "queued", "to": ["alice@example.com"] },
{
"status": "failed",
"to": ["bob@example.com"],
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "Not sent and not charged: it could not be handed to the delivery queue. Send it again in a new batch, with a new Idempotency-Key."
}
},
{ "id": "email_def456uvw", "status": "queued", "to": ["carol@example.com"] }
]
}
}emailsha una voce per ogni email della richiesta, nello stesso ordine.acceptedconta le email messe in coda per la consegna;failedconta quelle non inviate ed è sempre presente,0se sono andate tutte.- Un'email
queuedha unid: il suo stato si legge conGET /v1/emails/{id}, come per qualsiasi altra email. Un'emailfailednon haide non verrà inviata. - L'
error.messagedi un'email fallita dice se c'è stato un addebito. Quando non è stato possibile restituire la quota, il messaggio dice chemay still count against the monthly quota. In entrambi i casi non è partito niente. - Il 202 non ha l'header
Retry-After, nemmeno quando alcune email sono fallite.
Ripetere le email fallite
Rimanda solo le email fallite, in un nuovo batch con una nuova
Idempotency-Key. Ripetere la stessa richiesta con la stessa chiave
restituisce la risposta originale, fallimenti compresi, e non invia niente
(vedi Idempotenza).
Se nessuna email del batch è stata messa in coda, l'API risponde 503 SERVICE_UNAVAILABLE invece di 202: vedi Errori.
Idempotenza
Una Idempotency-Key copre l'intero batch, per 24 ore. Ripetere la richiesta
con la stessa chiave e lo stesso body restituisce la stessa risposta, anche se
parziale, e non invia niente. La stessa chiave con un body diverso risponde
409 IDEMPOTENCY_CONFLICT; una richiesta con una chiave la cui prima
richiesta è ancora in corso risponde 409 IDEMPOTENCY_IN_PROGRESS. Vedi
Idempotenza.
Quota
Ogni email conta sulla quota mensile
come un'email per ogni destinatario distinto fra to, cc e bcc, ignorando le maiuscole,
esattamente come un invio singolo. I destinatari si deduplicano dentro la
stessa email, mai fra email diverse: lo stesso indirizzo in due email del
batch conta come due email.
Il batch riserva la somma di tutte le sue email in una volta, tutto o
niente. Se all'account restano meno email di quanti destinatari distinti ha il batch in
totale, l'API risponde 429 QUOTA_EXCEEDED e non si scrive né si invia
niente:
{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "This batch has 120 recipients, more than the emails left on the free plan (3000 per month). Each distinct address in to, cc and bcc counts as one email. The quota is shared by every workspace of the account that owns this one.",
"docs": "https://docs.tratto.email/en/docs/error-codes",
"suggestion": "Send to fewer recipients, or have the workspace owner upgrade the plan at https://app.tratto.email/settings/billing."
}
}In test mode il tetto giornaliero di 100 email di test conta la somma del batch allo stesso modo.
Rate limit
Un batch conta come una richiesta sul limite di 100 richieste al secondo
per chiave API. In più, ogni chiave può fare al massimo 10 richieste batch
al secondo; oltre, l'API risponde 429 RATE_LIMITED con Retry-After: 1 e
il messaggio Rate limit exceeded: 10 batch requests/second per API key. Vedi
Rate Limit e Quote.
Errori
| Stato | Codice | Quando | Scritto o addebitato |
|---|---|---|---|
| 422 | VALIDATION_ERROR | 0 o più di 100 email; un'email non valida; più di 50 destinatari in un'email; scheduledAt, attachments o un campo sconosciuto; un header riservato; nessun mittente; un'email oltre 900 KB o un batch oltre 9 MiB dopo il render | Niente |
| 403 | FORBIDDEN | Il dominio mittente di un'email non è verificato (emails[i]: …). Controllato solo con una chiave live | Niente |
| 413 | PAYLOAD_TOO_LARGE | Body della richiesta oltre 5 MiB | Niente |
| 429 | QUOTA_EXCEEDED | Restano meno email di quanti destinatari ha il batch in totale | Niente |
| 429 | RATE_LIMITED | Più di 10 batch al secondo per chiave, o un altro rate limit. Riprova dopo Retry-After se presente, altrimenti dopo un breve backoff | Niente |
| 409 | IDEMPOTENCY_CONFLICT | Stessa Idempotency-Key, body diverso | Niente |
| 409 | IDEMPOTENCY_IN_PROGRESS | Stessa Idempotency-Key mentre la prima richiesta è ancora in corso | Niente |
| 503 | SERVICE_UNAVAILABLE | Troppi batch in corso, oppure nessuna email è stata messa in coda. Arriva con Retry-After: 1 | Niente inviato; nessun addebito quando ci sono troppi batch in corso, altrimenti il messaggio dice se c'è stato un addebito |
| 500 | INTERNAL_ERROR | L'esito del salvataggio delle email non è noto | Niente inviato; può esserci un addebito |
Dopo un 503, ripeti la stessa richiesta: la stessa Idempotency-Key si può
riusare, perché nessuna email del batch è partita.
Dopo un 500 nessuna email del batch è partita, ma il batch può contare sulla
quota mensile. Rimandalo: la stessa Idempotency-Key si può riusare.
Con gli SDK
Ogni SDK invia il batch con una sola chiamata. La chiamata restituisce la
risposta 202, anche quando alcune email non sono partite: controlla
failed e lo status di ogni email. Gli errori (422, 429, 503, 500 e gli
altri in Errori) arrivano come eccezioni, e su un 422 il campo
details dell'eccezione indica l'email con la sua posizione, come in
/emails/3/to. Gli SDK non ripetono le chiamate: segui le regole qui sopra,
la stessa chiave dopo un 503 o un 500, una nuova chiave per le email non
partite.
Node.js
emails.sendBatch(emails, idempotencyKey?) richiede @tratto/email 1.6.0 o
successiva. Vedi l'SDK Node.js.
import { randomUUID } from 'node:crypto';
import { Tratto } from '@tratto/email';
const tratto = new Tratto(process.env.TRATTO_API_KEY!);
const batch = await tratto.emails.sendBatch(
[
{ from: 'hello@yourdomain.com', to: 'alice@example.com', subject: 'La tua ricevuta', text: 'Grazie, Alice.' },
{ from: 'hello@yourdomain.com', to: 'bob@example.com', subject: 'La tua ricevuta', text: 'Grazie, Bob.' },
],
randomUUID(),
);
console.log(`${batch.accepted} in coda, ${batch.failed} non inviate`);
for (const e of batch.emails) {
if (e.status === 'failed') console.error(e.to, e.error.code, e.error.message);
}Python
emails.send_batch(emails, *, idempotency_key=None) richiede tratto-email
2.1.0 o successiva. La chiave va sulla chiamata: un'email con un suo
idempotency_key solleva ValueError. Vedi l'
SDK Python.
import uuid
from tratto import SendEmailOptions, Tratto
client = Tratto('tratto_live_...')
result = client.emails.send_batch(
[
SendEmailOptions(from_='hello@yourdomain.com', to='alice@example.com', subject='La tua ricevuta', text='Grazie, Alice.'),
SendEmailOptions(from_='hello@yourdomain.com', to='bob@example.com', subject='La tua ricevuta', text='Grazie, Bob.'),
],
idempotency_key=str(uuid.uuid4()),
)
print(result['accepted'], result['failed'])
for item in result['emails']: # stesso ordine della richiesta
if item['status'] == 'failed':
print(item['to'], item['error']['code'])PHP
emails->sendBatch(array $emails, ?string $idempotencyKey = null) richiede
tratto/email 1.1.0 o successiva. Vedi l'SDK PHP.
$emails = [
['from' => 'hello@yourdomain.com', 'to' => ['alice@example.com'], 'subject' => 'La tua ricevuta', 'text' => 'Grazie, Alice.'],
['from' => 'hello@yourdomain.com', 'to' => ['bob@example.com'], 'subject' => 'La tua ricevuta', 'text' => 'Grazie, Bob.'],
];
$batch = $tratto->emails->sendBatch($emails, 'receipts-2026-10-21');
// Reinvia solo le email non partite, in un nuovo batch con una nuova chiave.
$failed = [];
foreach ($batch['emails'] as $i => $result) {
if ($result['status'] === 'failed') {
$failed[] = $emails[$i];
}
}
if ($failed !== []) {
$tratto->emails->sendBatch($failed, 'receipts-2026-10-21-retry-1');
}Prossimi Passi
- Un'email alla volta? Vedi Invia Email
- Seguire cosa succede dopo? Configura i Webhook
- Pianificare il throughput? Leggi Rate Limit e Quote
Modifica questa pagina su GitHub
Ultimo aggiornamento