CDW Mail / Guia de integração

CDW Mail — API de integração

Para quem vai ligar um ERP, um portal do cliente ou qualquer sistema externo à plataforma.

Base: https://webmail.<seu-dominio>/api/v1 Contrato completo: api/openapi.yaml (OpenAPI 3.1 — dá para gerar cliente em qualquer linguagem a partir dele)


1. A chave

Toda chamada leva a chave no cabeçalho:

Authorization: Bearer cdw_myipgj85zhha_22QcrAy9mI1...

Uma chave pertence a uma revenda e vale para os domínios dela. Ela age com o poder administrativo dessa revenda — o mesmo escopo que o painel já aplica, sem régua de permissão paralela.

Como obter

Só o super admin cria, no painel. Uma revenda não cria as próprias: quem decide que uma integração existe é quem responde pela plataforma.

POST /chaves-api          { "revenda_uuid": "...", "nome": "ERP financeiro" }
GET  /chaves-api          lista (sem o segredo)
DELETE /chaves-api/{uuid} revoga

A chave aparece UMA VEZ

A resposta da criação traz a chave inteira, e é a única vez. O banco guarda só o hash. Quem perder, revoga e cria outra — é esse contrato que faz um vazamento do banco não virar acesso.

O que a chave NÃO pode

Entrar na caixa do cliente e ler e-mail 403
Criar ou listar outras chaves 403
Alcançar domínio de outra revenda 404

A recusa da personificação é deliberada e vale explicar: a chave tem o poder administrativo da revenda — criar caixa, mudar cota, suspender —, que é o que uma integração precisa. Ler o e-mail do cliente é outra categoria. A sessão humana que faz isso tem 2FA atrás, dura 30 minutos e desenha uma faixa vermelha na tela; uma chave estática num servidor de ERP não tem nada disso.

Domínio de outra revenda responde 404, e não 403, de propósito: um 403 confirmaria que aquele domínio existe, e bastaria varrer identificadores para mapear a base alheia.

Guardando a chave


2. Relatório de consumo

A pergunta de cobrança — quantas caixas e quanto espaço cada domínio usa:

GET /dominios?por_pagina=200
{
  "itens": [
    {
      "uuid": "01a0bfe8-...",
      "nome": "7hosting.com.br",
      "status": "ativo",
      "cota_bytes": 0,
      "cota_padrao_caixa_bytes": 1073741824,
      "uso": { "caixas": 8, "bytes_usados": 3083895544 }
    }
  ],
  "total": 10, "pagina": 1, "por_pagina": 200, "total_paginas": 1
}
campo o que é
uso.caixas contas ativas do domínio
uso.bytes_usados soma do que as caixas ocupam, em bytes
cota_bytes teto do domínio. 0 = ilimitado

bytes_usados é lido do servidor de e-mail, não estimado. Um laço percorre as caixas em rodízio e atualiza; o número pode estar alguns minutos atrasado, e nunca muito. Para cobrança mensal isso é indiferente; para mostrar "quanto sobrou" em tempo real, não é a fonte certa.

0 = ilimitado é a regra em toda cota desta API — domínio e caixa. Tratar zero como "sem espaço" é o engano previsível aqui.


3. Caixas

Listar

GET /caixas?dominio={uuid}&pagina=1&por_pagina=50
GET /caixas?q=financeiro

Sempre paginado. O total vem no corpo (total, total_paginas) — não presuma que uma página é tudo: com 101 caixas, ler só a primeira esconde uma e nada avisa.

Criar

POST /caixas
{ "email": "financeiro@cliente.com.br",
  "senha": "no mínimo 12 caracteres",
  "nome_exibicao": "Financeiro",
  "cota_bytes": 2147483648 }

cota_bytes ausente faz a caixa herdar a cota padrão do domínio.

409 ao criar pode ser um maildir órfão. Se o endereço já existiu, o e-mail da pessoa anterior continua em disco e a plataforma recusa criar por cima — é o que impede a nova pessoa herdar a correspondência de quem saiu. A mensagem diz o caminho; quem resolve é um humano, no painel.

Trocar a senha

PUT /caixas/{uuid}/senha
{ "nova_senha": "..." }

Repare: PUT, não POST. Responde 204.

Trocar a senha derruba as sessões abertas daquela caixa, no webmail e nos clientes de e-mail. É o efeito desejado quando alguém sai da empresa, e é um transtorno quando foi só para "atualizar o cadastro" — vale avisar quem clica.

Cota, nome, suspender

PATCH /caixas/{uuid}
{ "cota_bytes": 5368709120 }    aumenta a cota
{ "ativo": false }               suspende: não recebe e não autentica
{ "cota_bytes": null }           volta a HERDAR a cota do domínio

cota_bytes tem três estados, e eles são diferentes entre si:

valor efeito
ausente não mexe na cota
null volta a herdar a do domínio
número cota própria (0 = ilimitada)

Mandar 0 quando se queria null dá à caixa espaço ilimitado — o contrário do que normalmente se pretende.

Apagar

DELETE /caixas/{uuid}?confirmar=financeiro@cliente.com.br

O confirmar tem que ser o endereço exato. As mensagens não são apagadas junto: ficam em disco, e o endereço fica travado até um humano decidir se elas voltam para a mesma pessoa ou vão para o lixo.


4. Domínios

GET   /dominios
PATCH /dominios/{uuid}   { "status": "suspenso" }
PATCH /dominios/{uuid}   { "cota_padrao_caixa_bytes": 2147483648 }

Suspender um domínio derruba a entrega de todas as caixas dele. Mensagem que chegar nesse período é recusada na conversa SMTP — quem enviou recebe o aviso na hora, e não há fila esperando a reativação.


5. Erros

Todos seguem a RFC 9457:

{ "type": "https://cdwtech.com.br/erros/dados-invalidos",
  "title": "Dados inválidos", "status": 422,
  "instance": "/api/v1/caixas",
  "erros": { "senha": ["no mínimo 12 caracteres"] } }
status o que fazer
401 chave inválida ou revogada. Não tente de novo — conserte a chave.
403 a chave não tem esse poder. Ver "o que a chave NÃO pode".
404 não existe, ou é de outra revenda. Os dois são indistinguíveis de propósito.
409 conflito de estado — endereço em uso, limite da revenda estourado, maildir órfão. O detail explica.
422 corpo inválido. erros diz qual campo. Repetir igual dá igual.
429 excesso de tentativas. Respeite o Retry-After.

Campo desconhecido no corpo é recusado, não ignorado. É deliberado: um campo com nome errado ignorado em silêncio faz a integração achar que funcionou. Já aconteceu três vezes nesta plataforma, sempre com a mesma cara — "o botão não faz nada".


6. Antes de pôr em produção

Esta página é gerada de docs/api-integracao.md na construção da imagem — ela não pode divergir do repositório. A referência completa das rotas está em /api/docs.