Invia Email
L'endpoint principale per inviare email tramite Tratto
POST /v1/emails è l'endpoint principale per inviare email. Invia un'email transazionale semplice, usa un template, programma per dopo, o allega file.
Per inviare fino a 100 email in una sola chiamata, vedi Invio in Batch.
Prerequisiti
Prima di poter inviare email, hai bisogno di un dominio di invio verificato. Vedi Domini per la configurazione.
Invio Minimo
Invia un'email di base con solo i campi obbligatori.
cURL
curl -X POST https://api.tratto.email/v1/emails \
-H "Authorization: Bearer tratto_live_..." \
-H "Content-Type: application/json" \
-d '{
"from": "hello@tuodominio.it",
"to": "user@example.com",
"subject": "Benvenuto!",
"text": "Ciao, questa è un\'email di test."
}'Node.js
const response = await fetch('https://api.tratto.email/v1/emails', {
method: 'POST',
headers: {
'Authorization': 'Bearer tratto_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'hello@tuodominio.it',
to: 'user@example.com',
subject: 'Benvenuto!',
text: 'Ciao, questa è un\'email di test.',
}),
});
const { data } = await response.json();
console.log('Email inviata:', data.id);Python
import requests
response = requests.post(
'https://api.tratto.email/v1/emails',
headers={
'Authorization': 'Bearer tratto_live_...',
'Content-Type': 'application/json',
},
json={
'from': 'hello@tuodominio.it',
'to': 'user@example.com',
'subject': 'Benvenuto!',
'text': 'Ciao, questa è un\'email di test.',
},
)
data = response.json()['data']
print(f"Email inviata: {data['id']}")PHP
$tratto = new \Tratto\Client(getenv('TRATTO_API_KEY'));
$email = $tratto->emails->send([
'from' => 'hello@tuodominio.it',
'to' => 'user@example.com',
'subject' => 'Benvenuto!',
'text' => 'Ciao, questa è un\'email di test.',
]);
echo "Email inviata: {$email['id']}\n";Risposta:
{
"data": {
"id": "email_abc123xyz",
"status": "queued",
"from": "hello@tuodominio.it",
"to": "user@example.com",
"subject": "Benvenuto!",
"text": "Ciao, questa è un'email di test.",
"createdAt": "2025-06-30T12:00:00Z"
}
}Invia con HTML
Includi sia text (fallback) che html (contenuto renderizzato):
curl -X POST https://api.tratto.email/v1/emails \
-H "Authorization: Bearer tratto_live_..." \
-H "Content-Type: application/json" \
-d '{
"from": "hello@tuodominio.it",
"to": "user@example.com",
"subject": "Benvenuto!",
"text": "Ciao, questa è un\'email di test.",
"html": "<h1>Ciao!</h1><p>Questa è un\'email di test.</p>"
}'I client email mostrano html se supportato; ricadere a text altrimenti.
A seconda del tuo piano, Tratto può aggiungere un footer alla versione HTML — il badge "Sent using Tratto", obbligatorio su alcuni piani (oggi Free) e facoltativo sugli altri. Vedi Footer Email & Branding.
Invia con Markdown
Passa un corpo markdown invece di html e Tratto lo renderizza lato server
in HTML responsive ed email-safe (più una parte in testo semplice) usando il
formato Markdown:
curl -X POST https://api.tratto.email/v1/emails \
-H "Authorization: Bearer tratto_live_..." \
-H "Content-Type: application/json" \
-d '{
"from": "hello@tuodominio.it",
"to": "user@example.com",
"subject": "Benvenuto!",
"markdown": "# Benvenuto, {{firstName}}!\n\nGrazie per esserti registrato.\n\n[Apri la dashboard](https://app.tuodominio.it){button}",
"variables": {"firstName": "Alice"}
}'Regole e comportamento:
markdownehtmlsono mutuamente esclusivi — inviarli entrambi fallisce con 422 ("markdown and html are mutually exclusive — provide one or the other.").- Almeno uno tra
html,text,markdownotemplateIdè obbligatorio, e unmarkdownvuoto fallisce con 422 ("markdown must not be empty."). - Il markdown viene renderizzato una volta, alla richiesta; l'HTML e il testo risultanti vengono memorizzati sull'email, quindi i retry di consegna non ri-renderizzano mai.
- Un campo
textesplicito vince sulla parte testuale generata dal render. - Le
{{variabili}}vengono sostituite alla consegna, dopo il render — come per ogni altro invio. - L'HTML raw dentro il markdown viene escapato a testo letterale, e i link
javascript:/data:non diventano mai link. Vedi Template Markdown per la sintassi e il modello di sicurezza.
Invia con un Template
Invece di comporre HTML nel codice, usa un template con sostituzione di variabili.
Step 1: Crea un template (vedi Template)
{
"name": "Email di Benvenuto",
"html": "<h1>Ciao {{firstName}}!</h1><p>Il tuo codice: {{code}}</p>"
}Un template contiene un nome e un corpo. Non ha un oggetto: quello appartiene al singolo invio.
Step 2: Invia usando il template
curl -X POST https://api.tratto.email/v1/emails \
-H "Authorization: Bearer tratto_live_..." \
-H "Content-Type: application/json" \
-d '{
"from": "hello@tuodominio.it",
"to": "user@example.com",
"subject": "Benvenuta, Alice!",
"templateId": "tmpl_abc123",
"variables": {
"firstName": "Alice",
"code": "SECRET123"
}
}'subject è obbligatorio su ogni invio. L'oggetto e l'html del template (e,
per un template Markdown, la sua parte testuale fissata) vengono renderizzati
con le variables che passi; una variabile che non passi diventa una stringa
vuota.
Il template deve essere pubblicato. Questa richiesta non legge il template,
quindi risponde comunque 202 anche per uno in draft, ma l'email viene poi
marcata status: "failed" e non parte. Con l'API 0.29.0 o successiva
GET /v1/emails/{id} restituisce failureReason: "template_not_published", e
i webhook iscritti a failed ricevono un
evento failed; prima di quella versione
l'email risultava failed senza spiegazione. Pubblica il template prima di
inviare: vedi Template.
Destinatari: to, cc e bcc
Richiede l'API 0.21.0 o successiva.
Un'email può avere al massimo 50 destinatari distinti fra to, cc e
bcc insieme. Gli indirizzi si confrontano ignorando maiuscole e minuscole, e
un indirizzo che compare più volte, nello stesso campo o in campi diversi,
conta una volta sola. Oltre 50 l'API risponde 422 VALIDATION_ERROR e non
viene scritto né addebitato niente:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed.",
"docs": "https://docs.tratto.email/en/docs/error-codes",
"details": [{ "path": "/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." }]
}
}Dividi l'invio in più email, oppure usa una campagna per un pubblico più ampio.
Ogni destinatario distinto conta come un'email della
quota mensile. Un'email con to
a@example.com, cc A@example.com e bcc b@example.com conta come due
email. Il controllo è tutto o niente: se all'account restano meno email
di quanti destinatari distinti ha l'email, l'API risponde 429 QUOTA_EXCEEDED e non parte
niente, nemmeno verso i primi destinatari.
Il messaggio dipende da chi fa la richiesta. Il proprietario del workspace, nella dashboard, vede quante email restano:
{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "This email has 3 recipients, but only 2 of the 3000 monthly emails of the free plan remain. 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."
}
}Una chiave API o un membro ricevono lo stesso errore senza il residuo:
{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "This email has 3 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."
}
}GET /v1/emails/{id} e GET /v1/emails restituiscono to, cc e bcc
senza duplicati: di ogni indirizzo resta la prima grafia, nell'ordine to,
poi cc, poi bcc, e un cc o un bcc rimasto vuoto dopo la deduplica
viene omesso.
Programma un'Email
Invia un'email a un orario futuro usando il campo scheduledAt (formato ISO 8601):
curl -X POST https://api.tratto.email/v1/emails \
-H "Authorization: Bearer tratto_live_..." \
-H "Content-Type: application/json" \
-d '{
"from": "hello@tuodominio.it",
"to": "user@example.com",
"subject": "Promemoria!",
"text": "Ci vediamo domani.",
"scheduledAt": "2026-12-01T09:00:00Z"
}'L'email verrà messa in coda e inviata all'orario specificato (deve essere nel futuro).
Quota delle email programmate
Un'email live programmata consuma la quota mensile
del mese UTC del suo scheduledAt, non del mese in cui fai la richiesta:
un'email per destinatario distinto (richiede l'API 0.21.0 o successiva).
Vengono prenotate quando l'API accetta il POST. Se in quel mese
restano meno email di quanti destinatari distinti ha l'email, la richiesta fallisce con 429 QUOTA_EXCEEDED e il messaggio nomina il
mese:
{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "This email has 3 recipients, more than the emails left for 2026-11 on the free plan (3000 per month). Each distinct address in to, cc and bcc counts as one email. A scheduled email counts against the UTC month of its scheduledAt. The quota is shared by every workspace of the account that owns this one.",
"docs": "https://docs.tratto.email/en/docs/error-codes",
"suggestion": "Cancel or reschedule other emails for 2026-11, send to fewer recipients, or have the workspace owner upgrade the plan at https://app.tratto.email/settings/billing."
}
}Un mese corrente pieno non ti impedisce di programmare in un mese successivo che ha ancora spazio. Le email in test mode non toccano la quota mensile. Non esiste un orizzonte massimo di programmazione.
Il controllo avviene solo quando l'email viene accettata, quindi le email già programmate restano valide dopo un downgrade di piano.
Annullare un'email programmata
DELETE /v1/emails/{id} annulla un'email ancora scheduled e restituisce la
sua quota, un'email per destinatario distinto, al mese su cui era prenotata. Risponde 204 senza corpo. Un'email
già queued o sent non si può annullare: l'API risponde 409 CONFLICT.
curl -X DELETE https://api.tratto.email/v1/emails/email_abc123xyz \
-H "Authorization: Bearer tratto_live_..."Riprogrammare un'email programmata
PATCH /v1/emails/{id} con un nuovo scheduledAt, che deve essere nel
futuro, sposta un'email ancora scheduled. Nello stesso mese UTC la quota non
cambia. In un altro mese la quota si sposta con l'email: se nel mese di
destinazione restano meno email di quanti destinatari distinti ha l'email, l'API risponde 429 QUOTA_EXCEEDED e l'email resta
com'era. Il body accetta solo scheduledAt: con l'API 0.24.0 o successiva
qualunque altro campo fallisce con 422 VALIDATION_ERROR e l'email non cambia
(vedi Campi sconosciuti).
curl -X PATCH https://api.tratto.email/v1/emails/email_abc123xyz \
-H "Authorization: Bearer tratto_live_..." \
-H "Content-Type: application/json" \
-d '{"scheduledAt": "2026-12-01T09:00:00Z"}'{ "data": { "id": "email_abc123xyz", "scheduledAt": "2026-12-01T09:00:00.000Z" } }DELETE e PATCH possono anche rispondere 429 RATE_LIMITED con un header
Retry-After, quando altre richieste stanno aggiornando nello stesso momento
il contatore dell'account. Ripetere la richiesta dopo quell'attesa è sicuro.
Gli SDK non hanno metodi per annullare o riprogrammare un'email: chiama l'API via HTTP come negli esempi qui sopra.
Allegati
Richiede l'API 0.29.0 o successiva, e un piano Starter, Growth o Scale.
Carica prima ogni file con POST /v1/attachments, poi passa gli id che
restituisce in attachments:
curl -X POST https://api.tratto.email/v1/emails \
-H "Authorization: Bearer tratto_live_..." \
-H "Content-Type: application/json" \
-d '{
"from": "billing@yourdomain.com",
"to": "user@example.com",
"subject": "La tua fattura",
"text": "In allegato trovi la fattura.",
"attachments": [{ "id": "att_V1StGXR8Z5jdHi6BmyT0a" }]
}'L'email, il suo html e text e i suoi file insieme possono arrivare al
massimo a 25 MB (25.000.000 byte), senza limite sul numero di file. Un id
sconosciuto è 404 NOT_FOUND; un id ripetuto, un file dell'altra modalità
(live o test), uno che scade meno di un'ora dopo l'invio, o un'email oltre i
25 MB è 422 VALIDATION_ERROR; sul piano Free l'invio è
403 PLAN_UPGRADE_REQUIRED. In nessuno di questi casi viene scritto o
addebitato qualcosa.
Come caricare i file, i limiti di dimensione, i tipi vietati, la conservazione e lo spazio sono in Allegati.
Tag per l'Organizzazione
Aggiungi tag per organizzare e filtrare le email:
curl -X POST https://api.tratto.email/v1/emails \
-H "Authorization: Bearer tratto_live_..." \
-H "Content-Type: application/json" \
-d '{
"from": "hello@tuodominio.it",
"to": "user@example.com",
"subject": "Conferma Ordine",
"text": "Il tuo ordine è stato confermato.",
"tags": ["ordine", "transazionale"]
}'Successivamente, filtra le email per tag o usali nei webhook.
Riferimento Completo del Request Body
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
from | string | Mittente, come Nome <email> o indirizzo nudo. Con una chiave live il dominio deve essere verificato. Se omesso si usa il mittente transazionale del workspace, o quello generale; se mancano entrambi, 422. Vedi Mittenti | |
to | string | string[] | ✓ | Indirizzo del destinatario, o un array di indirizzi. to, cc e bcc insieme: al massimo 50 destinatari distinti, vedi Destinatari |
cc | string[] | Destinatari di copia carbone | |
bcc | string[] | Destinatari di copia carbone nascosta | |
subject | string | ✓ | Riga di oggetto dell'email (obbligatoria anche con un template, max 998 caratteri) |
text | string | Contenuto testo semplice (usato come fallback) | |
html | string | Contenuto HTML (mutuamente esclusivo con markdown) | |
markdown | string | Markdown, renderizzato lato server (mutuamente esclusivo con html, max 100.000 caratteri) | |
templateId | string | Template ID (al posto di text/html/markdown) | |
variables | object | Variabili da interpolate nel template | |
scheduledAt | string (ISO 8601) | Programma email per consegna futura | |
tags | string[] | Tag di organizzazione | |
replyTo | string | Indirizzo di risposta. Se omesso in una richiesta senza from, si applica il replyTo del mittente transazionale | |
headers | object | Custom email headers (avanzato) | |
attachments | object[] | File caricati con POST /v1/attachments, come [{ "id": "att_..." }] (richiede l'API 0.29.0 o successiva). Vedi Allegati |
Campi sconosciuti
Con l'API 0.24.0 o successiva, un campo che non è in questa tabella fallisce
con 422 VALIDATION_ERROR e non parte niente. I nomi distinguono maiuscole e
minuscole e sono in camelCase, quindi reply_to o scheduled_at sono campi
sconosciuti. La voce di details nomina in message il campo o i campi sconosciuti; per i
campi al primo livello del body, path è / e c'è una sola voce per tutti
(Unrecognized keys: "reply_to", "foo"):
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed.",
"docs": "https://docs.tratto.email/en/docs/error-codes",
"details": [
{ "path": "/", "message": "Unrecognized key: \"reply_to\"" }
]
}
}Le versioni precedenti scartavano i campi sconosciuti e accettavano l'email
con 202, quindi un replyTo scritto male partiva senza indirizzo di
risposta. Lo stesso controllo vale per
PATCH /v1/emails/{id} e vale da sempre
per i batch.
Risposta
Successo (202 Accepted): l'email è accettata e messa in coda. livemode
è true con una chiave live e false con una chiave di test. Per leggere lo
status e gli altri campi, chiama GET /v1/emails/{id}.
{
"data": {
"id": "email_abc123xyz",
"livemode": true
}
}Errore (4xx):
{
"error": {
"code": "FORBIDDEN",
"message": "The sending domain is not verified.",
"docs": "https://docs.tratto.email/it/domains",
"suggestion": "Add and verify the domain at https://app.tratto.email/domains before sending."
}
}Errori Comuni
FORBIDDEN: dominio non verificato
Causa: Il dominio from non è ancora verificato. L'API restituisce 403 con il
dominio indicato in message.
Fix: Completa il processo di verifica del dominio.
QUOTA_EXCEEDED
Causa: All'account restano, questo mese, meno email di quanti destinatari distinti
ha l'email, oppure, con scheduledAt, nel mese per cui è programmata. Non è
partito niente.
Fix: Invia a meno destinatari, aspetta il mese UTC successivo o passa a un piano superiore. Riprovare la stessa richiesta non serve. Vedi Destinatari.
RATE_LIMITED
Causa: Più di 100 richieste in un secondo con la stessa chiave API, o più di 500 richieste in un minuto dallo stesso IP.
Fix: Implementa backoff esponenziale e riprova più tardi. Vedi Rate Limits.
SERVICE_UNAVAILABLE
Causa: Tratto non è riuscito a mettere l'email nella coda di consegna (la
coda ha dato errore o non ha risposto entro 10 secondi). L'API restituisce 503
con l'header Retry-After e il messaggio Nothing was sent and nothing was charged. Retry the request; the same Idempotency-Key can be reused.
Nessuna email è stata creata e nessuna quota è stata consumata: riprovare non può causare un doppio invio.
Con l'API 0.21.0 o successiva lo stesso 503, con Retry-After: 1 e lo stesso
messaggio, copre anche un'email che non è stato possibile salvare: la quota
viene restituita. Se fallisce anche la restituzione, il messaggio è
Nothing was sent. This request may still count against the monthly quota. Retry the request; the same Idempotency-Key can be reused. Anche in questo caso non è partito niente. Se il salvataggio
segnala un errore ma l'email è stata scritta comunque, la richiesta prosegue e
risponde 202.
Fix: Riprova dopo i secondi indicati da Retry-After. Se hai inviato una
Idempotency-Key, riusa la stessa chiave. Vedi Idempotenza.
VALIDATION_ERROR
Causa: Richiesta non valida (campo mancante, JSON malformato, più di 50
destinatari distinti fra to, cc e bcc, un
campo sconosciuto con l'API 0.24.0 o successiva, un
allegato non valido, ecc.).
Fix: Controlla il messaggio di errore e consulta il riferimento Codici di Errore.
Prossimi Passi
- Molte email insieme? Vedi Invio in Batch
- Tracciare gli eventi email? Configura Webhook
- Controllare lo stato dell'email? Vedi Stato e Ciclo di Vita dell'Email
- Creare template? Vai a Template
- Gestire domini? Leggi Domini
Modifica questa pagina su GitHub
Ultimo aggiornamento