email-sender-domains-and-notification-preferences

# Email sender domains and notification preferences

Este documento e o brief de produto/implementacao para a proxima fase de
Communications Service e para o front. A decisao arquitetural esta em
`docs/adr/ADR-002-email-domains-and-notification-preferences.md`; o catalogo
configuravel e o i18n de dominio estao em
`docs/adr/ADR-003-configurable-notification-catalog.md`.

## Objetivo

Permitir que cada tenant envie e-mails customer-visible usando dominio proprio
verificado, sem expor Resend ou credenciais ao usuario final, e transformar
preferencias de notificacao em controles granulares por topico e canal.

## Fronteira

- O browser nao chama o Communications Service diretamente.
- O front deve usar uma API do backend/plataforma com permissao de admin da
  instancia.
- O backend/plataforma chama o Communications Service com token interno.
- O Communications Service chama Resend/Chatwoot/outros providers por adapter.
- Providers e chaves nunca aparecem no browser.

## Catalogo configuravel e i18n

O front deve renderizar a tela de notificacoes a partir de um catalogo servido
pelo Communications Service. O front nao deve hardcodar a lista fechada de
topicos, canais ou categorias.

O catalogo e faseado:

- v1: dado estruturado versionado neste repo e servido por endpoint. Topico novo
  ainda exige deploy do Communications, mas nao exige deploy de front;
- v2: dado canonico no Control Plane, hoje possivelmente PocketBase via
  colecao coordenada fora deste repo. O Communications acessa por port/adapter;
- futuro: admin UI com auditoria para editar topicos, traducoes, defaults e
  lifecycle.

O Communications resolve i18n de dominio no servidor. A resposta deve indicar
`requestedLocale`, `resolvedLocale`, `availableLocales` e `fallbackChain`. O
front nao implementa fallback de labels/descricoes de dominio.

Separacao de contratos:

- catalogo: topicos, canais, categorias, textos e defaults;
- capabilities: estado por instancia, como dominio verificado, canal disponivel
  e motivo de bloqueio;
- preferences: escolhas por usuario/workspace.

O BFF pode compor esses dados para uma tela unica, mas a origem deve continuar
separada para preservar cache e responsabilidades.

## UX - Dominios de e-mail

Tela recomendada:

- `Configuracoes > Comunicacoes > Dominios de e-mail`

Estados principais:

- sem dominio configurado;
- dominio criado, aguardando DNS;
- verificacao em andamento;
- dominio verificado;
- dominio com falha;
- dominio desativado.

Fluxo de adicionar dominio:

1. usuario informa o dominio/subdominio de envio;
2. UI recomenda subdominio, como `send.seudominio.com` ou
   `notifications.seudominio.com`, em vez do dominio raiz;
3. backend cria o dominio no Communications Service;
4. Communications Service cria o dominio no provider e retorna registros DNS;
5. UI mostra registros em tabela com botoes de copiar;
6. usuario adiciona os registros no DNS;
7. usuario clica em "Verificar agora";
8. UI acompanha `pending_dns`, `verifying`, `verified` ou `failed`;
9. depois de `verified`, usuario cria identidades de remetente.

Tabela de registros DNS:

- tipo;
- host/nome;
- valor;
- prioridade, quando existir;
- TTL, quando existir;
- status;
- botao copiar host;
- botao copiar valor;
- dica de DNS provider quando conhecida.

Campos de identidade de remetente:

- nome exibido;
- e-mail de remetente;
- e-mail de resposta;
- dominio associado;
- padrao da instancia;
- ativo/inativo.

Regras de UX:

- envio customer-visible fica bloqueado ate existir dominio `verified` e uma
  identidade ativa;
- e-mails da propria plataforma Ailian continuam usando dominio Ailian;
- nao mostrar "Resend" como conceito primario para o cliente;
- detalhes de provider podem ficar em modo suporte/admin;
- status de falha deve explicar proximo passo sem culpar o usuario por termos
  tecnicos;
- toda tela deve deixar claro que registros DNS podem levar tempo para propagar.

Copys sugeridos:

- "Configure um dominio para enviar e-mails aos seus clientes."
- "E-mails operacionais da Ailian continuam usando dominio Ailian."
- "Para proteger a reputacao do seu dominio principal, recomendamos usar um subdominio."
- "Nao foi possivel enviar: configure um dominio verificado."
- "A verificacao pode levar alguns minutos e, em alguns provedores DNS, ate 72 horas."

## UX - Preferencias granulares

Tela recomendada:

- `Configuracoes > Notificacoes`

Abas recomendadas:

- `Minhas notificacoes`;
- `Padroes do workspace`;
- `Catalogo de eventos`, somente admin/suporte se necessario.

Formato recomendado:

- linhas por topico/evento;
- colunas por canal: in-app, toast/realtime, push, e-mail, WhatsApp;
- celula com controle explicito: `toggle`, `locked`, `optin` ou `frequency`;
- celula com disponibilidade explicita: `allowed`, `topic_not_allowed`,
  `instance_unavailable` ou `mandatory`;
- estado herdado vs override explicito;
- cadeado para eventos obrigatorios;
- indicacao de canal indisponivel com motivo vindo de capabilities;
- filtros por produto, prioridade e categoria.

Topicos iniciais sugeridos:

- `crm.email.reply_received`;
- `crm.email.delivery_failed`;
- `crm.sla.due_soon`;
- `crm.sla.overdue`;
- `automation.run_failed`;
- `billing.invoice_issued`;
- `connectai.lead_handoff`.

Copys sugeridos:

- "Escolha como voce quer ser avisado para cada tipo de evento."
- "Obrigatorio: esta notificacao nao pode ser desativada."
- "Herdado do workspace."
- "Personalizado por voce."
- "WhatsApp exige opt-in explicito."

## Contratos internos propostos

Os endpoints abaixo sao proposta de implementacao futura. Eles nao estao
implementados nesta branch.

```text
GET  /internal/email/sending-domains?instanceId=<id>
POST /internal/email/sending-domains
GET  /internal/email/sending-domains/:id
POST /internal/email/sending-domains/:id/verify
POST /internal/email/sending-domains/:id/disable

GET  /internal/email/sender-identities?instanceId=<id>
POST /internal/email/sender-identities
PATCH /internal/email/sender-identities/:id

GET  /internal/notification-catalog?locale=<locale>
GET  /internal/notification-capabilities?instanceId=<id>&locale=<locale>
GET  /internal/notification-preferences?instanceId=<id>&userId=<id>
PUT  /internal/notification-preferences
```

Exemplo de dominio retornado:

```json
{
  "id": "dom_123",
  "instanceId": "inst_123",
  "domain": "send.example.com",
  "status": "pending_dns",
  "capabilities": {
    "sending": true,
    "receiving": false
  },
  "records": [
    {
      "kind": "SPF",
      "type": "TXT",
      "host": "send",
      "value": "\"v=spf1 include:amazonses.com ~all\"",
      "status": "pending"
    }
  ]
}
```

Exemplo de identidade:

```json
{
  "id": "sender_123",
  "instanceId": "inst_123",
  "domainId": "dom_123",
  "fromName": "Equipe Example",
  "fromEmail": "[email protected]",
  "replyTo": "[email protected]",
  "isDefault": true,
  "status": "active"
}
```

Exemplo de topico:

```json
{
  "schemaVersion": "notification-catalog.v1",
  "key": "crm.sla.overdue",
  "lifecycleStatus": "active",
  "categoryKey": "crm",
  "display": {
    "label": "SLA vencido",
    "description": "Aviso quando uma oportunidade ou lead passa do prazo de resposta."
  },
  "priority": "high",
  "mandatory": false,
  "allowedChannels": ["in_app", "realtime", "push", "email"],
  "defaultChannels": ["in_app", "push", "email"],
  "channelRules": [
    {
      "channelKey": "email",
      "control": "toggle",
      "availability": "allowed",
      "defaultValue": true
    }
  ]
}
```

Regras de `topicKey`:

- `topicKey` e contrato entre produtores e Communications, nao apenas UI;
- depois de ativo, nao renomeie a chave; altere label/descricao ou crie uma
  chave nova;
- topico desconhecido, `draft` ou `disabled` deve ser rejeitado na ingestao
  canonica antes de criar deliveries;
- topico `deprecated` pode ser aceito durante janela de transicao e deve emitir
  metrica/aviso.

Exemplo de preferencia:

```json
{
  "instanceId": "inst_123",
  "userId": "usr_123",
  "topicKey": "crm.sla.overdue",
  "channels": {
    "in_app": true,
    "realtime": true,
    "push": true,
    "email": false,
    "whatsapp": false
  }
}
```

## Dispatch

Antes de enviar e-mail customer-visible, o worker deve verificar:

- instancia do envio;
- dominio de envio verificado;
- identidade de remetente ativa;
- bloqueios de billing/entitlement quando aplicavel;
- supressoes tecnicas/provider-side;
- preferencias do destinatario, quando houver usuario interno;
- politica de opt-in do canal.

Se faltar dominio ou identidade, o envio nao deve trocar para dominio Ailian. O
resultado esperado e bloqueio/falha de configuracao visivel para o produto
chamador.

## Compatibilidade com o modelo atual

O modelo atual de `notification_preferences` pode continuar existindo durante a
migracao:

- flags por canal viram defaults globais;
- a matriz granular sobrescreve esses defaults quando existir;
- WhatsApp continua exigindo opt-in positivo;
- eventos obrigatorios continuam podendo usar `bypassPreferences`, mas devem ser
  restritos a casos legais, seguranca ou operacao essencial.

## Sequencia recomendada

1. Persistir dominios de envio e identidades de remetente.
2. Adicionar adapter de provider para criar/verificar dominios.
3. Expor APIs internas de dominio e identidade.
4. Fazer dispatch bloquear e-mail customer-visible sem dominio verificado.
5. Criar catalogo v1 como seed/dado estruturado no repo.
6. Expor catalogo, capabilities e preferencias em contratos separados.
7. Adicionar preferencias granulares e resolver fallback para flags legadas.
8. Integrar front por backend/proxy autorizado.
9. Evoluir catalogo v2 para Control Plane/PocketBase coordenado.

## Criterios de aceite

- cliente consegue cadastrar subdominio e copiar registros DNS;
- status de verificacao e atualizado sem expor provider como produto;
- e-mail customer-visible nao usa dominio Ailian em producao;
- identidade de remetente so fica ativa em dominio verificado;
- front mostra bloqueio claro quando e-mail externo nao esta configurado;
- preferencias permitem ligar/desligar canal por topico;
- eventos obrigatorios aparecem bloqueados na UI;
- topico novo nao exige deploy de front;
- topico desconhecido nao gera entrega silenciosa;
- Communications Service continua sendo o unico ponto de dispatch.