Guida
Crea un’app OAuth
Permetti agli utenti di collegare la tua app al loro workspace Emailit con OAuth 2.1 e PKCE, con registrazione del client, aggiornamento e revoca dei token ed errori.
Emailit gestisce un server di autorizzazione OAuth 2.1 su https://api.emailit.com. Usalo quando crei un’integrazione, come un CRM, uno strumento no-code o un client AI, che agisce per conto degli utenti di Emailit. I tuoi utenti accedono e approvano l’accesso nel browser, e tu ottieni i token per i workspace che scelgono senza mai gestire le loro chiavi API. È lo stesso flusso che usa il server MCP.
Come funziona
-
Registra un client con la registrazione dinamica del client, oppure ospita un documento di metadati del client ID.
-
Indirizza l’utente all’URL di autorizzazione con un code challenge PKCE.
-
L’utente accede a Emailit, sceglie i workspace che la tua app può usare e approva il permesso (scope) richiesto.
-
Emailit reindirizza all’URI di reindirizzamento con un codice di autorizzazione monouso.
-
Scambia il codice con un token di accesso valido 15 minuti e un token di aggiornamento (refresh token).
-
Chiama l’API con il token di accesso, e aggiornalo quando scade.
Endpoint
| Metodo | Percorso | Scopo |
|---|---|---|
GET |
/.well-known/oauth-authorization-server |
Metadati del server di autorizzazione (RFC 8414) |
GET |
/.well-known/oauth-protected-resource |
Metadati della risorsa protetta per l’API REST |
GET |
/.well-known/oauth-protected-resource/mcp |
Metadati della risorsa protetta per il server MCP |
POST |
/oauth/register |
Registrazione dinamica del client (RFC 7591) |
GET |
/oauth/authorize |
Accesso e consenso nel browser |
POST |
/oauth/token |
Scambio di un codice o di un token di aggiornamento |
POST |
/oauth/revoke |
Revoca di un’autorizzazione tramite il suo token di aggiornamento (RFC 7009) |
GET |
/oauth/grants |
Elenco delle autorizzazioni (dell’utente che ha effettuato l’accesso, o di un workspace con una chiave API) |
POST |
/oauth/grants/:id/revoke |
Revoca di un’autorizzazione |
PUT |
/oauth/grants/:id/workspaces |
Modifica dei workspace che un’autorizzazione può usare (da parte dell’utente che ha collegato l’app) |
Tutti i percorsi si trovano su https://api.emailit.com.
Individua il server
curl https://api.emailit.com/.well-known/oauth-authorization-server{
"issuer": "https://api.emailit.com",
"authorization_endpoint": "https://api.emailit.com/oauth/authorize",
"token_endpoint": "https://api.emailit.com/oauth/token",
"registration_endpoint": "https://api.emailit.com/oauth/register",
"revocation_endpoint": "https://api.emailit.com/oauth/revoke",
"jwks_uri": "https://api.emailit.com/.well-known/jwks.json",
"code_challenge_methods_supported": ["S256"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"response_types_supported": ["code"],
"token_endpoint_auth_methods_supported": ["none", "client_secret_basic", "client_secret_post"],
"client_id_metadata_document_supported": true,
"authorization_response_iss_parameter_supported": true,
"scopes_supported": ["sending", "full"]
}I client MCP partono invece dai metadati della risorsa protetta. Una richiesta non autenticata a https://api.emailit.com/mcp restituisce 401 con WWW-Authenticate: Bearer resource_metadata="https://api.emailit.com/.well-known/oauth-protected-resource/mcp", e quel documento rimanda al server di autorizzazione:
{
"resource": "https://api.emailit.com/mcp",
"authorization_servers": ["https://api.emailit.com"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["sending", "full"]
}Permessi
| Permesso | Concede |
|---|---|
sending |
Inviare e inoltrare email, e riprogrammare, annullare e ritentare gli invii. Lo stesso accesso di una chiave API di solo invio. |
full |
Ogni endpoint dell’API REST e ogni strumento MCP. Lo stesso accesso di una chiave API con accesso completo. |
full include già tutto ciò che consente sending. Richiedi sending se la tua app si limita a inviare, altrimenti full; è accettato anche sending full separato da uno spazio. Se ometti scope, Emailit usa tutti i permessi registrati dal client.
Registra il client
Puoi registrare un client in due modi. Entrambi funzionano con ogni flusso di questa pagina.
Registrazione dinamica del client
Invia una richiesta di registrazione. Non serve autenticazione, e ogni indirizzo IP può registrare fino a 20 client all’ora.
curl https://api.emailit.com/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "Acme CRM",
"redirect_uris": ["https://crm.acme.com/oauth/emailit/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scope": "full",
"token_endpoint_auth_method": "client_secret_basic",
"client_uri": "https://crm.acme.com",
"logo_uri": "https://crm.acme.com/logo.png"
}'| Campo | Obbligatorio | Descrizione |
|---|---|---|
client_name |
Sì | Mostrato nella pagina di consenso. Fino a 200 caratteri. |
redirect_uris |
Sì | Da 1 a 10 URI di reindirizzamento. Vedi Regole per gli URI di reindirizzamento. |
grant_types |
No | Deve includere authorization_code; può includere refresh_token. Il valore predefinito li comprende entrambi. |
response_types |
No | Solo code. |
scope |
No | Permessi separati da spazi. Il valore predefinito è sending full. |
token_endpoint_auth_method |
No | none (predefinito) per i client pubblici come app desktop, mobile e browser; client_secret_basic o client_secret_post per le app lato server. |
client_uri |
No | La home page della tua app. |
logo_uri |
No | Logo mostrato nella pagina di consenso. |
La risposta 201 riporta i metadati e aggiunge un client_id. I client riservati ricevono anche un client_secret, mostrato una sola volta:
{
"client_id": "3e8a1f52-7c4b-4d29-8e6f-1a5b9c0d2e73",
"client_id_issued_at": 1790847000,
"client_name": "Acme CRM",
"redirect_uris": ["https://crm.acme.com/oauth/emailit/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scope": "full",
"token_endpoint_auth_method": "client_secret_basic",
"client_uri": "https://crm.acme.com",
"logo_uri": "https://crm.acme.com/logo.png",
"client_secret": "Ky7WI2z5HxoT6q1z19tuiIev_U1zX0_FKhIIfh0OBco",
"client_secret_expires_at": 0
}client_secret_expires_at è sempre 0: i secret non scadono. Ogni client, pubblico o riservato, deve usare PKCE.
Documento di metadati del client ID
Se la tua app può ospitare un file JSON statico, puoi saltare la registrazione. Pubblica un documento a un URL HTTPS e usa quell’URL come client_id:
{
"client_id": "https://crm.acme.com/oauth/client.json",
"client_name": "Acme CRM",
"client_uri": "https://crm.acme.com",
"logo_uri": "https://crm.acme.com/logo.png",
"redirect_uris": ["https://crm.acme.com/oauth/emailit/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"token_endpoint_auth_method": "none",
"scope": "full"
}- Il
client_idnel documento deve essere esattamente uguale all’URL del documento. token_endpoint_auth_methoddeve esserenone. Sono client pubblici che si basano su PKCE.- Gli URI di reindirizzamento HTTPS devono trovarsi sullo stesso host del documento.
- Emailit scarica il documento durante l’autorizzazione, con un timeout di 5 secondi e senza seguire i reindirizzamenti, e lo tiene in cache per 5 minuti.
- La pagina di consenso mostra l’host del documento, ad esempio
crm.acme.com, al posto diclient_name.
I client AI usano questo metodo: ChatGPT, Claude e Grok si identificano con i propri documenti di metadati del client ID, quindi si collegano senza registrarsi prima. Per Grok, Emailit accetta anche i reindirizzamenti a console.x.ai.
Regole per gli URI di reindirizzamento
- Gli URI
https://sono consentiti. http://è consentito solo per gli host di loopback:127.0.0.1,localhoste[::1]. Per gli URI di loopback la porta può essere diversa al momento dell’autorizzazione, ma host, percorso e query devono corrispondere.localhoste127.0.0.1sono host diversi.- Gli schemi a uso privato come
cursor://,vscode://ocom.acme.crm://sono consentiti per le app native. - Gli URI
file,ftp,data,javascript,blob,aboutevbscript, e qualsiasi URI con un#fragment, vengono rifiutati. - A parte le porte di loopback, il
redirect_uriche invii deve corrispondere esattamente a uno di quelli registrati.
Indirizza l’utente a Emailit
Crea un verifier e un challenge PKCE, e uno state casuale, per ogni autorizzazione:
import { createHash, randomBytes } from 'node:crypto';
const codeVerifier = randomBytes(32).toString('base64url');
const codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url');
const state = randomBytes(16).toString('base64url');
// Store codeVerifier and state in the user's session.Poi reindirizza il browser dell’utente all’endpoint di autorizzazione:
https://api.emailit.com/oauth/authorize
?response_type=code
&client_id=3e8a1f52-7c4b-4d29-8e6f-1a5b9c0d2e73
&redirect_uri=https%3A%2F%2Fcrm.acme.com%2Foauth%2Femailit%2Fcallback
&scope=full
&state=Jq3k9V0n2xR7bLm1
&code_challenge=zsrvXVr2pbLDHbccAx_NY9osQ6nwqTnDZlIeFWKQOsg
&code_challenge_method=S256
&resource=https%3A%2F%2Fapi.emailit.com| Parametro | Obbligatorio | Descrizione |
|---|---|---|
response_type |
Sì | code |
client_id |
Sì | Il tuo client ID, o l’URL del tuo documento di metadati. |
redirect_uri |
Sì | Uno dei tuoi URI di reindirizzamento registrati. |
code_challenge |
Sì | SHA-256 del code verifier, codificato in Base64url. |
code_challenge_method |
Consigliato | S256. plain non è supportato. |
scope |
Consigliato | sending o full. Deve essere un permesso registrato dal client. |
state |
Consigliato | Un valore casuale che controlli nella callback. |
resource |
No | La risorsa che vuoi chiamare (RFC 8707): https://api.emailit.com per l’API REST o https://api.emailit.com/mcp per MCP. I token funzionano comunque su entrambe. |
Apri questo URL nel browser dell’utente. Non richiederlo dal tuo backend.
Cosa vede l’utente
- Accesso a Emailit. L’utente inserisce email e password. Se usa un’app di autenticazione per l’autenticazione a due fattori, inserisce poi il suo codice o un codice di recupero.
- Scelta dei workspace. La pagina mostra il tuo logo, il nome del client (o l’host del documento di metadati) e i permessi richiesti. L’utente sceglie All my workspaces, che include i workspace che creerà o a cui si unirà in seguito, oppure Only these workspaces con quelli che spunta, e sceglie il workspace in cui la tua app inizia.
- Consenso all’accesso. L’utente seleziona Allow access o Deny.
Un’autorizzazione può coprire più workspace. L’utente può modificare l’elenco in seguito nel pannello in Account > Connected apps, e la tua app vede la modifica alla richiesta successiva.
Dopo l’approvazione, Emailit reindirizza all’URI di reindirizzamento:
https://crm.acme.com/oauth/emailit/callback?code=XvRKyG5Pkplrd3xkRNaEayKkpkebEMdF&state=Jq3k9V0n2xR7bLm1&iss=https%3A%2F%2Fapi.emailit.comControlla che state corrisponda al valore che hai salvato e che iss sia https://api.emailit.com. Il codice è valido 10 minuti e si può usare una sola volta. Se l’utente seleziona Deny, il reindirizzamento contiene invece error=access_denied.
Scambia il codice con i token
Invia una richiesta POST all’endpoint dei token come application/x-www-form-urlencoded (è accettato anche JSON). Invia lo stesso redirect_uri e il code verifier originale:
curl https://api.emailit.com/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=authorization_code \
-d code="$CODE" \
-d redirect_uri=https://crm.acme.com/oauth/emailit/callback \
-d code_verifier="$CODE_VERIFIER"Con client_secret_basic, invia client ID e secret nell’header Authorization: Basic. Con client_secret_post, invia invece client_id e client_secret come campi del modulo. Non inviare il secret in entrambi i modi.
curl https://api.emailit.com/oauth/token \
-d grant_type=authorization_code \
-d client_id="$CLIENT_ID" \
-d code="$CODE" \
-d redirect_uri=http://127.0.0.1:53682/callback \
-d code_verifier="$CODE_VERIFIER"I client pubblici inviano client_id e nessun secret. PKCE dimostra che il flusso è stato avviato dallo stesso client.
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im9hdXRoLWhzMjU2In0…",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "XvRKyG5Pkplrd3xkRNaEayKkpkebEMdFn0h1VIuqixAOXybMtJfK8w",
"scope": "full"
}Il token di accesso dura 15 minuti (expires_in è in secondi). Trattalo come una stringa opaca: non analizzarlo e non fare affidamento sul suo contenuto.
Chiama l’API
Usa il token di accesso come token Bearer sull’API REST e sul server MCP, esattamente come una chiave API:
curl https://api.emailit.com/v2/domains \
-H "Authorization: Bearer $ACCESS_TOKEN"Le richieste REST vengono eseguite nel workspace predefinito dell’autorizzazione: quello in cui l’utente ha scelto di iniziare, o quello impostato in seguito. Gli strumenti MCP possono anche agire su un altro workspace consentito con l’argomento workspace. Ogni richiesta usa il permesso concesso e il ruolo dell’utente nel workspace, quindi una richiesta fuori dal permesso, o una route riservata agli Admin chiamata da un membro con ruolo Member, restituisce 403. Se l’utente lascia un workspace, anche l’autorizzazione lo perde.
Aggiorna i token
Prima che il token di accesso scada, o quando una richiesta restituisce 401, ottienine uno nuovo:
curl https://api.emailit.com/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=refresh_token \
-d refresh_token="$REFRESH_TOKEN"- Rotazione. Ogni aggiornamento restituisce un nuovo
refresh_tokenvalido 60 giorni. Salvalo e scarta quello vecchio. Finché aggiorni entro 60 giorni, la connessione non scade. - Rilevamento del riutilizzo. Se un vecchio token di aggiornamento viene usato di nuovo più di un minuto dopo essere stato sostituito, Emailit restituisce
invalid_grant(Refresh token reuse detected) e revoca l’intera autorizzazione. L’utente deve quindi autorizzare di nuovo. Esegui gli aggiornamenti in sequenza, così due worker non usano mai lo stesso token di aggiornamento. - Permesso più ristretto. Puoi passare
scopeper ottenere un token di accesso con meno permessi dell’autorizzazione. Non puoi chiederne di più.
Revoca un’autorizzazione
Quando un utente scollega Emailit dalla tua app, revoca l’autorizzazione con il suo token di aggiornamento:
curl https://api.emailit.com/oauth/revoke \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d token="$REFRESH_TOKEN"L’endpoint autentica il client allo stesso modo dell’endpoint dei token e restituisce sempre 200 con un corpo vuoto. Revocare il token di aggiornamento revoca l’intera autorizzazione: i suoi token di accesso smettono di funzionare alla richiesta successiva. I token di accesso non si possono revocare singolarmente; token_type_hint=access_token restituisce invalid_request.
Gli utenti possono anche revocare l’accesso della tua app dal proprio lato, nel pannello in App collegate, o con l’API delle autorizzazioni descritta qui sotto.
Gestisci le autorizzazioni
L’API delle autorizzazioni elenca e modifica le autorizzazioni dal lato dell’utente. Accetta la sessione del pannello dell’utente o una chiave API con accesso completo:
| Chiamante | GET /oauth/grants |
POST /oauth/grants/:id/revoke |
PUT /oauth/grants/:id/workspaces |
|---|---|---|---|
| L’utente che ha collegato l’app | Le sue autorizzazioni in tutti i workspace | Revoca l’intera autorizzazione | Modifica i workspace dell’autorizzazione |
| Chiave API con accesso completo | Le autorizzazioni che includono il workspace della chiave | Rimuove il workspace della chiave dall’autorizzazione; l’autorizzazione viene revocata quando non resta alcun workspace | Non consentito (403) |
Ogni autorizzazione nell’elenco ha questa forma:
{
"id": "9b1f6c2e-4d3a-4f8e-9a7b-2c5d8e1f0a3b",
"client_id": "https://chatgpt.com/oauth/client.json",
"client": { "name": "ChatGPT", "uri": "https://chatgpt.com", "logo_uri": null },
"default_workspace": { "id": "w3f9a1c2e", "name": "Acme" },
"workspace_access": "selected",
"workspaces": [{ "id": "w3f9a1c2e", "name": "Acme" }],
"scopes": ["sending", "full"],
"resource": "https://api.emailit.com/mcp",
"created_at": "2026-10-01T09:30:12Z",
"revoked_at": null,
"revoked_reason": null
}Per modificare i workspace di un’autorizzazione, invia access (all o selected), workspace_ids per selected e un default_workspace_id facoltativo, che deve essere uno dei workspace consentiti:
curl -X PUT https://api.emailit.com/oauth/grants/9b1f6c2e-4d3a-4f8e-9a7b-2c5d8e1f0a3b/workspaces \
-H "Authorization: Bearer $DASHBOARD_SESSION" \
-H "Content-Type: application/json" \
-d '{ "access": "selected", "workspace_ids": ["w3f9a1c2e"], "default_workspace_id": "w3f9a1c2e" }'L’utente deve essere membro di ogni workspace che seleziona. Un’autorizzazione revocata non si può modificare (409); l’app deve collegarsi di nuovo.
Errori
Gli endpoint OAuth restituiscono gli errori nel formato OAuth, non nel formato degli errori dell’API REST:
{
"error": "invalid_grant",
"error_description": "Authorization code has expired"
}| Errore | Stato | Quando |
|---|---|---|
invalid_request |
400 | Un parametro manca o non è valido, il client è sconosciuto al momento dell’autorizzazione, il redirect_uri non è registrato, code_challenge_method non è S256, oppure un client secret è stato inviato sia nell’header sia nel corpo. |
invalid_client |
401 | L’endpoint dei token o di revoca non riesce ad autenticare il client: client sconosciuto, secret mancante o errato. |
invalid_grant |
400 | Il codice non è valido, è già stato usato o è scaduto; il redirect_uri o il verifier PKCE non corrispondono; oppure il token di aggiornamento non è valido, è scaduto, è stato revocato o riutilizzato. |
invalid_scope |
400 | Il permesso non è supportato, non è stato registrato per il client o, in fase di aggiornamento, supera quanto concesso dall’autorizzazione. |
unsupported_response_type |
400 | response_type non è code. |
unsupported_grant_type |
400 | grant_type non è authorization_code né refresh_token. |
access_denied |
Reindirizzamento | L’utente ha selezionato Deny. |
too_many_requests |
429 | Più di 20 registrazioni all’ora da uno stesso indirizzo IP. |
server_error |
500 | Si è verificato un problema dal lato di Emailit. Riprova. |
Finché client_id e redirect_uri non sono convalidati, gli errori di autorizzazione vengono mostrati come JSON nel browser e mai reindirizzati. Dopo, gli errori vengono reindirizzati alla tua callback (con error, error_description, state e iss) solo per gli URI di reindirizzamento di loopback e a uso privato e per i client con documento di metadati; gli altri client ricevono una pagina di errore JSON. Il Deny di un utente viene sempre reindirizzato.
Checklist di sicurezza
- Tieni
client_secrete i token di aggiornamento sul tuo server, cifrati a riposo. Non includere mai un client secret in un’app mobile, desktop o browser; usa invece un client pubblico con PKCE. - Genera un nuovo
statee un nuovo code verifier per ogni autorizzazione, e controllastateeissnella callback. - Registra URI di reindirizzamento esatti. Non usare open redirect come callback.
- Salva ogni autorizzazione con il workspace a cui appartiene, e gestisci
invalid_grantchiedendo all’utente di collegarsi di nuovo. - Richiedi
sendingse invii solo email.