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
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:
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
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:
{: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:
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:
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.
{: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
{:ok, resposta} = Mailcore.Recursos.Emails.obter(cliente, id)
{:ok, dados} = Mailcore.Resposta.json(resposta)
estado = dados["estado"]
status_http = resposta.statusO 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:
{: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)
enddestino é 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
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))
endcorpo_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.