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/emails tranne scheduledAt. Per programmare un invio, manda l'email da sola con POST /v1/emails.
  • attachments non è accettato: risponde 422.
  • Un campo sconosciuto risponde 422: sia il body sia ogni email sono controllati in modo stretto. POST /v1/emails fa lo stesso con l'API 0.24.0 o successiva, vedi Campi sconosciuti.
  • Al massimo 50 destinatari distinti fra to, cc e bcc, 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"] }
    ]
  }
}
  • emails ha una voce per ogni email della richiesta, nello stesso ordine.
  • accepted conta le email messe in coda per la consegna; failed conta quelle non inviate ed è sempre presente, 0 se sono andate tutte.
  • Un'email queued ha un id: il suo stato si legge con GET /v1/emails/{id}, come per qualsiasi altra email. Un'email failed non ha id e non verrà inviata.
  • L'error.message di un'email fallita dice se c'è stato un addebito. Quando non è stato possibile restituire la quota, il messaggio dice che may 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

StatoCodiceQuandoScritto o addebitato
422VALIDATION_ERROR0 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 renderNiente
403FORBIDDENIl dominio mittente di un'email non è verificato (emails[i]: …). Controllato solo con una chiave liveNiente
413PAYLOAD_TOO_LARGEBody della richiesta oltre 5 MiBNiente
429QUOTA_EXCEEDEDRestano meno email di quanti destinatari ha il batch in totaleNiente
429RATE_LIMITEDPiù di 10 batch al secondo per chiave, o un altro rate limit. Riprova dopo Retry-After se presente, altrimenti dopo un breve backoffNiente
409IDEMPOTENCY_CONFLICTStessa Idempotency-Key, body diversoNiente
409IDEMPOTENCY_IN_PROGRESSStessa Idempotency-Key mentre la prima richiesta è ancora in corsoNiente
503SERVICE_UNAVAILABLETroppi batch in corso, oppure nessuna email è stata messa in coda. Arriva con Retry-After: 1Niente inviato; nessun addebito quando ci sono troppi batch in corso, altrimenti il messaggio dice se c'è stato un addebito
500INTERNAL_ERRORL'esito del salvataggio delle email non è notoNiente 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


Modifica questa pagina su GitHub

Ultimo aggiornamento