# E-mail transacional

A capability `core.transactional-email` fornece configuração protegida, templates versionados, preview, envio idempotente, fila, retentativas e painel administrativo. Requer OonCore `0.6.0` ou superior.

## Declaração funcional

Em `central.app.json`:

```json
{
  "capabilities": ["core.transactional-email"],
  "capabilitySettings": {
    "core.transactional-email": {
      "enabled": true,
      "scopePolicy": "tenant_override",
      "retentionDays": 30,
      "attachmentsPolicy": { "allowed": false },
      "templates": [
        {
          "code": "confirmacao",
          "definition": "./emails/confirmacao.json",
          "initialStatus": "active"
        }
      ]
    }
  }
}
```

`scopePolicy` aceita `app_only`, `tenant_required` ou `tenant_override`. `retentionDays` aceita 1 a 90. Nesta linha do Core, anexos não são permitidos.

A definição referenciada contém:

```json
{
  "code": "confirmacao",
  "name": "Confirmação",
  "subject": "Olá, {{nome}}",
  "htmlContent": "<p>Olá, {{nome}}.</p>",
  "textContent": "Olá, {{nome}}.",
  "variablesSchema": {
    "type": "object",
    "additionalProperties": false,
    "required": ["nome"],
    "properties": { "nome": { "type": "string" } }
  },
  "locale": "pt-BR",
  "initialStatus": "active",
  "attachmentsPolicy": { "allowed": false }
}
```

Templates usam somente interpolação Handlebars escapada. Blocos, helpers, subexpressões, triple-stache e conteúdo remoto executável não são aceitos.

## Envio no backend

Leia [APP_LICENSING.md](APP_LICENSING.md). O exemplo de **App comercial licenciado** demonstra somente a revalidação antes de enfileirar, verificável com mocks isolados. **Não habilita envio comercial assíncrono operacional**: a pendência do executor abaixo precisa ser resolvida antes disso. Root/globais tenantless seguem autoridade/RBAC próprios, sem tenant ou licença artificiais. A categoria não vem do request; tenant ausente em App comercial não é isenção. Runtime local não autoriza envio operacional.

```js
const { capabilities, licensing } = require("@oondemand/oon-core-back");

async function enviarConfirmacao({ email, nome, idempotencyKey }, req) {
  await licensing.assertBusinessOperation({
    tenantId: req.accessContext?.tenantId,
    correlationId: req.id,
  });
  return capabilities.transactionalEmail.send(
    {
      templateCode: "confirmacao",
      to: email,
      variables: { nome },
      idempotencyKey,
      correlationId: req.id,
      maxAttempts: 5,
    },
    {
      ...req.accessContext,
      userId: req.accessContext?.userId,
    },
  );
}
```

`idempotencyKey` é obrigatória. O retorno é `{ dispatchId, status, correlationId }`; `status` é `accepted` ou `duplicate`. Não trate `accepted` como entrega confirmada.

### Licenciamento, fila e retentativas

O guard propaga `423` com o código comercial (por exemplo, `APP_LICENSE_SUSPENDED`) ou `503`/`LICENSE_AUTHORITY_UNAVAILABLE`. Preserve status/código no tratamento padrão do Core, mostre o motivo e mantenha a operação pendente, sem enfileirar ou devolver sucesso. Contexto comercial ausente também impede o enqueue. Não use pagamento, cache positivo ou captura do erro como autorização.

A autorização no enqueue não autoriza um envio futuro. O executor precisa revalidar a autoridade e o contexto antes de **cada tentativa** no provedor, inclusive retry automático e retry de dead letter. Em OonCore 0.6.10, este exemplo não comprova essa integração no worker; não há neste contrato uma opção pública para adicioná-la. Não habilite esse caminho comercial até existir contrato/implementação e prova isolada de suspensão ou revogação entre enqueue e envio, indisponibilidade, contexto divergente e retentativa. Não contorne essa lacuna acessando internals do SDK ou criando executor paralelo no App.

Mesmo uma revalidação correta é pontual e não atômica com o envio: não cancela chamada já iniciada nem desfaz e-mail aceito pelo provedor. Recibos, callbacks, reconciliação e histórico de efeitos anteriores devem continuar acessíveis conforme suas regras; não confunda essas operações com um novo envio. `accepted` e `duplicate` não são comprovantes de autorização duradoura nem de entrega.

Outras operações públicas do namespace `capabilities.transactionalEmail` incluem preview e versionamento de templates, consulta de dispatches e retry de dead letter. Para administração interativa, prefira o componente do Core.

## Painel administrativo

```tsx
import { CoreTransactionalEmail } from "@oondemand/oon-core-front";

export function EmailPage() {
  return <CoreTransactionalEmail />;
}
```

Proteja a rota e declare/conceda apenas as permissões necessárias:

- `transactional-email.config.read`
- `transactional-email.config.manage`
- `transactional-email.templates.read`
- `transactional-email.templates.manage`
- `transactional-email.dispatches.read`
- `transactional-email.dispatches.retry`

O componente usa as rotas autenticadas em `/core/transactional-email/*`. Credenciais são inseridas pela tela protegida e armazenadas pelo Core; nunca as coloque no manifesto, frontend, repositório ou log.

## Segurança e operação

- o backend resolve app e tenant a partir do contexto validado;
- `tenant_required` exige tenant; `tenant_override` usa configuração do tenant quando houver e recua para a do App;
- destinatários e variáveis são validados antes de enfileirar;
- payload protegido tem retenção limitada; listagens não retornam o payload protegido;
- erros usam `TransactionalEmailError` e códigos `EMAIL_*` sanitizados;
- no runtime local, falhe fechado até a capability e seu secret store/configuração estarem disponíveis;
- testes devem substituir a fronteira externa; não envie e-mail real e não grave credenciais de teste.

