# Uso do SDK Elixir

O SDK requer Elixir 1.16+ e Erlang/OTP 26+. Os perfis de verificação são Elixir/OTP 1.16/26, 1.17/27, 1.18/27, 1.19/28 e 1.20/29.

Os exemplos usam um cliente criado por `Mailcore.cliente/1`. O cliente é um valor imutável que pode ser compartilhado entre processos. A aplicação OTP e o transporte gerenciam os processos de infraestrutura; use os módulos de recurso para as operações HTTP.

## Configuração

```elixir
cliente = Mailcore.cliente(
  tempo_limite: 45_000,
  tempo_conexao: 10_000,
  limite_resposta_bytes: 32 * 1024 * 1024
)
```

Prazos usam milissegundos e devem ser positivos, até 24 horas. O padrão de `tempo_limite` é 30 segundos, `tempo_conexao` é 10 segundos e o limite de resposta é 16 MiB. `url_base` usa `https://mailcore.com.br/v1` e conserva o prefixo ao acrescentar as rotas dos recursos. HTTP só é permitido para loopback e mediante `permitir_http_local: true`, destinado a testes.

`Mailcore.cliente()` usa `MAILCORE_API_KEY`. `chave_api: valor` fornece uma chave explícita. Para consultar uma fonte de credenciais a cada chamada:

```elixir
cliente = Mailcore.cliente(fornecedor_chave: fn -> obter_chave_no_segredo_da_aplicacao() end)
```

A função deve aceitar zero argumentos e ser segura para concorrência. Ela retorna a chave como binário; falhas não expõem a causa original. Não combine chave explícita e fornecedor. Um fornecedor inválido não é substituído silenciosamente pela variável de ambiente.

`certificado_ca: pem` configura uma autoridade certificadora privada, mantendo validação de certificado e hostname. O valor é o conteúdo PEM em binário. `cliente_http: req` aceita um `%Req.Request{}` da aplicação; configure sua CA no próprio Req e não combine com `certificado_ca` do SDK. Esse cliente mantém as políticas e os passos personalizados escolhidos pela aplicação.

## Opções por chamada

```elixir
alias Mailcore.Recursos.Emails

opcoes = [
  chave_idempotencia: "pedido-123-confirmacao",
  tempo_limite: 60_000,
  limite_resposta_bytes: 64 * 1024 * 1024,
  cabecalhos: %{"X-Correlacao" => ["pedido-123"]}
]

{:ok, resposta} = Emails.enviar(cliente, pedido, opcoes)
```

As opções são uma keyword list no último argumento. Cabeçalhos usam nomes string e listas de valores. Cabeçalhos reservados ao transporte, como `Authorization`, `Host`, `Cookie`, `Content-Type`, `Accept` e `Idempotency-Key`, não podem ser substituídos por essa opção. Configure a idempotência no campo próprio.

Cabeçalhos de mensagem, como `%{cabecalhos: %{"X-Origem" => "loja"}}` no pedido, pertencem ao e-mail. `cabecalhos` nas opções pertencem à requisição HTTP. O servidor valida os cabeçalhos de mensagem permitidos.

Listagens e corpos opcionais possuem conveniências com padrões explícitos:

```elixir
{:ok, primeira_pagina} = Emails.listar(cliente)
{:ok, pagina_filtrada} = Emails.listar(cliente, %{limite: 20}, opcoes)
{:ok, pagina_com_opcoes} = Emails.listar(cliente, %{}, opcoes)
{:ok, copia} = Mailcore.Recursos.Modelos.duplicar(cliente, id_modelo)
```

Um segundo argumento keyword de opções não substitui o mapa de filtros: para opções sem filtros, passe `%{}` antes da keyword list.

## Conteúdo, modelos e anexos

Pedidos usam mapas com chaves textuais ou átomos dos campos do contrato HTTP. Respostas decodificadas usam chaves string. Campos `nil` e `false` no pedido são preservados, assim como mapas e listas vazias. `nil` na raiz não substitui um objeto. Textos devem conter UTF-8 válido e números devem ser finitos.

Envio direto aceita texto e HTML, cópias, cópias ocultas, resposta, tags, cabeçalhos e anexos conforme o contrato:

```elixir
pedido = %{
  remetente: "Equipe <notificacoes@seudominio.com.br>",
  destinatarios: ["cliente@example.com"],
  assunto: "Pedido recebido",
  html: "<img src=\"cid:marca\" alt=\"Marca\"><p>Recebemos seu pedido.</p>",
  anexos: [%{
    nome: "marca.png",
    tipo_conteudo: "image/png",
    conteudo_base64: Base.encode64(conteudo_imagem),
    id_conteudo: "marca"
  }]
}
```

O conteúdo Base64 é padrão e não inclui prefixo `data:`. O identificador CID deve corresponder ao usado no HTML. O SDK não baixa arquivos remotos para formar anexos. Destinatários, Cc e Cco usam endereços sem nome de exibição; o envio direto individual admite nome no remetente.

Envio por modelo usa a referência de um modelo publicado:

```elixir
pedido = %{
  destinatarios: ["cliente@example.com"],
  modelo: %{id: "boas-vindas", variaveis: %{"NOME" => "Ana"}}
}
```

Os padrões publicados podem fornecer remetente, assunto e resposta. Envio individual por modelo aceita anexos Base64 sem CID; não combine com HTML/texto, Cc/Cco, cabeçalhos ou nome de exibição no remetente. O servidor valida as combinações, estados e permissões.

```elixir
{:ok, resposta} = Mailcore.Recursos.Lotes.enviar(cliente, lista_de_pedidos, opcoes)
```

Um lote contém de 1 a 100 mensagens, numa única requisição, sem divisão automática. Não aceita Cc/Cco, cabeçalhos, nome de exibição no remetente ou anexos. O servidor aplica a aceitação atômica e preserva a ordem.

`agendada_em` recebe uma data ISO 8601 com fuso. A janela e as regras de reagendamento e cancelamento pertencem ao servidor. Atualizações e ações conservam a revisão exigida pelo contrato; conflitos HTTP 409 continuam sob decisão da aplicação.

## Respostas, números e paginação

```elixir
{:ok, resposta} = Mailcore.Recursos.Emails.obter(cliente, id)
{:ok, dados} = Mailcore.Resposta.json(resposta)

estado = dados["estado"]
status_http = resposta.status
```

O binário original permanece em `resposta.dados`; `json/1` não altera esses bytes. Inteiros usam bignums do Elixir, até o limite de 1.024 dígitos do parser Jason na decodificação. Decimais são `%Decimal{}`, preservados sem conversão intermediária para float. A biblioteca mantém a configuração global do Jason da aplicação. Campos nulos, booleanos, mapas e listas vazias mantêm seus tipos. `json!/1` é uma conveniência que levanta `Mailcore.Erro` ao falhar.

Cada listagem consulta uma página e mantém o envelope recebido. Use o cursor `proximo` ou `proximo_cursor` como `antes`, conforme o recurso. Recebimento também aceita `depois`, sem combiná-lo com `antes`. Filtros `nil` são omitidos; `false` é enviado como `false`. Não há criação de átomos a partir das chaves de dados recebidos.

`%Mailcore.Erro{}` separa código, mensagem de negócio, status opcional e cabeçalhos. A representação padrão e `Exception.message/1` omitem a mensagem de negócio. O campo `erro.mensagem` pode conter dados pessoais retornados pela API; avalie o conteúdo antes de registrar ou exibir.

## Downloads em fluxo

Use as rotas autenticadas de exportação, original MIME e anexos:

```elixir
{:ok, arquivo} = File.open(destino, [:write, :exclusive, :binary])

try do
  consumidor = fn parte ->
    case IO.binwrite(arquivo, parte) do
      :ok -> :ok
      {:error, _motivo} ->
        {:error, %Mailcore.Erro{codigo: "erro_consumidor", mensagem: "Não foi possível gravar o arquivo."}}
    end
  end

  Mailcore.Recursos.Recebimento.baixar_original_fluxo(
    cliente,
    id,
    consumidor,
    limite_resposta_bytes: 64 * 1024 * 1024,
    tempo_limite: 60_000
  )
after
  File.close(arquivo)
end
```

`destino` é um caminho escolhido pela aplicação. O consumidor recebe cada bloco em binário e retorna `:ok` ou `{:error, %Mailcore.Erro{}}`; a aplicação pode reter o bloco. Sucesso informa `total_bytes` e deixa `dados` vazio. Uma falha exige tratar o arquivo parcial.

`ExportacoesContatos.baixar_fluxo/3`, `Recebimento.baixar_original_fluxo/3` e `Recebimento.baixar_anexo_fluxo/4` possuem opções no último argumento adicional. As variantes sem `_fluxo` acumulam a resposta em memória. O limite de bytes é aplicado durante a leitura, inclusive em fluxo e em respostas HTTP de erro. O comprimento declarado e os demais cabeçalhos são validados quando o transporte os disponibiliza ao SDK. Uma desconexão anterior à disponibilização desses metadados pode resultar em `falha_de_rede`, sem status HTTP; isso não equivale à classificação de um corpo completo pela biblioteca.

URLs temporárias em metadados não são seguidas automaticamente e não recebem a chave da API. Detalhes de HTML com `data_uri` podem crescer pelo conteúdo dos anexos; consulte com `formato_html: "cid"` ou estabeleça limite adequado.

## Idempotência

Persista a chave e o conteúdo da intenção antes de uma operação que suporte idempotência. Ao decidir repetir uma intenção, reutilize ambos. `Emails.obter_por_idempotencia/2` e `Lotes.obter_por_idempotencia/2` permitem conciliação com permissão de leitura. A retenção é determinada pelo servidor.

Não deduza rejeição a partir de timeout, cancelamento ou queda de conexão: a aceitação pode ter ocorrido antes da interrupção da resposta. O SDK não altera revisões nem resolve conflitos de edição por conta própria.

## Limites

A serialização JSON tem teto geral de 50 MiB e profundidade máxima de 100 níveis; isso não amplia limites menores do contrato de negócio. O teto acomoda escapes das importações CSV, cujo limite é 8 MiB. Valores de resposta permanecem sob o limite configurado da operação. O SDK preserva os dados do protocolo, mas não reproduz todos os schemas de negócio no cliente.

## Prazos e transporte

O prazo total inclui preparação do pedido, aquisição de credenciais, conexão e leitura do corpo, além do consumidor de fluxo. A tarefa interna é encerrada quando o prazo expira. O encerramento do processo chamador também encerra a tarefa de transporte por monitoramento supervisionado. Callbacks devem cooperar e retornar prontamente; operações em NIF ou serviços externos podem ter seus próprios efeitos e limites. Um prazo expirado não comprova que a API deixou de aceitar uma intenção.

O consumidor é executado pela tarefa de transporte. Ao enviar mensagens para outro processo, capture seu PID antes de iniciar a operação; `self()` dentro do consumidor identifica a tarefa que o executa. Cuide de arquivos parciais e de efeitos que o callback já realizou antes de uma falha.

O transporte padrão usa um `Req.Request` de baixo nível com `Req.Finch` e pool HTTP/1.1 compartilhado. Cada pedido tem opções próprias, independentes de `Req.default_options` da aplicação, com TLS e hostname verificados. A execução padrão não possui passos de retentativa, redirecionamento, descompressão ou decodificação automática e não mantém cookies. O SDK não mantém fila ou backoff de negócio.

Um `%Req.Request{}` injetado mantém seus próprios passos, adaptador e políticas; configure-o conscientemente. Conteúdo é solicitado como `identity`; codificações inesperadas são recusadas para preservar bytes de CSV, MIME e anexos. `Emails.cancelar/2` cancela uma intenção no servidor conforme seu estado; o encerramento de uma chamada de rede segue o ciclo de vida do processo chamador e seu prazo.

## Verificar webhooks

```elixir
case Mailcore.Assinatura.verificar(corpo_bruto, cabecalhos, segredo_webhook) do
  {:ok, evento} ->
    tipo = evento.tipo
    dados = evento.dados
    # Processe após deduplicar o identificador duravelmente na aplicação.

  {:error, erro} ->
    IO.puts(Exception.message(erro))
end
```

`corpo_bruto` é o binário original, antes de qualquer parser ou recodificação. Cabeçalhos usam nomes string e valores em binário ou listas de um único binário. Os campos assinados são `svix-id`, `svix-timestamp` e `svix-signature`; o segredo usa prefixo `whsec_...`.

A verificação usa HMAC-SHA256, comparação de assinaturas de tamanho fixo e tolerância padrão e máxima de 300 segundos. `tolerancia: segundos` pode reduzir a janela; `agora: instante_unix_em_segundos` permite testes determinísticos. O limite do corpo é 8 MiB.

A biblioteca não mantém estado de deduplicação. Armazene `svix-id` de forma durável antes de processar o evento novamente. Corpo e dados podem conter informações privadas; a autenticação da assinatura não autoriza sua publicação em logs.
