Sostituisci il provider email
PUT /v1/email-provider · autorizzazione email-provider:write · tutti i piani
Sostituisce il modo in cui l'ambiente invia le email. Puoi scrivere cinque modalità:
platform_default: KeenAuth invia dal proprio indirizzo. Si può impostare soloreply_to.platform_managed_domain: KeenAuth invia dal tuo dominio, firmato con DKIM per quel dominio. Aggiungi prima il dominio con domini mittenti email e pubblica prima i suoi record DNS; il dominio deve essereverified. Impostasender_domain_ide unfrom_addressesattamente in quel dominio (non in un suo sottodominio). Solo ambienti di produzione. Se il dominio in seguito smette di essere verificato, le email vengono inviate comeplatform_defaultfinché il dominio non torna verificato; la configurazione in sé non cambia.customer_smtp: il tuo server SMTP sulla porta465(TLS implicito) o587(STARTTLS è obbligatorio). Il certificato deve essere valido per il nome host.customer_api: il tuo account pressoses,sendgrid,postmark,mailgunobrevo.customer_webhook: KeenAuth genera il messaggio e lo consegna a uno dei tuoi endpoint webhook; la tua applicazione lo invia (vedi Consegna webhook). Impostawebhook_endpoint_idsu un endpoint abilitato di questo ambiente.
If-Match è obbligatorio e contiene la versione che hai letto (l'ETag di get). Per un ambiente mai configurato, invia If-Match: "0"; * non corrisponde. Una mancata corrispondenza restituisce 412.
La credenziale può essere scritta una sola volta. Inviala in credentials; nessuna risposta la restituisce. Se la ometti, la credenziale salvata viene mantenuta solo se modalità e destinazione (host SMTP, porta e nome utente, oppure provider) non cambiano. Altrimenti la richiesta fallisce con /credentials required.
Una modifica cancella il risultato dell'ultimo test di connessione e scrive un record di audit (email_provider.updated) con la modalità, il provider e solo l'id del dominio mittente, mai un indirizzo o la credenziale. Dopo una modifica, esegui un test di connessione.
Richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
mode |
stringa | sì | platform_default, platform_managed_domain, customer_smtp, customer_api o customer_webhook. |
sender_domain_id |
stringa | platform_managed_domain |
Un dominio mittente verified di questo ambiente (sdom_…). Non ammesso in altre modalità. |
webhook_endpoint_id |
stringa | customer_webhook |
Un endpoint webhook abilitato di questo ambiente (whe_…). Non ammesso in altre modalità. |
from_address |
stringa | tutte tranne platform_default |
Indirizzo mittente semplice, ad esempio noreply@example.com. In platform_managed_domain il suo dominio deve essere esattamente il dominio mittente. Non ammesso in platform_default. |
reply_to |
stringa | no | Indirizzo di risposta semplice. |
provider |
stringa | customer_api |
ses, sendgrid, postmark, mailgun o brevo. |
smtp_host |
stringa | customer_smtp |
Nome DNS con almeno due etichette (non un indirizzo IP). |
smtp_port |
intero | customer_smtp |
465 o 587. |
smtp_username |
stringa | customer_smtp |
Nome utente SMTP AUTH, da 1 a 256 caratteri. |
credentials |
stringa | vedi sopra | Solo scrittura, da 1 a 1024 byte. customer_smtp: la password SMTP. customer_api: <region>:<access key id>:<secret access key> per ses; la chiave API per sendgrid e mailgun (prefisso eu: per l'endpoint UE del provider); il token del server per postmark; la chiave API per brevo. |
Risposta
200 OK con la configurazione salvata (stessa struttura di get) e ETag impostato sulla nuova versione.
Errori
| HTTP | code | Quando |
|---|---|---|
| 400 | unknown_field |
Il corpo contiene un campo sconosciuto oppure non è un oggetto JSON. |
| 412 | precondition_failed |
If-Match non corrisponde alla versione salvata (qualcuno ha salvato nel frattempo), oppure è * prima della prima scrittura. |
| 413 | payload_too_large |
Il corpo supera i 16 KiB. |
| 428 | precondition_required |
If-Match manca. |
| 422 | validation_failed |
Un campo non supera la validazione; errors[] indica il campo e la regola (vedi sotto). |
| 401 | unauthenticated |
La credenziale manca, non è valida o è stata revocata. |
| 403 | insufficient_permissions |
La credenziale non ha l'autorizzazione richiesta. |
| 429 | rate_limited |
È stato superato il limite di richieste; riprova dopo il periodo indicato in Retry-After. |
| 503 | service_unavailable |
Una dipendenza non è disponibile temporaneamente; riprova dopo il periodo indicato in Retry-After. |
Regole in errors[]:
| Puntatore | Regola | Significato |
|---|---|---|
/mode |
required, invalid |
La modalità manca o è sconosciuta. |
/mode |
delivery_mode_unavailable |
La modalità non può inviare in questa installazione: customer_webhook richiede che la connessione di consegna webhook sia abilitata, platform_managed_domain richiede che la firma DKIM sia abilitata, e le modalità cliente richiedono che la consegna in uscita sia abilitata. |
/mode |
production_only |
platform_managed_domain è consentito solo negli ambienti di produzione. |
/mode |
fields_not_allowed |
È stato inviato un campo che la modalità non accetta. customer_webhook non accetta provider, campi SMTP, credentials o sender_domain_id. |
/sender_domain_id |
required, invalid |
platform_managed_domain è stato inviato senza un id di dominio mittente, oppure l'id non è un id sdom_…. |
/sender_domain_id |
not_found |
Questo ambiente non ha un dominio mittente con quell'id. Un dominio di un altro ambiente riceve la stessa risposta. |
/sender_domain_id |
not_verified |
Il dominio mittente esiste ma non è verified (record DNS in attesa, degradato o disabilitato). |
/from_address |
not_in_sender_domain |
L'indirizzo mittente non è esattamente nel dominio mittente. |
/webhook_endpoint_id |
required, invalid |
customer_webhook è stato inviato senza un id endpoint, oppure l'id non è un id whe_…. |
/webhook_endpoint_id |
not_found |
Questo ambiente non ha un endpoint webhook con quell'id. Un endpoint di un altro ambiente riceve la stessa risposta. |
/webhook_endpoint_id |
not_enabled |
L'endpoint esiste ma non è abilitato. |
/credentials |
required |
La modalità o la destinazione sono cambiate e non è stata inviata una nuova credenziale. |
/credentials |
invalid |
La credenziale è vuota, troppo lunga, contiene caratteri di controllo o non rispetta il formato del provider. |
/from_address, /reply_to, /provider, /smtp_host, /smtp_port, /smtp_username |
required, invalid |
Il campo manca o non è valido per la modalità. |
Consegna webhook
Nella modalità customer_webhook KeenAuth non invia il messaggio e non ricorre mai al proprio invio. Per ogni messaggio che genera (oggetto, HTML e testo), invia un evento firmato all'endpoint tramite HTTPS:
- La richiesta è
POSTconContent-Type: application/jsone gli header Standard Webhooks v1webhook-id,webhook-timestampewebhook-signature.webhook-idè l'id del messaggio (msg_…) ed è lo stesso in ogni tentativo dello stesso messaggio, quindi deduplica su di esso. Il tipo di evento non deve trovarsi nella sottoscrizione agli eventi dell'endpoint: l'endpoint lo riceve perché è l'endpoint di consegna. - Il successo è qualsiasi risposta
2xx. I reindirizzamenti non vengono seguiti e contano come errori. Un tentativo scade dopo 10 secondi. - Un tentativo fallito viene ripetuto dopo 15 secondi, per un massimo di 3 tentativi in totale. Dopo il terzo errore il messaggio viene contrassegnato come fallito e non viene inviata alcuna email. Un endpoint eliminato o disabilitato dopo che hai salvato la configurazione fa fallire il messaggio subito, senza tentativi.
- Il contenuto del messaggio non viene memorizzato dal servizio webhook. Invia solo a endpoint di cui ti fidi: il corpo contiene l'indirizzo del destinatario e qualsiasi codice di accesso o link.
Corpo (schema_version 1):
{
"id": "msg_01k7xq3m5r8t9v0w1x2y3z4a5b",
"type": "email.send_requested",
"schema_version": 1,
"environment_id": "env_01k7xq3m5r8t9v0w1x2y3z4a5b",
"occurred_at": "2026-10-27T09:00:00Z",
"data": {
"message_id": "msg_01k7xq3m5r8t9v0w1x2y3z4a5b",
"family": "email_otp",
"locale": "en",
"to": "ada@example.com",
"from": "auth@example.com",
"from_name": "Example",
"reply_to": "support@example.com",
"subject": "Your sign-in code",
"html": "<p>Your code is 123456</p>",
"text": "Your code is 123456",
"link": "https://example.keenauth.app/...",
"code": "123456"
}
}
Campo di data |
Descrizione |
|---|---|
message_id, family, locale |
L'id del messaggio (uguale all'id dell'envelope), la famiglia di template e la lingua in cui il messaggio è stato generato. |
to |
Indirizzo del destinatario. |
from, from_name, reply_to |
Presenti solo se impostati: from è il from_address di questa configurazione. |
subject |
Oggetto generato. |
html, text |
Contenuto generato; almeno uno dei due è presente. |
link, code |
Presenti solo quando il messaggio contiene un link di accesso o un codice monouso. |
La firma è v1, seguito dal base64 di HMAC-SHA256(key, "<webhook-id>.<webhook-timestamp>.<raw body>"). La chiave è il valore di 32 byte codificato dai 43 caratteri base62 che seguono il prefisso idp_whsec_ del segreto di firma dell'endpoint. Verifica il corpo grezzo della richiesta prima di analizzarlo, e rifiuta un timestamp che si discosta più di 300 secondi dal tuo orologio. Durante la rotazione di un segreto, webhook-signature contiene due firme separate da spazi.
Python:
import base64, hashlib, hmac, time
ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
PREFIX = "idp_whsec_"
def signing_key(secret: str) -> bytes:
"""The HMAC key is the 32-byte value behind the 43 base62 characters after the prefix."""
body = secret[len(PREFIX):len(PREFIX) + 43]
n = 0
for ch in body:
n = n * 62 + ALPHABET.index(ch)
return n.to_bytes(32, "big")
def verify(secret: str, headers: dict, raw_body: bytes, tolerance: int = 300) -> bool:
msg_id = headers["webhook-id"]
ts = headers["webhook-timestamp"]
if abs(time.time() - int(ts)) > tolerance:
return False
signed = f"{msg_id}.{ts}.".encode() + raw_body
mac = hmac.new(signing_key(secret), signed, hashlib.sha256).digest()
expected = "v1," + base64.b64encode(mac).decode()
# webhook-signature holds one or more space-separated signatures.
return any(hmac.compare_digest(expected, s) for s in headers["webhook-signature"].split(" "))
Node.js:
import { createHmac, timingSafeEqual } from "node:crypto";
const ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const PREFIX = "idp_whsec_";
// The HMAC key is the 32-byte value behind the 43 base62 characters after the prefix.
function signingKey(secret) {
let n = 0n;
for (const ch of secret.slice(PREFIX.length, PREFIX.length + 43)) n = n * 62n + BigInt(ALPHABET.indexOf(ch));
return Buffer.from(n.toString(16).padStart(64, "0"), "hex");
}
export function verify(secret, headers, rawBody, tolerance = 300) {
const id = headers["webhook-id"];
const ts = headers["webhook-timestamp"];
if (Math.abs(Date.now() / 1000 - Number(ts)) > tolerance) return false;
const mac = createHmac("sha256", signingKey(secret)).update(`${id}.${ts}.`).update(rawBody).digest();
const expected = Buffer.from(`v1,${mac.toString("base64")}`);
// webhook-signature holds one or more space-separated signatures.
return headers["webhook-signature"].split(" ").some((s) => {
const got = Buffer.from(s);
return got.length === expected.length && timingSafeEqual(got, expected);
});
}
Esempio
curl -X PUT https://api.eu.keenauth.com/v1/email-provider \
-H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
-H "Content-Type: application/json" \
-H 'If-Match: "0"' \
-d @email-provider.json
email-provider.json:
{
"mode": "customer_smtp",
"from_address": "no-reply@example.com",
"smtp_host": "smtp.example.com",
"smtp_port": 587,
"smtp_username": "mailer",
"credentials": "<SMTP password>"
}
Per inviare i messaggi a un endpoint webhook:
{
"mode": "customer_webhook",
"webhook_endpoint_id": "whe_01k7xq3m5r8t9v0w1x2y3z4a5b",
"from_address": "auth@example.com"
}
In questa modalità non c'è alcuna connessione da verificare con test; testa l'endpoint con Testa un endpoint webhook.
Per inviare dal tuo dominio mittente verificato:
{
"mode": "platform_managed_domain",
"sender_domain_id": "sdom_01k7xq3m5r8t9v0w1x2y3z4a5b",
"from_address": "auth@mail.example.com",
"reply_to": "support@example.com"
}
In questa modalità non c'è alcuna connessione da verificare con test; controlla invece il dominio con verify.
CLI: keenauth email-provider put --if-match '"0"' --data @email-provider.json. Conserva la credenziale in un file oppure passala dallo standard input (--data -), così non finisce nella cronologia della shell.