MailSenpai / SMTP Senpai / API

Documentazione per sviluppatori

API e SMTP di SMTP Senpai

Manda le email transazionali del tuo software con una chiamata REST che risponde in JSON, oppure via SMTP con utente e password. Con la stessa chiave leggi il registro degli invii e gestisci gli indirizzi da non contattare. In più, un indirizzo da mettere nel form del tuo sito.

Indirizzo basehttps://app.mailsenpai.com/relay/v1
AutenticazioneBearer msp_…
FormatoJSON · UTF-8
SMTPrelay.mailsenpai.com:2525 STARTTLS

Arrivi da un’altra API di email? SMTP Senpai accetta anche le richieste nel formato di altre 14 API di email transazionali: cambi indirizzo base e chiave, il codice resta (percorsi /relay/compat/<servizio>/ oppure host dedicati come sendgrid-api.mailsenpai.com). Tutti i collegamenti pronti: integrazioni.

Novità della versione 1.2: API compatibili, intestazioni di buona consegna (List-Unsubscribe One-Click, Feedback-ID…) e fino a 500 destinatari per chiamata.

I marchi appartengono ai rispettivi titolari.

Indice della pagina
  1. Panoramica
  2. Autenticazione
  3. Primo invio in un minuto
  4. Esempi Node, Python, PHP
  5. Riferimento endpoint
    1. POST /invio
    2. GET /stato
    3. GET /statistiche
    4. GET /eventi
    5. GET /soppressi
    6. POST /sopprimi
    7. POST /riammetti
  6. Webhook
  7. Errori
  8. Limiti
  9. Modulo per i siti
  10. SMTP
  11. Buone pratiche
  12. FAQ

Tre modi per spedire

Portano tutti agli stessi server, con la stessa firma DKIM sul tuo dominio, lo stesso registro e la stessa lista di soppressione. Scegli in base a cosa hai già.

API REST

Una chiamata HTTPS per email, risposta in JSON con l'esito. Ideale per software su misura, funzioni serverless e automazioni.

SMTP

Server, porta, utente e password: funziona con qualunque programma o libreria di posta. Supporta allegati e più destinatari.

Modulo dei siti

Un indirizzo da mettere nell'attributo action del tuo form: ogni compilazione ti arriva per email. Nessun codice lato server.

Prima di spedire serve un dominio verificato: nell'area cliente trovi i record DNS da pubblicare (servono a firmare le email con DKIM sul tuo dominio) e li controlliamo noi. Il mittente di ogni email deve stare su quel dominio o su un suo sottodominio.

Autenticazione

Ogni chiamata all'API porta la tua chiave nell'intestazione Authorization: Bearer msp_…. La chiave inizia con msp_ ed è lunga 52 caratteri: la trovi nell'area cliente, pagina SMTP Senpai, riquadro Chiave per le tue applicazioni.

HTTP
Authorization: Bearer msp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • Rigenerandola dall'area cliente, quella vecchia smette subito di funzionare.
  • Tienila sul server, in una variabile d'ambiente: l'API non accetta chiamate dai browser (non invia intestazioni CORS), così la chiave non finisce nel codice di una pagina web. Per i form dei siti c'è l'indirizzo dei moduli, che non richiede chiave.
  • Se la tua piattaforma non permette intestazioni personalizzate, puoi passare la chiave nel campo chiave del corpo (JSON o form). Per le richieste GET si accetta anche come parametro dell'indirizzo, ma è sconsigliato: gli indirizzi finiscono nei registri dei server e dei proxy.
  • Le credenziali SMTP (utente e password) sono diverse dalla chiave API e si cambiano separatamente.

Chiave mancante o sbagliata:

HTTP 401
{
  "ok": false,
  "errore": "chiave non valida"
}

Il primo invio in un minuto

  1. Verifica il dominio da cui spedirai (area cliente, pagina Domini). Se hai appena attivato la prova, è il dominio che hai indicato all'iscrizione.
  2. Copia la chiave e mettila in una variabile d'ambiente: export SMTPSENPAI_CHIAVE=msp_…
  3. Lancia questa richiesta, con un destinatario tuo e un mittente sul dominio verificato:
curl
curl https://app.mailsenpai.com/relay/v1/invio \
  -H "Authorization: Bearer $SMTPSENPAI_CHIAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "a": "cliente@esempio.it",
    "da": "ordini@tuodominio.it",
    "oggetto": "Prova da SMTP Senpai",
    "testo": "Funziona!"
  }'

Se tutto è a posto la risposta è questa, e in pochi secondi l'email è nella casella:

HTTP 200
{
  "ok": true,
  "inviato": true,
  "id_messaggio": "9f2c4e1a7b3d5f6e8a9b0c1d@tuodominio.it",
  "residuo": 8759,
  "oltre_il_piano": false
}

Conserva id_messaggio: è anche il Message-ID dell'email e ti serve per ritrovarla nel registro.

Esempi pronti: Node, Python, PHP

Per ogni linguaggio c'è la versione con l'API REST e quella via SMTP. Gli esempi leggono chiave, utente e password da variabili d'ambiente: SMTPSENPAI_CHIAVE, SMTPSENPAI_UTENTE, SMTPSENPAI_PASSWORD.

Node.js · fetch
// Node.js 18+: fetch è già incluso
async function inviaEmail() {
  const risposta = await fetch('https://app.mailsenpai.com/relay/v1/invio', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.SMTPSENPAI_CHIAVE}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      a: 'cliente@esempio.it',
      da: 'ordini@tuodominio.it',
      nome_mittente: 'Il tuo negozio',
      oggetto: 'Conferma ordine 10293',
      html: '<p>Grazie, il tuo ordine è confermato.</p>',
      rispondi_a: 'assistenza@tuodominio.it',
    }),
  });
  const esito = await risposta.json();
  if (!esito.ok) {
    throw new Error(`${risposta.status}: ${esito.errore}`);
  }
  console.log(esito.inviato ? `Inviata: ${esito.id_messaggio}` : `Non inviata: ${esito.motivo}`);
}

inviaEmail().catch((e) => { console.error(e.message); process.exitCode = 1; });

API REST, nessuna dipendenza.

Node.js · Nodemailer
// npm install nodemailer
const nodemailer = require('nodemailer');

const transporter = nodemailer.createTransport({
  host: 'relay.mailsenpai.com',
  port: 2525,
  secure: false,      // si parte in chiaro...
  requireTLS: true,   // ...e si passa subito a STARTTLS
  auth: {
    user: process.env.SMTPSENPAI_UTENTE,
    pass: process.env.SMTPSENPAI_PASSWORD,
  },
});

async function inviaEmail() {
  const info = await transporter.sendMail({
    from: '"Il tuo negozio" <ordini@tuodominio.it>',
    to: 'cliente@esempio.it',
    subject: 'La tua fattura',
    text: 'In allegato trovi la fattura.',
    html: '<p>In allegato trovi la fattura.</p>',
    attachments: [{ filename: 'fattura.pdf', path: './fattura.pdf' }],
  });
  console.log('Accettata:', info.messageId);
}

inviaEmail().catch((e) => { console.error(e.message); process.exitCode = 1; });

Via SMTP, con un allegato.

Python · requests
# pip install requests
import os
import requests

risposta = requests.post(
    "https://app.mailsenpai.com/relay/v1/invio",
    headers={"Authorization": f"Bearer {os.environ['SMTPSENPAI_CHIAVE']}"},
    json={
        "a": "cliente@esempio.it",
        "da": "ordini@tuodominio.it",
        "nome_mittente": "Il tuo negozio",
        "oggetto": "Conferma ordine 10293",
        "html": "<p>Grazie, il tuo ordine è confermato.</p>",
        "testo": "Grazie, il tuo ordine è confermato.",
    },
    timeout=30,
)
esito = risposta.json()
if not esito["ok"]:
    raise RuntimeError(f"{risposta.status_code}: {esito['errore']}")
print(esito["id_messaggio"] if esito["inviato"] else esito["motivo"])

API REST.

Python · smtplib
import os
import smtplib
from email.message import EmailMessage

msg = EmailMessage()
msg["From"] = "Il tuo negozio <ordini@tuodominio.it>"
msg["To"] = "cliente@esempio.it"
msg["Subject"] = "La tua fattura"
msg.set_content("In allegato trovi la fattura.")
msg.add_alternative("<p>In allegato trovi la fattura.</p>", subtype="html")
with open("fattura.pdf", "rb") as allegato:
    msg.add_attachment(allegato.read(), maintype="application",
                       subtype="pdf", filename="fattura.pdf")

with smtplib.SMTP("relay.mailsenpai.com", 2525, timeout=30) as server:
    server.starttls()
    server.login(os.environ["SMTPSENPAI_UTENTE"], os.environ["SMTPSENPAI_PASSWORD"])
    server.send_message(msg)

Via SMTP, solo libreria standard, con un allegato.

PHP · cURL
<?php
$ch = curl_init('https://app.mailsenpai.com/relay/v1/invio');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('SMTPSENPAI_CHIAVE'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode([
        'a' => 'cliente@esempio.it',
        'da' => 'ordini@tuodominio.it',
        'nome_mittente' => 'Il tuo negozio',
        'oggetto' => 'Conferma ordine 10293',
        'html' => '<p>Grazie, il tuo ordine è confermato.</p>',
    ], JSON_UNESCAPED_UNICODE),
]);
$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$esito = json_decode((string) $body, true);
if (empty($esito['ok'])) {
    throw new RuntimeException($status . ': ' . ($esito['errore'] ?? 'risposta non valida'));
}
echo $esito['inviato'] ? $esito['id_messaggio'] : $esito['motivo'];

API REST, nessuna dipendenza.

PHP · PHPMailer
<?php
// composer require phpmailer/phpmailer
use PHPMailer\PHPMailer\PHPMailer;

require 'vendor/autoload.php';

$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host       = 'relay.mailsenpai.com';
$mail->Port       = 2525;
$mail->SMTPAuth   = true;
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Username   = getenv('SMTPSENPAI_UTENTE');
$mail->Password   = getenv('SMTPSENPAI_PASSWORD');
$mail->CharSet    = 'UTF-8';

$mail->setFrom('ordini@tuodominio.it', 'Il tuo negozio');
$mail->addAddress('cliente@esempio.it');
$mail->Subject = 'La tua fattura';
$mail->isHTML(true);
$mail->Body    = '<p>In allegato trovi la fattura.</p>';
$mail->AltBody = 'In allegato trovi la fattura.';
$mail->addAttachment('fattura.pdf');
$mail->send();

Via SMTP, con un allegato.

Nell'area cliente, nel riquadro Esempi pronti, trovi anche la configurazione per WordPress (WP Mail SMTP o FluentSMTP) già compilata con i tuoi dati.

SDK e librerie

Preferisci una libreria alle chiamate HTTP dirette? I pacchetti ufficiali usano questa API: npm install @mailsenpai/smtp-senpai (Node.js), npm install @mailsenpai/nestjs (NestJS), pip install mailsenpai (Python, con il backend per Django), gem install mailsenpai (Ruby e Rails, su RubyGems), composer require mailsenpai/symfony-mailer symfony/http-client (Symfony) e composer require mailsenpai/laravel (Laravel). I pacchetti per Strapi, Payload, Medusa, Directus, n8n e Activepieces (@mailsenpai/activepieces-piece-smtp-senpai, su npm), con i collegamenti ai registri e al codice sorgente su GitLab, sono nella pagina Integrazioni.

Riferimento endpoint

Indirizzo base https://app.mailsenpai.com/relay/v1. Tutte le risposte sono JSON con il campo ok. I messaggi di errore sono in italiano.

POST/relay/v1/invio

Manda una email a uno o più destinatari (fino a 500 per chiamata: ognuno riceve la sua copia e il suo identificativo), anche con allegati. Fino a 50 destinatari la chiamata spedisce subito e risponde 200; da 51 a 500 la richiesta viene accettata e messa in coda: risposta 202 con in_coda, le copie partono entro un minuto. Per copia e copia nascosta usa SMTP.

Campi

CampoObbligatorioDescrizione
asìDestinatario: un indirizzo, oppure un elenco fino a 500 indirizzi (oltre 50 vanno in coda, risposta 202).
dasìMittente, su un dominio verificato (o un suo sottodominio).
oggettosìOggetto. Gli accenti sono gestiti in automatico.
html / testoalmeno unoCorpo HTML e/o solo testo. Se mandi solo l'HTML, la versione testuale la ricaviamo noi.
nome_mittentenoNome visualizzato del mittente.
rispondi_anoIndirizzo per le risposte (Reply-To). Se non è valido viene ignorato.
intestazioninoOggetto JSON di intestazioni personalizzate. Passano i nomi che iniziano con X- (es. X-Ordine) e le intestazioni di buona consegna: List-Unsubscribe, List-Unsubscribe-Post (solo One-Click, con un indirizzo https), List-Id e le altre List-*, Feedback-ID, Precedence, In-Reply-To, References, priorità e lingua. Quelle che toccano mittente, struttura o server di invio vengono ignorate e sono elencate in intestazioni_ignorate; un valore non valido dà 400.

Ogni campo ha anche un nome inglese, che puoi usare al posto di quello italiano:

ItalianoInglese
ato
dafrom
nome_mittentefrom_name
oggettosubject
testotext
rispondi_areply_to
intestazioniheaders
allegatiattachments

Allegati

Elenco di oggetti con nome (filename), contenuto (content, in base64) e, se vuoi, tipo (content_type). Al massimo 10 file e 10 MB in tutto; i file eseguibili (.exe, .js, .bat e simili) vengono rifiutati.

Più destinatari

Con un elenco in a la risposta riporta l'esito di ognuno in risultati: chi è in soppressione viene trattenuto, gli altri partono.

Idempotenza

Aggiungi l'intestazione Idempotency-Key (fino a 100 caratteri, per esempio l'id del tuo ordine): se ripeti la stessa richiesta entro 24 ore ti restituiamo la prima risposta, con l'intestazione Idempotent-Replayed: true, e non spediamo di nuovo. La stessa chiave con dati diversi dà 422.

curl, due destinatari e un allegato
curl https://app.mailsenpai.com/relay/v1/invio \
  -H "Authorization: Bearer $SMTPSENPAI_CHIAVE" \
  -H "Idempotency-Key: ordine-10293" \
  -H "Content-Type: application/json" \
  -d '{
    "a": ["cliente@esempio.it", "amministrazione@esempio.it"],
    "da": "ordini@tuodominio.it",
    "oggetto": "La tua fattura",
    "testo": "In allegato trovi la fattura.",
    "allegati": [{"nome": "fattura.pdf", "contenuto": "JVBERi0xLjQK...", "tipo": "application/pdf"}]
  }'
HTTP 200, più destinatari
{
  "ok": true,
  "inviati": 2,
  "non_inviati": 0,
  "risultati": [
    {"a": "cliente@esempio.it", "inviato": true, "id_messaggio": "9f2c4e1a7b3d5f6e8a9b0c1d@tuodominio.it"},
    {"a": "amministrazione@esempio.it", "inviato": true, "id_messaggio": "1b7e0d2f4a6c8e9b3d5f7a1c@tuodominio.it"}
  ],
  "residuo": 8757,
  "oltre_il_piano": false
}

Il corpo può essere JSON (consigliato) oppure un normale form application/x-www-form-urlencoded.

Esempio

curl
curl https://app.mailsenpai.com/relay/v1/invio \
  -H "Authorization: Bearer $SMTPSENPAI_CHIAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "a": "cliente@esempio.it",
    "da": "ordini@tuodominio.it",
    "oggetto": "Prova da SMTP Senpai",
    "testo": "Funziona!"
  }'

Risposte

CodiceQuando
200 "inviato": trueAccettata: parte subito. Il campo residuo stima il volume rimasto; oltre_il_piano è vero se è passata grazie alla continuità di invio.
200 "inviato": falseIl destinatario è nella lista di soppressione: non parte e non consuma volume.
400Destinatario o mittente non validi, oggetto mancante, nessun corpo.
403Mittente su un dominio non verificato, oppure account sospeso.
429Volume del mese finito e continuità di invio spenta (o tetto di spesa raggiunto).
502Il server di invio non ha accettato il messaggio: il testo riporta la sua risposta.
409Una richiesta con la stessa Idempotency-Key è ancora in corso.
413Allegati oltre i 10 MB in tutto.
422Idempotency-Key già usata nelle ultime 24 ore con dati diversi.
HTTP 200, destinatario in soppressione
{
  "ok": true,
  "inviato": false,
  "motivo": "indirizzo nella lista di soppressione"
}

GET/relay/v1/stato

Stato dell'account, dati SMTP (senza password), volume del mese e numero di indirizzi in soppressione. stato vale active oppure pending (in attivazione). per_ora è il ritmo orario del tuo piano.

curl
curl https://app.mailsenpai.com/relay/v1/stato \
  -H "Authorization: Bearer $SMTPSENPAI_CHIAVE"
HTTP 200
{
  "ok": true,
  "stato": "active",
  "smtp": {
    "server": "relay.mailsenpai.com",
    "porta": 2525,
    "porta_alternativa": 2525,
    "utente": "r1234@tuodominio.it",
    "sicurezza": "STARTTLS"
  },
  "volume": {
    "mese": "2026-10",
    "incluso": 10000,
    "usato": 1240,
    "residuo": 8760,
    "per_ora": 400
  },
  "tracciamento": false,
  "soppressi": 17
}

GET/relay/v1/statistiche

Totali degli ultimi giorni e andamento giorno per giorno. Parametro giorni: da 1 a 90, predefinito 30. Il dettaglio per_giorno copre al massimo gli ultimi 30 giorni e, se non ci sono movimenti, arriva come array vuoto []. Aperture e clic compaiono solo se hai attivato il tracciamento.

curl
curl "https://app.mailsenpai.com/relay/v1/statistiche?giorni=7" \
  -H "Authorization: Bearer $SMTPSENPAI_CHIAVE"
HTTP 200
{
  "ok": true,
  "giorni": 7,
  "dati": {
    "sent": 812,
    "delivered": 798,
    "bounce": 9,
    "defer": 14,
    "complaint": 0,
    "tracciamento": "non attivo: ...",
    "tasso_consegna": 98.3,
    "tasso_rimbalzo": 1.11,
    "tasso_segnalazioni": 0.0
  },
  "per_giorno": {
    "2026-09-30": { "delivered": 120, "bounce": 2 },
    "2026-10-01": { "sent": 95, "delivered": 93 }
  }
}

GET/relay/v1/eventi

Gli ultimi movimenti, dal più recente: il registro dettagliato copre 7 giorni sul gratuito e 90 sui piani a pagamento. Parametri: quanti (da 1 a 500, predefinito 50) e tipo. Per leggere solo le novità usa il cursore dopo (after): con dopo=0 ricevi i movimenti dal più vecchio, e il campo prossimo è il valore da passare alla chiamata successiva; altri vale true se ce ne sono ancora. È il modo giusto per leggerli a intervalli, senza perderne e senza doppioni. Le date sono in UTC.

TipoSignificato
sentAccettata via API o modulo.
deliveredConsegnata al server del destinatario.
deferRinviata dal destinatario: riproviamo in automatico.
bounceRifiuto definitivo: l'indirizzo entra nella lista di soppressione.
complaintSegnalazione di spam: l'indirizzo entra nella lista di soppressione.
droppedNon spedita perché il destinatario era già in soppressione.
open / clickAperture e clic, solo con il tracciamento attivo (altrimenti chiederli dà 403).
curl
curl "https://app.mailsenpai.com/relay/v1/eventi?tipo=bounce&quanti=20" \
  -H "Authorization: Bearer $SMTPSENPAI_CHIAVE"
HTTP 200
{
  "ok": true,
  "eventi": [
    {
      "event_id": "88213",
      "relay_id": "12",
      "type": "bounce",
      "email": "mario@esempio.it",
      "domain": "esempio.it",
      "from_domain": "tuodominio.it",
      "code": "550",
      "reason": "5.1.1 user unknown",
      "message_id": "",
      "url": "",
      "happened_at": "2026-10-01 08:42:10"
    }
  ]
}
curl, solo le novità
curl "https://app.mailsenpai.com/relay/v1/eventi?dopo=88213&quanti=100" \
  -H "Authorization: Bearer $SMTPSENPAI_CHIAVE"
HTTP 200
{
  "ok": true,
  "eventi": [ { "event_id": 88214, "type": "delivered", "email": "mario@esempio.it", "...": "..." } ],
  "prossimo": "88214",
  "altri": false
}

GET/relay/v1/soppressi

Gli indirizzi (e i domini interi, scritti @dominio.it) a cui non scriviamo più. Parametri: quanti (da 1 a 1000, predefinito 100) e cerca. reason vale bounce, complaint oppure manuale; totale conta tutte le voci, senza il filtro.

curl
curl "https://app.mailsenpai.com/relay/v1/soppressi?cerca=esempio.it" \
  -H "Authorization: Bearer $SMTPSENPAI_CHIAVE"
HTTP 200
{
  "ok": true,
  "totale": 2,
  "elenco": [
    { "supp_id": "501", "relay_id": "12", "email": "mario@esempio.it",
      "reason": "bounce", "note": "5.1.1 user unknown", "created_at": "2026-10-01 08:42:10" },
    { "supp_id": "488", "relay_id": "12", "email": "@concorrente.it",
      "reason": "manuale", "note": "", "created_at": "2026-09-20 10:00:00" }
  ]
}

POST/relay/v1/sopprimi

Aggiunge un indirizzo, oppure un dominio intero (@dominio.it o dominio.it), alla lista. Campo facoltativo nota (max 255 caratteri). Risponde {"ok": true, "email": "…", "gia_presente": false}; se la voce c'era già gia_presente è true. Una voce non valida dà 400.

curl
curl https://app.mailsenpai.com/relay/v1/sopprimi \
  -H "Authorization: Bearer $SMTPSENPAI_CHIAVE" \
  -H "Content-Type: application/json" \
  -d '{"email": "mario@esempio.it", "nota": "ha chiesto di non essere contattato"}'

POST/relay/v1/riammetti

Toglie dalla lista una voce che hai aggiunto tu. Scrivila esattamente come compare in elenco (i domini con la chiocciola). Gli indirizzi entrati per un rimbalzo o una segnalazione di spam vanno ricontrollati da noi: chiedilo dall'area cliente, così nessuno toglie per errore un blocco che protegge il recapito. Per queste voci la risposta è 409; per una voce che non c'è, 404.

curl
curl https://app.mailsenpai.com/relay/v1/riammetti \
  -H "Authorization: Bearer $SMTPSENPAI_CHIAVE" \
  -H "Content-Type: application/json" \
  -d '{"email": "mario@esempio.it"}'

Webhook: gli esiti in tempo reale

Invece di leggere /eventi a intervalli, puoi farti avvisare: a ogni evento del tuo account mandiamo un POST JSON all'indirizzo https che scegli. Un evento per chiamata, così funziona anche con Zapier, Make e n8n. Lo imposti nell'area cliente, riquadro Avvisi in tempo reale, oppure con l'API qui sotto.

EventoQuando arriva
sentAccettata dai nostri server (via API o modulo).
deliveredConsegnata al server del destinatario.
bounceRifiuto definitivo: l'indirizzo entra in soppressione.
deferRinviata: riproviamo in automatico.
complaintSegnalata come spam.
droppedTrattenuta perché il destinatario è in soppressione.
open / clickAperture e clic, solo con il tracciamento attivo.

Il corpo della chiamata

JSON
{
  "id": "evt_88214",
  "type": "delivered",
  "created_at": "2026-10-09T10:26:01Z",
  "data": {
    "email": "mario@esempio.it",
    "domain": "esempio.it",
    "from_domain": "tuodominio.it",
    "code": "2.0.0",
    "reason": "",
    "message_id": "9f2c4e1a7b3d5f6e8a9b0c1d@tuodominio.it",
    "url": ""
  },
  "account": "codice-del-tuo-account"
}
IntestazioneContenuto
X-MailSenpai-EventIl tipo di evento.
X-MailSenpai-DeliveryIdentificativo della consegna: resta uguale nei tentativi successivi, usalo per non registrare due volte lo stesso evento.
X-MailSenpai-Signaturet=<unix>,v1=<hex>: firma HMAC-SHA256 di "<t>.<corpo>" con il tuo segreto (whsec_…).

Rispondi con un codice 2xx entro 8 secondi. Se il tuo server non risponde o dà un errore riproviamo dopo 1 minuto, 5 minuti, 30 minuti, 2, 6 e 12 ore: circa 24 ore in tutto. Accettiamo solo indirizzi https pubblici.

Verificare la firma

Node.js
import crypto from 'node:crypto';

// corpo grezzo della richiesta (stringa) e intestazione X-MailSenpai-Signature
function firmaValida(body, header, secret) {
  const m = /t=(\d+),v1=([a-f0-9]+)/.exec(header || '');
  if (!m) return false;
  if (Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false; // max 5 minuti
  const atteso = crypto.createHmac('sha256', secret).update(`${m[1]}.${body}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(atteso), Buffer.from(m[2]));
}
PHP
<?php
$body = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_MAILSENPAI_SIGNATURE'] ?? '';
$secret = getenv('MAILSENPAI_WEBHOOK_SECRET');
if (!preg_match('/t=(\d+),v1=([a-f0-9]+)/', $sig, $m) || abs(time() - (int) $m[1]) > 300
    || !hash_equals(hash_hmac('sha256', $m[1] . '.' . $body, $secret), $m[2])) {
    http_response_code(401); exit;
}
$evento = json_decode($body, true);
http_response_code(200);
Python
import hmac, hashlib, re, time

def firma_valida(body: bytes, header: str, secret: str) -> bool:
    m = re.match(r"t=(\d+),v1=([a-f0-9]+)", header or "")
    if not m or abs(time.time() - int(m.group(1))) > 300:
        return False
    atteso = hmac.new(secret.encode(), m.group(1).encode() + b"." + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(atteso, m.group(2))

Impostarlo con l'API

ChiamataCosa fa
GET /relay/v1/webhookIndirizzo, eventi scelti, stato e consegne delle ultime 24 ore.
POST /relay/v1/webhookImposta url, eventi (events), attivo (active); con nuovo_segreto (rotate_secret) genera un segreto nuovo. Un url vuoto spegne il webhook. La risposta contiene il segreto.
POST /relay/v1/webhook/provaManda subito un evento di prova (con "test": true) e ti dice cosa ha risposto il tuo server.
curl
curl https://app.mailsenpai.com/relay/v1/webhook \
  -H "Authorization: Bearer $SMTPSENPAI_CHIAVE" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://tuosito.it/webhook/mailsenpai", "eventi": ["delivered", "bounce", "complaint"]}'

Errori

Un errore ha sempre "ok": false e un campo errore con la spiegazione in italiano, insieme al codice HTTP:

CodiceSignificatoCosa fare
400Dati mancanti o non validi.Correggi la richiesta: non ripeterla uguale.
401Chiave mancante o non valida.Controlla l'intestazione Authorization; se hai rigenerato la chiave, aggiorna la variabile.
403Dominio del mittente non verificato, tracciamento non attivo, oppure account sospeso (il messaggio dice perché: piano scaduto o non pagato, volume finito, tetto di spesa).Verifica il dominio o controlla il piano nell'area cliente.
404Indirizzo inesistente (endpoint o modulo).Controlla il percorso.
405Metodo HTTP sbagliato (per esempio POST su /stato).Usa il metodo indicato nell'intestazione Allow.
409Voce bloccata dai nostri server (riammetti) oppure Idempotency-Key ancora in corso.Chiedi lo sblocco dall'area cliente; per l'idempotenza riprova tra qualche secondo.
413Allegati troppo grandi.Resta sotto i 10 MB, oppure usa SMTP (fino a 50 MB).
422Idempotency-Key riusata con dati diversi, oppure webhook non valido.Usa una chiave nuova per ogni invio diverso.
429Volume del mese finito senza continuità di invio.Aumenta il volume o accendi la continuità: la risposta contiene i link alle pagine giuste.
502Il server di invio ha rifiutato o non era raggiungibile.Riprova dopo qualche secondo, con attesa crescente. Prima di ripetere, controlla in /eventi che l'email non sia già partita.
HTTP 429
{
  "ok": false,
  "errore": "volume del mese esaurito: aumenta il piano o accendi la continuità di invio dalla tua area",
  "residuo": 0,
  "aumenta": "https://app.mailsenpai.com/customer/il-mio-piano",
  "pannello": "https://app.mailsenpai.com/customer/relay-smtp"
}

Limiti

CosaLimite
Destinatari per chiamata APIFino a 500: ognuno riceve la sua copia. Fino a 50 partono subito, da 51 a 500 vanno in coda (risposta 202). Via SMTP puoi anche indicarne più d'uno nello stesso messaggio.
VolumeQuello del tuo piano, per mese di calendario. Contano le email arrivate a un esito (consegnate o rimbalzate); quelle trattenute dalla soppressione no. Ti avvisiamo per email prima che finisca.
Volume finitoSul gratuito gli invii si fermano al raggiungimento delle soglie, senza addebiti né accodamento delle email rifiutate. Nei piani a pagamento, con la continuità di invio accesa si prosegue a blocchi fino al tetto di spesa scelto; con continuità spenta gli invii si fermano fino al mese successivo o all’aumento del piano.
Ritmo orarioSul gratuito: fino a 30 email nell’ultima ora; superata la soglia, i nuovi invii vengono rifiutati fino a quando torna disponibilità, senza conservare le email rifiutate. Nei piani a pagamento vale il ritmo del piano (campo per_ora di /stato): oltre quel ritmo i messaggi restano in coda; con un IP dedicato nuovo il ritmo sale per gradi nelle prime settimane.
Richieste APINessun limite di frequenza dichiarato: resta comunque il volume del piano.
Dimensione messaggio SMTP50 MB
Registro dettagliato7 giorni sul gratuito, 90 sui piani a pagamento (/eventi, fino a 500 righe per chiamata); i totali restano oltre.
StatisticheFino a 90 giorni; andamento giornaliero fino a 30.
IdempotenzaCon l'intestazione Idempotency-Key la stessa richiesta ripetuta entro 24 ore non spedisce di nuovo.
Allegati via API10 file e 10 MB in tutto, niente eseguibili.
Moduli dei siti25 moduli per account, 3 destinatari per modulo (+1 con _cc), 60 campi da 5.000 caratteri, 25 invii l'ora dalla stessa rete.

Modulo per i siti

Per i siti che non sanno mandare email (pagine statiche, temi senza plugin, hosting che bloccano la posta). Crea il modulo nell'area cliente, copia il suo indirizzo e mettilo nell'attributo action del tuo form, con method="POST". Non serve nessuna chiave.

HTML
<form action="https://app.mailsenpai.com/relay/f/CODICE" method="POST">
  <label>Nome <input type="text" name="nome" required></label>
  <label>Email <input type="email" name="email" required></label>
  <label>Messaggio <textarea name="messaggio" required></textarea></label>

  <input type="hidden" name="_subject" value="Nuova richiesta dal sito">
  <input type="hidden" name="_next" value="https://www.tuosito.it/grazie/">
  <input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">

  <button type="submit">Invia</button>
</form>

Sostituisci CODICE con il codice del tuo modulo. Ogni compilazione ti arriva per email, impaginata, con il Reply-To di chi ha scritto: premi rispondi e gli scrivi.

Campi speciali

CampoA cosa serve
_subjectOggetto della email che ricevi. In mancanza vale quello impostato nel modulo.
_replytoIndirizzo a cui rispondere. Se manca, usiamo il primo campo che ha mail nel nome e contiene un indirizzo valido.
_nextPagina di ringraziamento (indirizzo completo, http o https) per l'invio classico, su un tuo dominio verificato o sullo stesso sito della pagina di ringraziamento impostata nel modulo. In mancanza vale quella del modulo, oppure mostriamo una pagina di conferma.
_ccUna copia a un altro indirizzo. Funziona solo se l'hai permesso nelle impostazioni del modulo.
_gotchaCampo esca: tienilo nascosto e vuoto. Se un robot lo riempie, la compilazione viene scartata in silenzio.
_formatCon il valore plain ricevi la email in solo testo.

Gli altri nomi che iniziano con _ vengono ignorati. I file allegati al form non vengono trasmessi.

Invio con JavaScript

Se mandi il form con fetch e l'intestazione Accept: application/json (oppure con un corpo JSON), la risposta è JSON e puoi restare sulla pagina. L'indirizzo accetta richieste da qualunque sito (CORS): per limitarlo ai tuoi, indica i domini nelle impostazioni del modulo.

JavaScript
const form = document.querySelector('form[action*="/relay/f/"]');

form.addEventListener('submit', async (event) => {
  event.preventDefault();
  const risposta = await fetch(form.action, {
    method: 'POST',
    body: new FormData(form),
    headers: { Accept: 'application/json' },
  });
  const esito = await risposta.json();
  if (esito.ok) {
    form.replaceWith('Grazie, ti rispondiamo presto.');
  } else {
    alert(esito.errore);
  }
});
RispostaQuando
200 {"ok": true, ...}Ricevuto e inoltrato. Con l'invio classico: rimando con 303 a _next o pagina di conferma.
403Modulo in pausa, servizio non attivo, oppure sito non fra i domini abilitati.
404Codice del modulo inesistente.
409Non c'è ancora un dominio di invio verificato.
422Il form è arrivato senza campi compilati.
429Troppi invii dalla stessa rete nell'ultima ora, oppure volume del mese finito.

Le email dei moduli partono da moduli@tuodominio.it, con il nome del modulo come mittente, e contano nel volume del mese come tutte le altre. Se vuoi, chi compila riceve una risposta automatica con le tue parole, e ogni compilazione resta archiviata nell'area cliente.

SMTP

Per i programmi che hanno già una schermata server SMTP (gestionali, e-commerce, CRM, WordPress) e quando servono allegati o più destinatari.

ParametroValore
Serverrelay.mailsenpai.com
Porta2525 (quella indicata nella tua area, campo porta di /stato)
CifraturaSTARTTLS (obbligatoria: l'accesso è possibile solo dopo STARTTLS). Nei plugin si chiama spesso TLS.
AutenticazioneLOGIN / PLAIN con utente e password della tua area
Dimensione massima50 MB per messaggio, allegati compresi
Altro8BITMIME, SMTPUTF8

Cosa succede in SMTP

  • Mittente (From) su un dominio non verificato: il messaggio viene rifiutato con 550 5.7.1.
  • Destinatario nella lista di soppressione: il server risponde OK, così il tuo programma non va in errore, ma l'email non parte, non consuma volume e nel registro compare come dropped.
  • Credenziali sbagliate: 535 5.7.8.
  • Gli invii SMTP finiscono nello stesso registro e nelle stesse statistiche dell'API.

Buone pratiche

  • Pubblica tutti i record DNS che trovi nell'area: la firma DKIM sul tuo dominio e l'SPF dicono ai provider che le email sono davvero tue. Aggiungi un record DMARC, partendo da p=none.
  • Spedisci sempre da un indirizzo del dominio verificato e usa rispondi_a se le risposte devono arrivare altrove.
  • Manda sempre anche la versione testuale, o lascia che la ricaviamo noi dall'HTML: le email solo immagine o solo HTML arrivano peggio.
  • Lascia lavorare la soppressione: rimbalzi definitivi e segnalazioni di spam escono da soli dagli invii. Aggiungi tu chi chiede di non essere più contattato.
  • Riprova solo sugli errori temporanei (502), con attesa crescente; mai su 400, 401 e 403.
  • Usa le intestazioni X-… per collegare l'email al tuo sistema (numero d'ordine, id utente) e conserva id_messaggio.
  • Controlla ogni tanto /statistiche: un tasso di rimbalzo che sale è il primo segnale di una lista da pulire.
  • Tieni separate le email di servizio da quelle promozionali: per le campagne c'è la piattaforma MailSenpai.

Domande frequenti

Posso mandare allegati con l'API?

Sì: con il campo allegati (base64), fino a 10 file e 10 MB in tutto. Per messaggi più grandi usa SMTP, fino a 50 MB (vedi gli esempi Nodemailer, smtplib e PHPMailer).

Come mando la stessa email a più persone?

Metti fino a 500 indirizzi in un elenco: ognuno riceve la sua copia e ha il suo esito, come si fa per le email transazionali. Oltre 50 la richiesta va in coda (risposta 202). Copia e copia nascosta sono disponibili solo via SMTP.

Ci sono i webhook per consegne e rimbalzi?

Non ancora. Per sapere com'è andata leggi /eventi (ad esempio ogni pochi minuti, filtrando per tipo) oppure /statistiche per i totali.

Esiste un ambiente di prova?

Non c'è una sandbox separata: con il piano gratuito hai un account vero, senza carta e senza scadenza, con cui spedire a indirizzi tuoi. Il pulsante di prova nell'area manda una email di verifica con le tue credenziali.

Posso chiamare l'API dal browser?

No, e non è un caso: la chiave darebbe a chiunque la possibilità di spedire a tuo nome. Chiama l'API dal tuo server o da una funzione serverless. Per i form usa l'indirizzo dei moduli, che è fatto apposta.

Le email partono subito?

Sì: la risposta 200 arriva quando il messaggio è stato preso in carico dai server di invio, che lo consegnano subito nel rispetto del ritmo orario del piano. Se il server del destinatario rimanda, riproviamo in automatico.

Cosa succede quando finisce il volume del mese?

Ti avvisiamo per email prima che succeda. Se hai acceso la continuità di invio si prosegue a blocchi fino al tetto di spesa che hai scelto; altrimenti gli invii si fermano (l'API risponde 429) finché non aumenti il volume o non inizia il mese nuovo.

Le email vengono firmate con DKIM?

Sì, sul tuo dominio, sia via API sia via SMTP, una volta pubblicati i record DNS che trovi nell'area cliente.

Posso usare SMTP Senpai con Zapier, Make o n8n?

Sì, con il modulo HTTP generico della piattaforma: metodo POST, indirizzo /relay/v1/invio, intestazione Authorization con la tua chiave e corpo JSON come negli esempi. Dove c'è un modulo SMTP, puoi usare anche quello.

Prova con il tuo dominio

Parti gratis, senza carta e senza scadenza. Chiave API, credenziali SMTP e moduli si attivano dopo la conferma dell’email e la verifica del dominio.