Leitfaden
OAuth-App entwickeln
Lassen Sie Nutzer Ihre App per OAuth 2.1 und PKCE mit ihrem Emailit-Workspace verbinden, einschließlich Client-Registrierung, Token-Erneuerung, Widerruf und Fehlern.
Emailit betreibt unter https://api.emailit.com einen OAuth-2.1-Autorisierungsserver. Nutzen Sie ihn, wenn Sie eine Integration entwickeln, etwa ein CRM, ein No-Code-Tool oder einen KI-Client, die im Namen von Emailit-Nutzern handelt. Ihre Nutzer melden sich im Browser an und erlauben den Zugriff, und Sie erhalten Tokens für die Workspaces, die sie auswählen, ohne je mit ihren API-Schlüsseln umzugehen. Denselben Ablauf verwendet auch der MCP-Server.
So funktioniert es
-
Registrieren Sie einen Client per dynamischer Client-Registrierung oder hosten Sie ein Client-ID-Metadatendokument.
-
Leiten Sie den Nutzer zur Autorisierungs-URL weiter und übergeben Sie dabei eine PKCE-Code-Challenge.
-
Der Nutzer meldet sich bei Emailit an, wählt die Workspaces, die Ihre App nutzen darf, und bestätigt den angeforderten Scope.
-
Emailit leitet zurück an Ihre Redirect-URI, mit einem einmalig gültigen Autorisierungscode.
-
Tauschen Sie den Code ein gegen ein Zugriffstoken mit 15 Minuten Laufzeit und ein Refresh-Token.
-
Rufen Sie die API auf und verwenden Sie dabei das Zugriffstoken; erneuern Sie es, wenn es abläuft.
Endpunkte
| Methode | Pfad | Zweck |
|---|---|---|
GET |
/.well-known/oauth-authorization-server |
Metadaten des Autorisierungsservers (RFC 8414) |
GET |
/.well-known/oauth-protected-resource |
Metadaten der geschützten Ressource für die REST-API |
GET |
/.well-known/oauth-protected-resource/mcp |
Metadaten der geschützten Ressource für den MCP-Server |
POST |
/oauth/register |
Dynamische Client-Registrierung (RFC 7591) |
GET |
/oauth/authorize |
Anmeldung und Zustimmung im Browser |
POST |
/oauth/token |
Code oder Refresh-Token eintauschen |
POST |
/oauth/revoke |
Freigabe mit ihrem Refresh-Token widerrufen (RFC 7009) |
GET |
/oauth/grants |
Freigaben auflisten (die des angemeldeten Nutzers oder die eines Workspaces per API-Schlüssel) |
POST |
/oauth/grants/:id/revoke |
Freigabe widerrufen |
PUT |
/oauth/grants/:id/workspaces |
Ändern, welche Workspaces eine Freigabe nutzen kann (durch den Nutzer, der die App verbunden hat) |
Alle Pfade liegen auf https://api.emailit.com.
Server ermitteln
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"]
}MCP-Clients beginnen stattdessen mit den Metadaten der geschützten Ressource. Eine nicht authentifizierte Anfrage an https://api.emailit.com/mcp gibt 401 mit WWW-Authenticate: Bearer resource_metadata="https://api.emailit.com/.well-known/oauth-protected-resource/mcp" zurück, und dieses Dokument verweist auf den Autorisierungsserver:
{
"resource": "https://api.emailit.com/mcp",
"authorization_servers": ["https://api.emailit.com"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["sending", "full"]
}Scopes
| Scope | Gewährt |
|---|---|
sending |
Senden und Weiterleiten von E-Mails sowie Verschieben, Stornieren und erneutes Senden von Sendungen. Derselbe Zugriff wie ein reiner Sende-Schlüssel. |
full |
Alle Endpunkte der REST-API und alle MCP-Tools. Derselbe Zugriff wie ein API-Schlüssel mit Vollzugriff. |
full umfasst bereits alles, was sending erlaubt. Fordern Sie sending an, wenn Ihre App nur sendet, andernfalls full; auch ein durch Leerzeichen getrenntes sending full wird akzeptiert. Wenn Sie scope weglassen, verwendet Emailit alle Scopes, die der Client registriert hat.
Client registrieren
Sie können einen Client auf zwei Arten registrieren. Beide funktionieren mit allen Abläufen auf dieser Seite.
Dynamische Client-Registrierung
Senden Sie eine Registrierungsanfrage. Eine Authentifizierung ist nicht nötig, und jede IP-Adresse kann bis zu 20 Clients pro Stunde registrieren.
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"
}'| Feld | Erforderlich | Beschreibung |
|---|---|---|
client_name |
Ja | Wird auf der Zustimmungsseite angezeigt. Bis zu 200 Zeichen. |
redirect_uris |
Ja | 1 bis 10 Redirect-URIs. Siehe Regeln für Redirect-URIs. |
grant_types |
Nein | Muss authorization_code enthalten; darf refresh_token enthalten. Standardwert: beide. |
response_types |
Nein | Nur code. |
scope |
Nein | Durch Leerzeichen getrennte Scopes. Standardwert: sending full. |
token_endpoint_auth_method |
Nein | none (Standard) für öffentliche Clients wie Desktop-, Mobil- und Browser-Apps; client_secret_basic oder client_secret_post für serverseitige Apps. |
client_uri |
Nein | Die Startseite Ihrer App. |
logo_uri |
Nein | Logo, das auf der Zustimmungsseite angezeigt wird. |
Die Antwort mit 201 gibt die Metadaten zurück und ergänzt eine client_id. Vertrauliche Clients erhalten zusätzlich ein client_secret, das nur einmal angezeigt wird:
{
"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 ist immer 0: Secrets laufen nicht ab. Jeder Client, ob öffentlich oder vertraulich, muss PKCE verwenden.
Client-ID-Metadatendokument
Wenn Ihre App eine statische JSON-Datei hosten kann, können Sie die Registrierung überspringen. Veröffentlichen Sie ein Dokument unter einer HTTPS-URL und verwenden Sie diese URL als Ihre 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"
}client_idim Dokument muss exakt der URL des Dokuments entsprechen.token_endpoint_auth_methodmussnonesein. Es handelt sich um öffentliche Clients, die auf PKCE setzen.- HTTPS-Redirect-URIs müssen auf demselben Host liegen wie das Dokument.
- Emailit ruft das Dokument während der Autorisierung ab, mit einem Timeout von 5 Sekunden und ohne Weiterleitungen zu folgen, und speichert es 5 Minuten im Cache.
- Die Zustimmungsseite zeigt statt
client_nameden Host des Dokuments an, zum Beispielcrm.acme.com.
KI-Clients verwenden diese Methode: ChatGPT, Claude und Grok identifizieren sich mit ihren eigenen Client-ID-Metadatendokumenten und verbinden sich daher ohne vorherige Registrierung. Für Grok akzeptiert Emailit außerdem Weiterleitungen zu console.x.ai.
Regeln für Redirect-URIs
- URIs mit
https://sind erlaubt. http://ist nur für Loopback-Hosts erlaubt:127.0.0.1,localhostund[::1]. Bei Loopback-URIs darf sich der Port zum Zeitpunkt der Autorisierung unterscheiden, Host, Pfad und Query müssen aber übereinstimmen.localhostund127.0.0.1sind unterschiedliche Hosts.- Private Schemata wie
cursor://,vscode://odercom.acme.crm://sind für native Apps erlaubt. - URIs mit
file,ftp,data,javascript,blob,aboutundvbscriptsowie jede URI mit einem#fragmentwerden abgelehnt. - Abgesehen von Loopback-Ports muss die gesendete
redirect_uriexakt einer registrierten entsprechen.
Nutzer zu Emailit weiterleiten
Erzeugen Sie für jede Autorisierung einen PKCE-Verifier und eine Challenge sowie einen zufälligen state:
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.Leiten Sie dann den Browser des Nutzers zum Autorisierungsendpunkt weiter:
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| Parameter | Erforderlich | Beschreibung |
|---|---|---|
response_type |
Ja | code |
client_id |
Ja | Ihre Client-ID oder die URL Ihres Metadatendokuments. |
redirect_uri |
Ja | Eine Ihrer registrierten Redirect-URIs. |
code_challenge |
Ja | Base64url-kodierter SHA-256-Hash des Code-Verifiers. |
code_challenge_method |
Empfohlen | S256. plain wird nicht unterstützt. |
scope |
Empfohlen | sending oder full. Muss ein Scope sein, den der Client registriert hat. |
state |
Empfohlen | Ein Zufallswert, den Sie beim Callback prüfen. |
resource |
Nein | Die Ressource, die Sie aufrufen möchten (RFC 8707): https://api.emailit.com für die REST-API oder https://api.emailit.com/mcp für MCP. Die Tokens funktionieren in jedem Fall für beide. |
Öffnen Sie diese URL im Browser des Nutzers. Rufen Sie sie nicht von Ihrem Backend aus ab.
Was der Nutzer sieht
- Bei Emailit anmelden. Der Nutzer gibt seine E-Mail-Adresse und sein Passwort ein. Wenn er für die Zwei-Faktor-Authentifizierung eine Authenticator-App nutzt, gibt er anschließend deren Code oder einen Wiederherstellungscode ein.
- Workspaces wählen. Die Seite zeigt Ihr Logo, Ihren Client-Namen (oder den Host Ihres Metadatendokuments) und die angeforderten Scopes. Der Nutzer wählt All my workspaces, was auch Workspaces einschließt, die er später erstellt oder denen er später beitritt, oder Only these workspaces mit den angehakten Workspaces, und legt fest, in welchem Workspace Ihre App startet.
- Zugriff erlauben. Er wählt Allow access oder Deny.
Eine Freigabe kann mehrere Workspaces umfassen. Der Nutzer kann die Liste später in der Weboberfläche unter Account > Connected apps ändern, und Ihre App sieht die Änderung bei ihrer nächsten Anfrage.
Nach der Bestätigung leitet Emailit zu Ihrer Redirect-URI weiter:
https://crm.acme.com/oauth/emailit/callback?code=XvRKyG5Pkplrd3xkRNaEayKkpkebEMdF&state=Jq3k9V0n2xR7bLm1&iss=https%3A%2F%2Fapi.emailit.comPrüfen Sie, ob state mit dem gespeicherten Wert übereinstimmt und ob iss gleich https://api.emailit.com ist. Der Code ist 10 Minuten gültig und kann einmal verwendet werden. Wählt der Nutzer Deny, enthält die Weiterleitung stattdessen error=access_denied.
Code gegen Tokens eintauschen
Senden Sie eine POST-Anfrage an den Token-Endpunkt als application/x-www-form-urlencoded (JSON wird ebenfalls akzeptiert). Senden Sie dieselbe redirect_uri und den ursprünglichen Code-Verifier:
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"Bei client_secret_basic senden Sie Client-ID und Secret im Header Authorization: Basic. Bei client_secret_post senden Sie stattdessen client_id und client_secret als Formularfelder. Senden Sie das Secret nicht auf beiden Wegen.
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"Öffentliche Clients senden client_id und kein Secret. PKCE belegt, dass derselbe Client den Ablauf gestartet hat.
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im9hdXRoLWhzMjU2In0…",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "XvRKyG5Pkplrd3xkRNaEayKkpkebEMdFn0h1VIuqixAOXybMtJfK8w",
"scope": "full"
}Das Zugriffstoken ist 15 Minuten gültig (expires_in in Sekunden). Behandeln Sie es als undurchsichtigen String: Parsen Sie es nicht und verlassen Sie sich nicht auf seinen Inhalt.
API aufrufen
Verwenden Sie das Zugriffstoken als Bearer-Token für die REST-API und den MCP-Server, genau wie einen API-Schlüssel:
curl https://api.emailit.com/v2/domains \
-H "Authorization: Bearer $ACCESS_TOKEN"REST-Anfragen laufen im Standard-Workspace der Freigabe: dem Workspace, in dem der Nutzer starten wollte oder zu dem er später gewechselt hat. MCP-Tools können mit ihrem Argument workspace auch einen anderen erlaubten Workspace ansprechen. Jede Anfrage verwendet den gewährten Scope und die Rolle des Nutzers im Workspace; eine Anfrage außerhalb des Scopes oder eine Admin-Route, die ein Mitglied mit der Rolle Member aufruft, gibt daher 403 zurück. Verlässt der Nutzer einen Workspace, verliert auch die Freigabe den Zugriff darauf.
Tokens erneuern
Bevor das Zugriffstoken abläuft oder wenn eine Anfrage 401 zurückgibt, holen Sie ein neues:
curl https://api.emailit.com/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=refresh_token \
-d refresh_token="$REFRESH_TOKEN"- Rotation. Jede Erneuerung gibt ein neues
refresh_tokenzurück, das 60 Tage gültig ist. Speichern Sie es und verwerfen Sie das alte. Solange Sie innerhalb von 60 Tagen erneuern, läuft die Verbindung nicht ab. - Erkennung von Wiederverwendung. Wird ein altes Refresh-Token mehr als eine Minute nach seiner Ersetzung erneut verwendet, gibt Emailit
invalid_grant(Refresh token reuse detected) zurück und widerruft die gesamte Freigabe. Der Nutzer muss dann erneut autorisieren. Serialisieren Sie Erneuerungen, damit nie zwei Worker dasselbe Refresh-Token verwenden. - Engerer Scope. Sie können
scopeübergeben, um ein Zugriffstoken mit weniger Scopes als die Freigabe zu erhalten. Mehr können Sie nicht anfordern.
Freigabe widerrufen
Wenn ein Nutzer Emailit von Ihrer App trennt, widerrufen Sie die Freigabe mit ihrem Refresh-Token:
curl https://api.emailit.com/oauth/revoke \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d token="$REFRESH_TOKEN"Der Endpunkt authentifiziert Ihren Client auf dieselbe Weise wie der Token-Endpunkt und gibt immer 200 mit leerem Body zurück. Das Widerrufen des Refresh-Tokens widerruft die gesamte Freigabe: Ihre Zugriffstokens funktionieren ab der nächsten Anfrage nicht mehr. Zugriffstokens lassen sich nicht einzeln widerrufen; token_type_hint=access_token gibt invalid_request zurück.
Nutzer können den Zugriff Ihrer App auch selbst widerrufen, in der Weboberfläche unter Connected apps oder mit der unten beschriebenen Freigaben-API.
Freigaben verwalten
Die Freigaben-API listet und ändert Freigaben aus Sicht des Nutzers. Sie akzeptiert die Sitzung des Nutzers in der Weboberfläche oder einen API-Schlüssel mit Vollzugriff:
| Aufrufer | GET /oauth/grants |
POST /oauth/grants/:id/revoke |
PUT /oauth/grants/:id/workspaces |
|---|---|---|---|
| Der Nutzer, der die App verbunden hat | Seine Freigaben über alle Workspaces | Widerruft die gesamte Freigabe | Ändert die Workspaces der Freigabe |
| API-Schlüssel mit Vollzugriff | Freigaben, die den Workspace des Schlüssels enthalten | Entfernt den Workspace des Schlüssels aus der Freigabe; die Freigabe wird widerrufen, wenn kein Workspace übrig bleibt | Nicht erlaubt (403) |
Jede Freigabe in der Liste hat diese Form:
{
"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
}Um die Workspaces einer Freigabe zu ändern, senden Sie access (all oder selected), bei selected zusätzlich workspace_ids und optional default_workspace_id, das einer der erlaubten Workspaces sein muss:
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" }'Der Nutzer muss Mitglied jedes ausgewählten Workspaces sein. Eine widerrufene Freigabe kann nicht bearbeitet werden (409); die App muss sich erneut verbinden.
Fehler
OAuth-Endpunkte geben Fehler im OAuth-Format zurück, nicht im Fehlerformat der REST-API:
{
"error": "invalid_grant",
"error_description": "Authorization code has expired"
}| Fehler | Status | Wann |
|---|---|---|
invalid_request |
400 | Ein Parameter fehlt oder ist ungültig, der Client ist bei der Autorisierung unbekannt, die redirect_uri ist nicht registriert, code_challenge_method ist nicht S256 oder ein Client-Secret wurde sowohl im Header als auch im Body gesendet. |
invalid_client |
401 | Der Token- oder Widerrufsendpunkt kann den Client nicht authentifizieren: unbekannter Client, fehlendes oder falsches Secret. |
invalid_grant |
400 | Der Code ist ungültig, bereits verwendet oder abgelaufen; die redirect_uri oder der PKCE-Verifier stimmt nicht überein; oder das Refresh-Token ist ungültig, abgelaufen, widerrufen oder wiederverwendet. |
invalid_scope |
400 | Der Scope wird nicht unterstützt, wurde für den Client nicht registriert oder geht bei der Erneuerung über die Freigabe hinaus. |
unsupported_response_type |
400 | response_type ist nicht code. |
unsupported_grant_type |
400 | grant_type ist nicht authorization_code oder refresh_token. |
access_denied |
Weiterleitung | Der Nutzer hat Deny gewählt. |
too_many_requests |
429 | Mehr als 20 Registrierungen pro Stunde von einer IP-Adresse. |
server_error |
500 | Bei Emailit ist etwas schiefgelaufen. Versuchen Sie es erneut. |
Solange client_id und redirect_uri nicht validiert sind, werden Autorisierungsfehler als JSON im Browser angezeigt und nie weitergeleitet. Danach werden Fehler nur bei Loopback- und privaten Redirect-URIs sowie bei Clients mit Metadatendokument an Ihren Callback weitergeleitet (mit error, error_description, state und iss); andere Clients erhalten eine JSON-Fehlerseite. Ein Deny des Nutzers wird immer weitergeleitet.
Sicherheits-Checkliste
- Bewahren Sie
client_secretund Refresh-Tokens auf Ihrem Server auf, verschlüsselt gespeichert. Liefern Sie nie ein Client-Secret in einer Mobil-, Desktop- oder Browser-App aus; verwenden Sie stattdessen einen öffentlichen Client mit PKCE. - Erzeugen Sie für jede Autorisierung einen neuen
stateund Code-Verifier und prüfen Siestateundissbeim Callback. - Registrieren Sie exakte Redirect-URIs. Verwenden Sie keine offenen Weiterleitungen (Open Redirects) als Callbacks.
- Speichern Sie jede Freigabe zusammen mit dem Workspace, zu dem sie gehört, und reagieren Sie auf
invalid_grant, indem Sie den Nutzer auffordern, sich erneut zu verbinden. - Fordern Sie
sendingan, wenn Sie nur E-Mails senden.