@baruk/communication
v0.2.0
Published
cap.communication.transactional-email — a capability de comunicacao do ecossistema Baruk, provider-agnostic e sem dependencia de produto.
Maintainers
Readme
@baruk/communication — engine de comunicação do ecossistema
Implementa cap.communication.transactional-email, contrato v0.2.2.
O problema que ele resolve
A capability existia e era boa — provider-agnostic, com dedup por reserva, erros
classificados por retryability e pontos de extensão para fora. Só que morava dentro de
@baruk/core, o motor do Baruk Office.
Medido antes desta extração:
| | |
|---|---|
| Implementações da mesma capability no ecossistema | 6 |
| Delas consumindo a canônica | 2 (sc-organizacao-contabil, baruk-connect) |
| Delas com envio próprio | 3 (dental-platform-engine, manga-verde-bi, protetica-dente) |
| Dependências reais do módulo mail/ | 1 — node:crypto |
| Arquivos alcançados ao importar mail/ | 9 |
| Arquivos alcançados ao importar o barrel de @baruk/core | 75 |
| Peso de instalar @baruk/core para mandar um e-mail | 8,5 MB (postgres, zod, @baruk/shared) |
| Peso deste pacote | 17,9 kB |
O acoplamento nunca foi de código. Era de endereço.
O que ficou de fora, e por quê
auth-templates.ts (casca HTML com a marca "Baruk Contábil") e
transactional-outbound.ts (adapter dos fluxos de identidade do Office: staff_invite,
password_reset) continuam em @baruk/core. São do produto, não da capability — e um
engine que conhecesse a marca de um produto não serviria aos outros.
Há teste que reprova se o nome de qualquer produto aparecer em src/.
Contrato
sendTransactionalEmail(input): Promise<DeliveryResult>DeliveryStatus = 'sent' | 'failed' | 'skipped' — não existe queued: a capability é
síncrona e 1:1, e fila é responsabilidade do consumidor.
DeliveryErrorCode é organizado por o que fazer, não por causa técnica:
| Código | Natureza |
|---|---|
| timeout, provider-unavailable | transitório — repetir é seguro |
| invalid-recipient, provider-rejected | permanente — repetir nunca resolve |
| unknown | tratado como permanente |
skipped deve ser lido pelo consumidor como "não entregue". Sem apiKey, todo envio
é skipped e nenhuma rede é tocada.
sent significa o provedor aceitou a responsabilidade pela entrega — não que chegou,
não que foi lido. Quem quiser afirmar entrega precisa de webhook, que não está aqui.
Pontos de extensão
TemplateRenderer, SenderResolver, DeliveryHook e DedupStore são injetados. O
renderer padrão é passthrough e falha alto se o chamador não fornecer subject/html:
um motor de template dentro da capability decidiria a aparência de produtos que ela não
conhece.
InMemoryDedupStore declara mode: 'in-memory' — não promete dedup entre réplicas.
Quem precisa da garantia fornece um store mode: 'shared' (Redis/Postgres).
Dependências
Nenhuma. Só o Node. Há teste que reprova qualquer import que não seja relativo ou
node:.
Testes
npm test compila e roda contra o dist/ — o que vai ao npm. Testar o fonte deixaria
passar exatamente a classe de defeito que este pacote nasceu para corrigir: um artefato
publicado que não entrega o que o repositório promete.
Cobertura: aceito · falha transitória · falha permanente · destinatário inválido · provedor indisponível · resposta malformada · credencial ausente · credencial nunca vazando no erro · idempotência · concorrência · fronteira de produto · fronteira de dependência.
Publicação
A primeira versão sai por publicação local interativa, com 2FA — não por OIDC. O npm
exige que o pacote exista antes de aceitar um trusted publisher (POST
/-/package/{package}/trust responde 404 para pacote inexistente; npm trust diz "Package
must exist"), e não há equivalente ao pending publisher do PyPI (npm/cli#8544, aberto).
Depois dela, npm trust github configura o publisher e todas as versões seguintes saem
por OIDC, pelo publish.yml — sem token em lugar nenhum. Nenhum Granular Access Token
foi criado para o bootstrap; a decisão foi publicar interativamente e não abrir credencial
de escopo @baruk.
Depois de publicado, @baruk/core deve passar a re-exportar deste pacote em
packages/core/src/mail/, para que exista uma fonte canônica só. Enquanto isso não
acontece, este engine não é consumido por ninguém.
Estado do gate em products/parcelai/docs/adr/0008-entrega-de-comunicacao.md.
