# Uma caixa de entrada, dois provedores: onde colocar a fronteira

> Dois canais de mensageria com regras incompatíveis atendidos pelas mesmas telas. O que variou, o que não podia variar e por que capacidade de provedor mora em código, não no banco.

Publicado em 2026-06-02 por Time Orion · Blog da Orion
Versão canônica: https://orion.moe/blog/uma-caixa-de-entrada-dois-provedores
Índice do blog: https://orion.moe/blog

---

Nosso painel atende conversas que chegam por dois caminhos diferentes de mensageria: um canal oficial, com API documentada, e um canal não oficial, com conexão persistente própria.

Eles são incompatíveis em quase tudo que importa:

| | Oficial | Não oficial |
|---|---|---|
| Conexão | requisição HTTP | socket persistente, um processo por conta |
| Janela para iniciar conversa | existe, e é limitada | não existe |
| Mensagem pré-aprovada | obrigatória fora da janela | não se aplica |
| Grupos | não expostos | disponíveis |
| Mídia | upload prévio, envio por identificador | bytes direto |
| Histórico ao conectar | não vem | vem |

E, mesmo assim, quem usa o painel vê **uma caixa de entrada só**. Este post é sobre onde a fronteira entre os dois foi colocada — e por quê.

## O que não podia variar

Antes de decidir o que abstrair, decidimos o que **nunca** muda entre provedores:

conversa, mensagem, atribuição de responsável, notificação, histórico e controle de acesso.

Essas coisas são do nosso domínio, não do provedor. Se um dia entrar um terceiro canal, elas continuam iguais — e é justamente por isso que a tabela central de canais nasceu com nome genérico e uma coluna de "família". Nomear a tabela com o nome do primeiro provedor teria custado uma migração de renomeação no dia em que o segundo chegasse.

## O que variou

O que muda entre provedores é **como se envia** e **quais regras se aplicam**. Isso ficou atrás de uma interface — um contrato com as operações que todo canal precisa oferecer.

E ficou uma decisão que vale destacar: **as capacidades de cada provedor vivem em código, não em configuração**.

Era tentador guardá-las numa tabela: "este canal suporta grupos", "aquele exige mensagem pré-aprovada". Seria flexível e seria errado. Capacidade não é preferência de operação — é propriedade de quem implementou a integração. Um administrador não pode "ligar" suporte a grupos num provedor que não expõe grupos; ele só conseguiria criar um estado impossível.

O corolário está no código: a função que resolve as capacidades de um provedor **lança** quando recebe um provedor desconhecido, em vez de devolver um padrão. Assim, o portão que decide se pode enviar mensagem fora da janela falha fechado. Um padrão permissivo teria permitido envio indevido; um padrão restritivo teria quebrado o canal em silêncio. Lançar é a única opção que grita.

## O espaço de identificadores

Um problema prático interessante: os dois provedores geram identificadores de mensagem em espaços diferentes. O do canal oficial é global; o do não oficial só é único dentro de cada conversa.

A tabela tem uma restrição de unicidade global — que é o que garante a deduplicação de reentrega e o casamento com os eventos de status.

A solução foi assimétrica de propósito: o identificador do canal oficial entra **intocado**, e o do não oficial entra **prefixado** com a identificação do canal e da origem. Reescrever o identificador do primeiro teria desmontado o vínculo com os eventos de status e com as citações já gravadas — dado histórico que já existia.

A lição: quando um espaço de identificadores precisa acomodar outro, quem chega depois é quem se adapta.

## O que não virou linha

Estado volátil — presença, "digitando", estado da conexão, batimento — não vira registro. Ele atualiza o estado atual do canal e pronto.

São dezenas de milhares de eventos por dia que não respondem a nenhuma pergunta futura. O mesmo raciocínio vale para a sincronização de histórico ao conectar uma conta: ela chega em lotes, e o receptor precisa estar preparado para processar um lote de centenas de mensagens numa requisição, em vez de uma requisição por mensagem.

## A regra que ficou

> Abstraia o que **muda entre implementações**. Mantenha concreto o que é do **seu** domínio.

Abstrair demais produz uma camada que precisa ser atualizada a cada provedor novo — o oposto do objetivo. Abstrair de menos produz condicionais espalhadas pela aplicação inteira.

O teste prático: quando o terceiro provedor chegar, quantos arquivos precisam mudar? Se a resposta for "um, mais o registro de provedores", a fronteira está no lugar certo.

## O resumo

Uma caixa de entrada com dois provedores incompatíveis é possível quando a fronteira separa **regra de envio** (que varia) de **modelo de conversa** (que não varia) — e quando capacidade é propriedade do código, com falha fechada para o desconhecido.

O que o usuário vê é uma tela só. O que sustenta isso é a lista, feita antes, do que jamais poderia variar.
