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
- Em variável de ambiente ou cofre de segredos, nunca no código.
- A marca
cdw_existe para os varredores do GitHub e do GitLab acharem uma chave que vaze. Se isso acontecer: revogue primeiro, investigue depois. - Revenda suspensa deixa de autenticar na hora — como deixa de operar na tela.
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
- Pagine. Toda lista é paginada, e
totalestá no corpo. - Trate 409 como informação, não como falha. Ele carrega a regra de negócio que você precisa mostrar a quem clicou.
- Não guarde a chave no código. Variável de ambiente ou cofre.
- Uma chave por integração. Duas integrações na mesma chave não se distinguem na auditoria, e revogar uma derruba a outra.
- Tudo fica auditado. Toda escrita pela chave aparece em
/painel/auditoriacomochave:<nome>@<revenda>— deixando óbvio, meses depois, que foi integração e não uma pessoa.