Guia
Criar um app OAuth
Permita que os usuários conectem a sua aplicação ao workspace deles no Emailit com OAuth 2.1 e PKCE, incluindo registro do cliente, atualização de tokens, revogação e erros.
O Emailit mantém um servidor de autorização OAuth 2.1 em https://api.emailit.com. Use-o quando você criar uma integração, como um CRM, uma ferramenta no-code ou um cliente de IA, que age em nome de usuários do Emailit. Os seus usuários fazem login e aprovam o acesso no navegador, e você recebe tokens para os workspaces que eles escolherem sem nunca lidar com as chaves de API deles. É o mesmo fluxo que o servidor MCP usa.
Como funciona
-
Registre um cliente com o registro dinâmico de clientes ou hospede um documento de metadados do client ID.
-
Envie o usuário para a URL de autorização com um code challenge PKCE.
-
O usuário faz login no Emailit, escolhe os workspaces que a sua aplicação pode usar e aprova o escopo solicitado.
-
O Emailit redireciona de volta para a sua URI de redirecionamento com um código de autorização de uso único.
-
Troque o código por um token de acesso de 15 minutos e um token de atualização.
-
Chame a API com o token de acesso e atualize-o quando ele expirar.
Endpoints
| Método | Caminho | Finalidade |
|---|---|---|
GET |
/.well-known/oauth-authorization-server |
Metadados do servidor de autorização (RFC 8414) |
GET |
/.well-known/oauth-protected-resource |
Metadados do recurso protegido para a API REST |
GET |
/.well-known/oauth-protected-resource/mcp |
Metadados do recurso protegido para o servidor MCP |
POST |
/oauth/register |
Registro dinâmico de clientes (RFC 7591) |
GET |
/oauth/authorize |
Login e consentimento no navegador |
POST |
/oauth/token |
Trocar um código ou um token de atualização |
POST |
/oauth/revoke |
Revogar uma autorização com o token de atualização dela (RFC 7009) |
GET |
/oauth/grants |
Listar autorizações (as do usuário conectado ou as de um workspace, com uma chave de API) |
POST |
/oauth/grants/:id/revoke |
Revogar uma autorização |
PUT |
/oauth/grants/:id/workspaces |
Alterar quais workspaces uma autorização pode usar (o usuário que conectou o app) |
Todos os caminhos ficam em https://api.emailit.com.
Descobrir o servidor
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"]
}Os clientes MCP começam pelos metadados do recurso protegido. Uma requisição não autenticada para https://api.emailit.com/mcp retorna 401 com WWW-Authenticate: Bearer resource_metadata="https://api.emailit.com/.well-known/oauth-protected-resource/mcp", e esse documento aponta para o servidor de autorização:
{
"resource": "https://api.emailit.com/mcp",
"authorization_servers": ["https://api.emailit.com"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["sending", "full"]
}Escopos
| Escopo | Concede |
|---|---|
sending |
Enviar e encaminhar e-mails, além de reagendar, cancelar e tentar de novo os envios. O mesmo acesso de uma chave de API Sending Only. |
full |
Todos os endpoints da API REST e todas as ferramentas MCP. O mesmo acesso de uma chave de API Full Access. |
full já inclui tudo o que sending permite. Solicite sending se a sua aplicação só envia e full nos outros casos; sending full, separado por espaço, também é aceito. Se você omitir scope, o Emailit usa todos os escopos que o cliente registrou.
Registrar o seu cliente
Você pode registrar um cliente de duas formas. As duas funcionam com todos os fluxos desta página.
Registro dinâmico de clientes
Envie uma requisição de registro. Não é preciso autenticação, e cada endereço IP pode registrar até 20 clientes por hora.
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 | Obrigatório | Descrição |
|---|---|---|
client_name |
Sim | Exibido na página de consentimento. Até 200 caracteres. |
redirect_uris |
Sim | De 1 a 10 URIs de redirecionamento. Consulte Regras das URIs de redirecionamento. |
grant_types |
Não | Deve incluir authorization_code; pode incluir refresh_token. O padrão são os dois. |
response_types |
Não | Apenas code. |
scope |
Não | Escopos separados por espaço. O padrão é sending full. |
token_endpoint_auth_method |
Não | none (padrão) para clientes públicos, como apps de desktop, mobile e de navegador; client_secret_basic ou client_secret_post para aplicações do lado do servidor. |
client_uri |
Não | A página inicial da sua aplicação. |
logo_uri |
Não | Logo exibido na página de consentimento. |
A resposta 201 repete os metadados e adiciona um client_id. Os clientes confidenciais também recebem um client_secret, exibido uma única vez:
{
"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: os segredos não expiram. Todo cliente, público ou confidencial, deve usar PKCE.
Documento de metadados do client ID
Se a sua aplicação pode hospedar um arquivo JSON estático, você pode pular o registro. Publique um documento em uma URL HTTPS e use essa URL como o seu 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"
}- O
client_iddo documento deve ser exatamente igual à URL do documento. token_endpoint_auth_methoddeve sernone. São clientes públicos que dependem de PKCE.- As URIs de redirecionamento HTTPS devem estar no mesmo host do documento.
- O Emailit busca o documento durante a autorização, com um timeout de 5 segundos e sem seguir redirecionamentos, e o mantém em cache por 5 minutos.
- A página de consentimento mostra o host do documento, por exemplo,
crm.acme.com, em vez declient_name.
Os clientes de IA usam este método: o ChatGPT, o Claude e o Grok se identificam com os próprios documentos de metadados do client ID, então se conectam sem se registrar antes. Para o Grok, o Emailit também aceita redirecionamentos para console.x.ai.
Regras das URIs de redirecionamento
- URIs
https://são permitidas. http://só é permitido para hosts de loopback:127.0.0.1,localhoste[::1]. Em URIs de loopback, a porta pode ser diferente no momento da autorização, mas o host, o caminho e a query devem corresponder.localhoste127.0.0.1são hosts diferentes.- Esquemas de uso privado, como
cursor://,vscode://oucom.acme.crm://, são permitidos para apps nativos. - URIs
file,ftp,data,javascript,blob,aboutevbscript, e qualquer URI com um#fragment, são rejeitadas. - Fora as portas de loopback, a
redirect_urique você envia deve ser exatamente igual a uma URI registrada.
Enviar o usuário para o Emailit
Crie um verifier e um challenge PKCE, além de um state aleatório, para cada autorização:
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.Depois, redirecione o navegador do usuário para o endpoint de autorização:
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| Parâmetro | Obrigatório | Descrição |
|---|---|---|
response_type |
Sim | code |
client_id |
Sim | O seu client ID ou a URL do seu documento de metadados. |
redirect_uri |
Sim | Uma das suas URIs de redirecionamento registradas. |
code_challenge |
Sim | SHA-256 do code verifier, codificado em Base64url. |
code_challenge_method |
Recomendado | S256. plain não é aceito. |
scope |
Recomendado | sending ou full. Deve ser um escopo que o cliente registrou. |
state |
Recomendado | Um valor aleatório que você confere no callback. |
resource |
Não | O recurso que você quer chamar (RFC 8707): https://api.emailit.com para a API REST ou https://api.emailit.com/mcp para o MCP. Os tokens funcionam nos dois de qualquer forma. |
Abra essa URL no navegador do usuário. Não a busque a partir do seu backend.
O que o usuário vê
- Login no Emailit. O usuário digita o e-mail e a senha. Se ele usa um app autenticador para a autenticação de dois fatores, digita em seguida o código dele ou um código de recuperação.
- Escolha dos workspaces. A página mostra o seu logo, o nome do seu cliente (ou o host do seu documento de metadados) e os escopos que você solicitou. O usuário escolhe All my workspaces, que inclui os workspaces que ele criar ou em que entrar depois, ou Only these workspaces com os que ele marcar, e escolhe por onde a sua aplicação começa.
- Permissão de acesso. O usuário seleciona Allow access ou Deny.
Uma autorização pode cobrir vários workspaces. O usuário pode alterar a lista depois em Account > Connected apps no painel, e a sua aplicação vê a mudança na próxima requisição.
Na aprovação, o Emailit redireciona para a sua URI de redirecionamento:
https://crm.acme.com/oauth/emailit/callback?code=XvRKyG5Pkplrd3xkRNaEayKkpkebEMdF&state=Jq3k9V0n2xR7bLm1&iss=https%3A%2F%2Fapi.emailit.comConfira se state corresponde ao valor que você guardou e se iss é https://api.emailit.com. O código vale por 10 minutos e pode ser usado uma vez. Se o usuário selecionar Deny, o redirecionamento leva error=access_denied.
Trocar o código por tokens
Faça um POST para o endpoint de token como application/x-www-form-urlencoded (JSON também é aceito). Envie a mesma redirect_uri e o code verifier original:
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"Com client_secret_basic, envie o client ID e o segredo no cabeçalho Authorization: Basic. Com client_secret_post, envie client_id e client_secret como campos do formulário. Não envie o segredo das duas formas.
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"Os clientes públicos enviam client_id e nenhum segredo. O PKCE prova que o mesmo cliente iniciou o fluxo.
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Im9hdXRoLWhzMjU2In0…",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "XvRKyG5Pkplrd3xkRNaEayKkpkebEMdFn0h1VIuqixAOXybMtJfK8w",
"scope": "full"
}O token de acesso dura 15 minutos (expires_in é em segundos). Trate-o como uma string opaca: não o interprete nem dependa do conteúdo dele.
Chamar a API
Use o token de acesso como bearer token na API REST e no servidor MCP, exatamente como uma chave de API:
curl https://api.emailit.com/v2/domains \
-H "Authorization: Bearer $ACCESS_TOKEN"As requisições REST são executadas no workspace padrão da autorização: aquele por onde o usuário escolheu começar ou para o qual ele mudou depois. As ferramentas MCP também podem usar outro workspace permitido com o argumento workspace. Toda requisição usa o escopo concedido e o papel do usuário no workspace, então uma requisição fora do escopo, ou uma rota exclusiva de Admin chamada por um membro com o papel Member, retorna 403. Se o usuário sair de um workspace, a autorização também perde o acesso a ele.
Atualizar tokens
Antes de o token de acesso expirar, ou quando uma requisição retornar 401, obtenha um novo:
curl https://api.emailit.com/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=refresh_token \
-d refresh_token="$REFRESH_TOKEN"- Rotação. Cada atualização retorna um novo
refresh_token, válido por 60 dias. Guarde-o e descarte o antigo. Enquanto você atualizar dentro de 60 dias, a conexão não expira. - Detecção de reutilização. Se um token de atualização antigo for usado de novo mais de um minuto depois de ter sido substituído, o Emailit retorna
invalid_grant(Refresh token reuse detected) e revoga a autorização inteira. O usuário então precisa autorizar de novo. Serialize as atualizações para que dois workers nunca usem o mesmo token de atualização. - Escopo mais restrito. Você pode passar
scopepara obter um token de acesso com menos escopos do que a autorização. Não é possível pedir mais.
Revogar uma autorização
Quando um usuário desconectar o Emailit da sua aplicação, revogue a autorização com o token de atualização dela:
curl https://api.emailit.com/oauth/revoke \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d token="$REFRESH_TOKEN"O endpoint autentica o seu cliente da mesma forma que o endpoint de token e sempre retorna 200 com o corpo vazio. Revogar o token de atualização revoga a autorização inteira: os tokens de acesso dela deixam de funcionar na próxima requisição. Os tokens de acesso não podem ser revogados sozinhos; token_type_hint=access_token retorna invalid_request.
Os usuários também podem revogar o acesso da sua aplicação do lado deles, em Apps conectados no painel, ou com a API de autorizações abaixo.
Gerenciar autorizações
A API de autorizações lista e altera autorizações do lado do usuário. Ela aceita a sessão do usuário no painel ou uma chave de API Full Access:
| Quem chama | GET /oauth/grants |
POST /oauth/grants/:id/revoke |
PUT /oauth/grants/:id/workspaces |
|---|---|---|---|
| O usuário que conectou o app | As autorizações dele em todos os workspaces | Revoga a autorização inteira | Altera os workspaces da autorização |
| Chave de API Full Access | As autorizações que incluem o workspace da chave | Remove o workspace da chave da autorização; a autorização é revogada quando não sobra nenhum workspace | Não permitido (403) |
Cada autorização da lista tem este formato:
{
"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
}Para alterar os workspaces de uma autorização, envie access (all ou selected), workspace_ids para selected e um default_workspace_id opcional, que deve ser um dos workspaces permitidos:
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" }'O usuário deve ser membro de todos os workspaces que selecionar. Uma autorização revogada não pode ser editada (409); o app precisa se conectar de novo.
Erros
Os endpoints OAuth retornam erros no formato OAuth, não no formato de erro da API REST:
{
"error": "invalid_grant",
"error_description": "Authorization code has expired"
}| Erro | Status | Quando |
|---|---|---|
invalid_request |
400 | Um parâmetro está ausente ou é inválido, o cliente é desconhecido na autorização, a redirect_uri não está registrada, code_challenge_method não é S256 ou um segredo do cliente foi enviado tanto no cabeçalho quanto no corpo. |
invalid_client |
401 | O endpoint de token ou de revogação não consegue autenticar o cliente: cliente desconhecido, segredo ausente ou errado. |
invalid_grant |
400 | O código é inválido, já foi usado ou expirou; a redirect_uri ou o verifier PKCE não corresponde; ou o token de atualização é inválido, expirou, foi revogado ou reutilizado. |
invalid_scope |
400 | O escopo não é aceito, não foi registrado para o cliente ou excede a autorização na atualização. |
unsupported_response_type |
400 | response_type não é code. |
unsupported_grant_type |
400 | grant_type não é authorization_code nem refresh_token. |
access_denied |
Redirecionamento | O usuário selecionou Deny. |
too_many_requests |
429 | Mais de 20 registros por hora a partir de um endereço IP. |
server_error |
500 | Algo deu errado do lado do Emailit. Tente de novo. |
Até que client_id e redirect_uri sejam validados, os erros de autorização são exibidos como JSON no navegador e nunca redirecionados. Depois disso, os erros são redirecionados para o seu callback (com error, error_description, state e iss) apenas para URIs de redirecionamento de loopback e de uso privado e para clientes com documento de metadados; os outros clientes recebem uma página de erro em JSON. O Deny de um usuário é sempre redirecionado.
Checklist de segurança
- Mantenha o
client_secrete os tokens de atualização no seu servidor, criptografados em repouso. Nunca inclua um segredo de cliente em um app mobile, de desktop ou de navegador; use um cliente público com PKCE. - Gere um novo
statee um novo code verifier para cada autorização e confirastateeissno callback. - Registre URIs de redirecionamento exatas. Não use redirecionadores abertos como callbacks.
- Guarde cada autorização junto com o workspace a que ela pertence e trate
invalid_grantpedindo ao usuário que se conecte de novo. - Solicite
sendingse você só envia e-mails.