Produtos cuja interface são mensagens de texto
O apelo é real: as pessoas não gostam de instalar aplicativos ou visitar sites, mas todos podem enviar mensagens de texto. A pegadinha é que você não é dono do canal. As operadoras dos EUA são, e elas têm uma política de conteúdo e um processo de aprovação que pode te recusar permanentemente. Esse portão decide o que o produto pode ser, então ele vem primeiro.
Etapa 0 — o portão. Faça isso antes do design, não depois.
A partir de fevereiro de 2025, operadoras dos EUA bloqueiam tráfego A2P não registrado por completo, então uma marca e uma campanha devem ser aprovadas antes que uma única mensagem seja enviada. Releia a documentação de erros ativa a cada vez; a política da operadora muda.
| Código | Gatilho | Reenviar? |
|---|---|---|
| 30953 | linguagem explícita/sugestiva, ou um site vinculado hospedando "conteúdo pornográfico, de acompanhantes, namoro ou entretenimento adulto" | Não |
| 30885 | "alto risco" — marketing enganoso, ou um registro que deturpa o tráfego | Não |
| 30898 | muitas marcas em um único EIN, ou um registro com aparência duplicativa | Sim, com justificativa |
Regra: nomeie a categoria de conteúdo do produto em voz alta na primeira resposta e diga se ela está nessa lista. Se a categoria (ex.: namoro) for literalmente uma palavra-chave em uma regra de rejeição, descobrir isso no minuto cinco muda a arquitetura, o texto da página de destino e o plano de fallback. SHAFT é a abreviação: Sexo, Ódio, Álcool, Armas de fogo, Tabaco — além de Cannabis, e namoro anda junto.
Regra: registre um projeto paralelo como uma marca de Empresário Individual (Sole Proprietor), não sob o EIN de uma empresa existente. Padrões de risco seguem o TIN (30898), então uma recusa em um projeto paralelo não deve afetar a capacidade de envio do seu negócio principal. Empresário Individual é voltado para pessoas físicas sem TIN; a taxa de transferência é baixa, e baixa é suficiente para um beta.
Regra: nunca crie uma página de destino falsa para obter aprovação de campanha. Uma página que esconde o que o tráfego realmente é é o padrão 30885, e converte um risco de um número em um risco de todos os números que você já registrou. Indique as probabilidades claramente e deixe o proprietário decidir.
Descreva o serviço pelo que ele faz mecanicamente, e somente se isso for verdade (ex.: journaling e responsabilidade em pequenos grupos, quando é isso que as mensagens são). Se nenhuma descrição verdadeira ultrapassar o portão, a resposta é um canal diferente, não um eufemismo melhor.
A revisão recompensa fatos que você pode apontar: aceitação apenas inbound, volume conversacional, sem links em mensagens recorrentes, STOP tratado antes de tudo, conteúdo explícito recusado na porta.
Etapa 0.5 — leia o briefing por NÚMEROS e OBJETOS, não por intenção
Mantenha o briefing do fundador literalmente como README.md e diga em CLAUDE.md que ele tem precedência sobre
todo documento de engenharia.
Regra: antes de escrever uma máquina de estados, liste cada duração no briefing e cada substantivo sobre o qual ela atua. Se existirem duas durações, elas são duas funcionalidades. Um briefing com um atraso de uma semana nas respostas dos amigos chegando ao autor e um bloqueio de duas semanas na atualização do próprio autor é fácil de colapsar em uma retenção de sete dias em a entrada — o único objeto sobre o qual nenhum atraso era — e todo erro downstream segue dessa fusão.
Regra: nomeie o objeto de cada atraso — "retido DE quem". Retido dos amigos e retido do autor são produtos opostos.
Regra: quando o design literal do briefing não puder ser aplicado no canal, diga isso e dê as alternativas — não escolha silenciosamente o que é aplicável. (O criador de um chat em grupo é um membro dele, então nenhum código pode reter as respostas do grupo dele.) Isso é uma decisão de premissa, e decisões de premissa são do proprietário.
Uma leitura incorreta coerente não parece ser uma. Mantenha um parágrafo de design substituído riscado no lugar em vez de deletá-lo; ele mostra a uma sessão posterior como o briefing foi mal interpretado.
Etapa 1 — o que o canal pode e não pode fazer
- Mensagens em grupo a partir de código funcionam. Twilio Conversations faz MMS em grupo apenas em códigos longos +1 — não gratuitos, não códigos curtos — com limite de 10 participantes incluindo seu próprio número. Não afirme o contrário de memória.
- Participantes de SMS de MMS em grupo carregam um Address e nenhum ProxyAddress, então um número atende a todas as salas — mas a regra de unicidade de participante/proxy (50425) não tem comportamento documentado para participantes sem proxy. Nunca projete várias salas concorrentes que compartilham um número e um conjunto de participantes. Uma sala durável por pessoa tem um conjunto de participantes estável.
- Uma página web não pode ler a agenda de contatos de um iPhone. A Contact Picker API é apenas Chrome-no-Android. "Sincronizar seus contatos" está morto na chegada; substitua por uma linha que o usuário encaminha — o texto de entrada do destinatário é o registro de consentimento que as operadoras querem de qualquer forma.
- Um bot do Telegram não pode criar um grupo nem adicionar ninguém a um, e com o modo de privacidade ativo, ele só ouve comandos endereçados a ele. Ambos ajudam: o criador adiciona o bot ao chat que já possui, e conversas gerais não podem ser confundidas com entrada. Criar um bot precisa de um humano no @BotFather (sem API); peça o token.
- A WhatsApp Cloud API não tem mensagens em grupo.
- APIs do iMessage (Sendblue, LoopMessage) fazem bolhas azuis e chats em grupo reais, mas custam cerca de US$ 100/linha/mês mais taxas de configuração.
Etapa 2 — arquitetura que sobrevive à perda do canal
Porque o canal pode ser recusado, o transporte deve ser um arquivo:
machine.js puro: (contexto, evento) -> {respostas, efeitos} sem E/S, sem relógio
engine.js montagem de contexto -> máquina -> efeitos o caminho inteiro
effects.js createEffects({db, canal}) executa decisões
twilio.js / telegram.js intercambiável
api/*.js adaptadores finos sobre engine.js ~10 linhas cada
sim/ banco em memória + canal de console
- O núcleo de decisão recebe
nowcomo argumento e não toca na rede. Todo bug nessa classe de produto é um bug de sequenciamento (um lembrete que dispara duas vezes, uma mensagem para uma sala vazia, um item retido que nunca chega), e nenhum é testável contra uma rede ativa e um timer de sete dias. - Adaptadores são argumentos, nunca imports, na camada de efeitos — a mesma refatoração que o simulador e o canal de fallback precisam.
- Sem lógica dentro de um manipulador HTTP. Integração específica de canal escrita dentro
de
api/telegram.jsé intestável; extraia-a.
Etapa 3 — dirija-o, não apenas teste-o
node sim/drive.js roteirizado: o loop completo, uma semana pulada, as proteções
node sim/drive.js --repl digite como qualquer número; /as <n> /jump <dias> /state
- O simulador deve executar o caminho de produção, não uma cópia paralela.
- Espere que uma primeira direção revele vários defeitos reais que testes unitários não conseguem alcançar: uma sala que se cumprimenta antes de alguém estar nela, um dia da semana simples para uma data a uma semana, um lembrete que não diz qual item significa, uma sala cujos membros todos saíram.
- Quando um teste falha, verifique o teste primeiro (uma regex de data que não permite a vírgula em
Sun, Aug 30; umdoesNotMatchque dispara em uma mensagem diferente e legítima). - Nunca decida ou imprima a partir de um timestamp carimbado pelo armazenamento. O armazenamento escreve
created_atdo relógio real, não donowinjetado do mecanismo, então sob um relógio pulado ele mente (e imprime datas no passado). Decida apenas a partir de colunas que a máquina escreveu, e pendure datas relacionadas a uma âncora armazenada.
Etapa 4 — texto para uma tela de bloqueio
Um arquivo contém toda string de saída.
- Sem emoji, sem pontos de exclamação, sem marketing.
- Nunca um dia da semana simples para uma data com mais de um ou dois dias de distância.
Sun Aug 30. - Perdoe pontuação e maiúsculas/minúsculas em toda palavra-chave —
Wait.eYes!devem funcionar. - Prosa contendo uma palavra-chave ainda é prosa: "we should wait and see" não é WAIT. Corresponda a mensagem inteira canonizada, nunca uma substring.
- STOP / HELP / START são analisados antes de todo estado da aplicação e antes de qualquer chamada de modelo, então a desistência não pode ser quebrada por um bug em outro lugar ou uma falha de API.
- Se um modelo protege conteúdo, falhe fechado: se a verificação não puder executar, não envie nada e diga isso.
Etapa 5 — se um modelo reescrever conteúdo do usuário, execute-o antes de confiar nele
Uma proteção que remove nomes de terceiros de entradas de usuário pode ler perfeitamente e ainda:
- Inventar um fato — ao pedir para substituir "Jane Doe" por "um identificador natural", escreveu "a garota do app" quando nada mencionou um app; os destinatários leram uma fabricação como fato.
- Corrigir demais e deletar detalhe real — "Jane do clube de corrida" se tornou "ela".
Regra: a instrução deve dizer o que o modelo NÃO PODE inventar e o que ele NÃO PODE deletar. A forma funcional: baseie a substituição na fonte, substitua o token não a frase ao redor dele, e prefira um fallback neutro a reutilizar um detalhe que a frase já afirma.
Regra: fixe cada defeito do modelo ativo como um teste opt-in, ignorado sem uma chave de API, para que edições de prompt não possam regredir silenciosamente sem aumentar os custos a cada execução de teste. Reserve 2 a 5 segundos por chamada de proteção.
Custos (ordem de grandeza; confirme ativo)
Um código longo +1 custa um ou dois dólares por mês; SMS, menos de um centavo por segmento em cada direção; MMS, alguns centavos. O registro de Empresário Individual custa alguns dólares uma vez mais uma pequena taxa mensal de campanha. O Telegram é gratuito.
Perguntar vs. Decidir
- Perguntar: se deve submeter um registro honesto sabendo que pode ser recusado permanentemente, ou começar no Telegram (dinheiro e um resultado irreversível); qualquer coisa que mude o que o produto é.
- Decidir: arquitetura, voz do texto, a máquina de estados, cobertura de teste, qual adaptador primeiro, formatos de data, comportamento da proteção.
Antes de relatar algo como bloqueado, enumere cada credencial candidata (uma chave de API esgotada no disco não significa que não há outra que funcione).
Veja também: build-remote-mcp-server (outro padrão de "superfície nativa").