# Push: ficar no OneSignal, trocar pelo Firebase, ou pôr o Novu na frente (09/10/2026)

Pedido do dono, textual: "meça o esforço de trocar o onesignal pelo firebase. Compare com o Novu também
(https://github.com/novuhq/novu) e diga os pros e contras de cada um deles". Levantado em 09/10/2026 por
leitura do código, três contagens na cópia `franquiabv_dev` e a documentação oficial de cada um (as fontes
estão no fim). **Nada foi decidido nem mudado**: a ordem do dono continua valendo ("por hora, ignorar tudo do
onesignal, visagismo e sms") até ele dizer outra coisa. *(Ele disse em 10/10/2026: a decisão logo abaixo.)*

## A decisão do dono (10/10/2026)

Textual: "Aguarde terminar o R16. Sobre a modificação do OneSignal, o motivo da troca é o custo mensal,
principalmente conforme for escalando. vamos pela opção A (Firebase direto), mas deixar o caminho aberto para a opção
C (Novu instalado por nós) se fizer sentido no futuro. Faça a medição e já prepare para a migração. irá entrar já no
servidor de teste, apenas do app ser atualizado apenas depois em produção".

- **A opção A**, o servidor falando direto com o FCM, **pelo custo mensal** do OneSignal ao escalar (a medição está
  abaixo). **A C fica aberta**: o envio mora atrás de uma interface (`ISBase\Push\EnviadorDePush`), e o Novu
  auto-hospedado, se vier, é mais uma implementação dela.
- A ordem antiga ("por hora, ignorar tudo do onesignal") **cede a esta, para esta migração**.
- **O servidor de teste recebe o código já**; a **produção segue no OneSignal** até o aplicativo novo ser publicado.
  O padrão da configuração (`push.provedor`) é `onesignal`, e a produção só muda quando alguém escrever outro valor no
  `local.php` dela.

**O que já está feito (a primeira parte, 10/10/2026):** a camada de envio com o OneSignal (o mesmo pedido de hoje,
agora com tempo limite, SSL e a resposta lida; e a API nova pronta para a chave nova do D3) e o FCM (a HTTP v1, o
token OAuth2 da conta de serviço com um JWT assinado à mão, o cache dele, a retentativa no 429 e no 5xx como a
página de escala do FCM manda, com um disjuntor para a queda, e o token morto); a convivência pela configuração
(`onesignal`, `ambos` ou `fcm`; o `fcm` só vale com o cadastro dos aparelhos, da segunda parte, e até lá cede ao
OneSignal); o D456; e os ambientes sem saída para os
três nomes novos (`api.onesignal.com`, `fcm.googleapis.com`, `oauth2.googleapis.com`).

**A segunda parte, no mesmo dia (10/10/2026, PUSH-FCM-APARELHOS):** a tabela dos aparelhos (`clientes_aparelhos`:
o token do FCM ou o FID, a plataforma, a versão do aplicativo e o telefone da sessão que o registrou; um aparelho é de
um cliente por vez, e só enquanto o telefone dele for o da sessão), com a migração
no passo 0 do corte; o cadastro pelo banco, lido em lote pela campanha e pela fila, que tira os mortos que o FCM
devolve; a rota da API V4 com que o aplicativo novo registra e retira o aparelho; e o guia para quem mantém o
aplicativo, `docs/push-guia-do-aplicativo.md`. A porta do Novu está logo abaixo da medição.

**A terceira rodada (11/10/2026, as respostas 71 a 76 do dono):** o aplicativo novo adota o login V4 (a 71), e o
servidor renova a sessão V4 (`POST /api/v4/sessoes/renovacao`, o D59); a tabela dos aparelhos está na cópia local, nos
dois bancos (a 72); o teto de 10 aparelhos e a convivência por cliente ficam (a 73 e a 75), e o liga-desliga do push,
como está até o aplicativo novo (a 76); e o servidor de teste está preparado para falar com o Google, com um projeto
Firebase só de teste, em `ambos`, o OneSignal ainda cortado (a 74; ligar é do dono, pelo passo de
`docs/ambiente-teste.md`, "O push pelo Firebase no teste"). **O que falta** (as credenciais, o aplicativo nas lojas com
o login V4 e o SDK do Firebase, a tabela na base do teste do BVIP, o teste em aparelho e as três perguntas novas do lote)
está no **D461** de `docs/debitos-tecnicos.md`. *(Até 11/10/2026 esta lista dizia também "o login V4 no aplicativo" e
"a tabela na cópia local": a 71 e a 72 os responderam.)*

## O que o OneSignal faz hoje

- **Um arquivo fala com ele**, `module/ISBase/src/Util/OneSignal.php`: título e texto (só em `en`) e um link, por
  um POST por destinatário na API legada (`onesignal.com/api/v1/notifications`, cabeçalho `Basic`). Sem imagem,
  segmento, agendamento nem dados: o horário (9h às 20h) é o ERP que controla. O web push (`enviarNotificacaoWebPush`)
  é código morto.
- **Dois chamadores, os dois no cron por URL:** `CronJobService::marketingPush` (a fila `push_app`, que nove pontos
  do sistema enchem) e `CronJobService::marketingEnvios` (as campanhas da tela de marketing).
- **O ERP não guarda aparelho nenhum.** Quem liga aparelho a cliente é o OneSignal, pela marcação `usuario` que o
  aplicativo grava com o id do cliente. É isso que uma troca precisa recriar.
- **Volume medido na cópia:** 42 campanhas de push e 58.812 envios entre 01/02 e 01/08/2026 (cerca de 9,8 mil
  por mês); as rotinas automáticas só mandam push aos clientes das 8 unidades ativas sem WhatsApp. Clientes com
  acesso ao aplicativo nos 30 dias até 16/08/2026: 6.156 (53.484 já acessaram alguma vez).
- **O aplicativo não está no repositório.** Pelo registro de acessos (D5), o iPhone é cerca de 78% das chamadas,
  e há sinais de mais de uma tecnologia (CFNetwork, okhttp, Dart). A pilha não é conhecida.
- Achados no caminho: o D456 (a campanha para "Usuários" e a marcação dos clientes), o D457 (um backup do
  `local.php` com as chaves antigas) e, já registrados, o D67 (sem tempo limite, SSL desligado, falha muda, o
  liga-desliga que não desliga) e o D3 (as chaves que estiveram no código).

## As opções

| | Servidor (ERP) | Aplicativo | Infraestrutura | Total | Custo por mês |
|---|---|---|---|---|---|
| **A. Firebase (FCM) direto** | 48 a 77 h | 20 a 56 h | 3 a 6 h | 71 a 139 h | US$ 0 |
| **B. Novu na nuvem, entregando pelo FCM** | 36 a 56 h | 20 a 56 h | 4 a 8 h | 60 a 120 h | cerca de US$ 30 no volume medido |
| **C. Novu auto-hospedado, pelo FCM** | 36 a 56 h | 20 a 56 h | 16 a 40 h | 72 a 152 h, mais 2 a 6 h/mês | a máquina (US$ 40 a 100, não pesquisado) |
| **D1. Novu na frente do próprio OneSignal** | 22 a 51 h | 0 a 10 h | 3 a 5 h | 25 a 66 h | Novu mais OneSignal |
| **D2. Ficar no OneSignal e consertar o envio** | 5 a 9 h | nada | 1 a 2 h | 5 a 11 h | pela tabela de 09/10, pelo menos US$ 93 (US$ 19 fixos + US$ 0,012 por usuário ativo), se não houver plano antigo |

As horas são estimativa (faixa), não medida. **Acréscimos para A, B e C:**

- **O cadastro do aparelho.** O aplicativo precisa mandar o token ao ERP por uma rota com a sessão do cliente, e
  essa sessão é o login V4 por código no WhatsApp, que o aplicativo instalado não tem (D59) e que não funciona
  nas 8 unidades sem WhatsApp. Ou se constrói o login no aplicativo (+16 a 40 h no aplicativo, +4 a 8 h no
  servidor), ou se aceita uma rota sem login, com a confiança da marcação de hoje -- decisão do dono.
- **As lojas.** Toda versão nova pede o Xcode 26 e o SDK iOS 26 (desde 28/04/2026) e o target SDK 36 do Android
  (desde 31/08/2026, com extensão até 01/11/2026): +0 a 16 h por plataforma, se o aplicativo estiver atrás.
- **As esperas, fora das horas:** a revisão da Apple (1 a 2 dias), a do Google Play (até 7 dias no pior caso
  comum) e a convivência dos dois provedores, de 30 a 90 dias, até a cauda atualizar o aplicativo.
- **Biblioteca no PHP:** a `google/auth` e a `kreait/firebase-php` pedem `psr/cache` 2 ou 3, que o
  `laminas-cache` trava no 1 (D11; desde 08/11/2026 no 3.13.0, que ainda pede `psr/cache` `^1.0`): o token OAuth2 da conta de serviço sai à mão (JWT RS256 com
  `openssl_sign`), no molde do `ArmazenamentoS3.php`. O SDK PHP do Novu pede PHP 8.2 e ainda é beta: a API REST à mão.

## Prós e contras

**A. Firebase direto.** A favor: grátis; a lista de aparelhos fica no nosso banco (contar, auditar e apagar pela
LGPD com o cliente); um fornecedor a menos (o Google já entrega o Android, por baixo do OneSignal). Contra: o
maior trabalho no servidor (token morto, renovação, plataforma); sem painel de campanha ligado ao ERP;
estatística fraca (o "recebido" só no Android, com até 24 h de atraso); o aplicativo precisa de versão nova.

**B. Novu na nuvem.** A favor: o Novu guarda os aparelhos e limpa o token FCM inválido; o registro de cada envio;
textos editáveis no painel sem deploy; prepara caixa de entrada, e-mail e preferências por canal; trocar o
provedor depois não mexe no ERP. Contra: não dispensa o FCM nem o aplicativo novo; mais um fornecedor, com
mensalidade que cresce com o volume; os dados saem do país, e o contrato de dados usa as cláusulas da UE, não as
cláusulas-padrão da ANPD exigidas desde 23/08/2025; o WhatsApp da rede (Evolution/WAHA) não tem provedor nativo
(só o passo HTTP); economiza de 10 a 20 h de servidor em relação à A.

**C. Novu auto-hospedado.** A favor: os dados ficam conosco; sem cobrança por volume; o núcleo é MIT. Contra:
Node, MongoDB e Redis para um time de PHP; a edição comunitária tem um usuário, sem MFA, SSO nem RBAC; versões
quase todo mês, e a passagem da v0 para a v2 refez os fluxos à mão; mais um serviço no plantão.

**D1. Novu na frente do OneSignal.** Pode dispensar o aplicativo novo, se ele já grava o `external_id` (não se
sabe). Paga dois fornecedores e não resolve custo, controle nem LGPD; o provedor OneSignal do Novu manda o
cabeçalho `Basic`, e não se sabe se a chave nova o aceita.

**D2. Ficar no OneSignal.** O menor esforço, sem aplicativo novo; o painel e as estatísticas seguem. Continua
pagando por usuário ativo, com os aparelhos e os dados fora do ERP e do país. Faz o que o D3 e o D67 pedem: a chave
nova (que já leva à API nova, `api.onesignal.com` com o cabeçalho `Key` -- e esse nome entra no loopback dos
compose e do `HOSTS_MORTOS`), tempo limite, SSL ligado e a falha registrada.

## A recomendação de 09/10/2026

- **A D2 primeiro, se o dono descongelar o OneSignal:** ela não é alternativa às outras, é o passo zero de
  qualquer troca, porque durante a convivência o OneSignal tem de continuar entregando.
- **Se a troca for por custo ou controle: a A**, atrás de uma interface de envio (uma implementação OneSignal e
  uma FCM convivendo), que deixa o Novu entrar depois como outra implementação.
- **A B só se o dono quiser em breve** e-mail, caixa de entrada no aplicativo, resumos ou preferências por canal.
  **A C não**, para este time. **A D1 não.**

## O que só o dono responde

1. O que motiva a troca: custo, LGPD, controle dos dados ou outros canais?
2. Quem mantém o aplicativo dos clientes, em que ele é feito, e quanto custa uma versão nova nas lojas?
3. Qual é o plano do OneSignal e quanto custa? (O MAU e a divisão iOS/Android saem de uma exportação do
   painel, que traz tokens e ids de cliente e pede tratamento de dado pessoal.)
4. Quem é dono do projeto Firebase que o OneSignal usa hoje, e da conta Apple Developer com a chave `.p8`?
5. Alguém manda push direto pelo painel do OneSignal, fora do ERP?

## A medição de 10/10/2026

Pedida pelo dono ("Faça a medição"). Só contagens, por `SELECT` na cópia `franquiabv_dev` (a data mais recente da
cópia é 16/08/2026), sem dado pessoal (as consultas ficaram no rascunho da sessão, fora do repositório).

**O volume das campanhas de push, por mês** (a data da campanha; pela data de cada envio dá o mesmo):

| Mês | Campanhas de push | Envios |
|---|---:|---:|
| fev/2026 | 4 | 1.553 |
| mar/2026 | 6 | 10.332 |
| abr/2026 | 4 | 5.112 |
| mai/2026 | 8 | 16.199 |
| jun/2026 | 7 | 9.136 |
| jul/2026 | 13 | 16.480 |
| **01/02 a 01/08/2026** | **42** | **58.812** (média de 9.802 por mês) |

A maior campanha de push mandou 6.220 envios (a maior lista de destinatários, de qualquer tipo, é a de 6.573 do D67).

**As rotinas automáticas.** O push automático sai quando a unidade *não* tem o WhatsApp ligado *com* o texto de
WhatsApp daquela rotina, o cliente **já abriu o aplicativo alguma vez** e a unidade tem o texto de aplicativo da rotina
(`CronJobService`). A regra do código não tem janela de tempo: é `!empty(ultimoAcessoApp)`, e nenhuma das consultas
(`selecionarAniversariantes`, `selecionarClientesNovos`, `selecionarPorDataUltimaVisita`, a das vendas do NPS) olha o
último acesso. Por isso são **duas réguas**, até 16/08/2026 (a data da cópia), contadas pela regra de cada rotina:

- **pela regra do código** (quem já abriu o aplicativo alguma vez): o **teto** do que pode sair ao provedor;
- **com o aplicativo aberto nos 30 dias**: a **estimativa** de quem o tem em uso.

*(A primeira versão desta seção, de 10/10/2026, contou só a segunda régua e a chamou de teto -- "7.733 por mês", a
soma do maior mês do aniversário e do cadastro com **todos** os alcançáveis do "sentimos" e do NPS, estes dobrados.
A verificação do mesmo dia achou o erro, e as contagens foram refeitas.)*

| Rotina | A unidade que vale, e quando sai | Pela regra: clientes (unidades) | Pela regra: o maior mês | Nos 30 dias: clientes (unidades) | Nos 30 dias: o maior mês |
|---|---|---:|---:|---:|---:|
| `notificarAniversario` | a do cadastro; uma vez por ano, no dia | 23.186 (30) | 1.991 (aniversariantes) | 2.823 (21) | 265 |
| `clientesNotificarPrimeiraVisita` | a do cadastro; uma vez, no dia seguinte ao cadastro | 21.261 (27) | 228 (clientes novos, fev a jul) | 2.671 (18) | 104 |
| `clientesNotificarSentimosSuaFalta` | a da última visita; 30 dias depois dela | 21.464 (29) | 2.141 (últimas visitas, fev a jul) | 2.858 (14) | 1.053 |
| `npsNotificacoes` | a da última visita; por venda, no máximo uma a cada 21 dias | 18.300 (27) | 2.667 (clientes com venda, fev a jul; até o dobro) | 2.191 (12) | 1.636 (até o dobro) |
| **as quatro** | | | **7.027** (até 9.694) | | **3.058** (até 4.694) |

**A população pela regra é de 7 a 8 vezes a dos 30 dias** (23.186 contra 2.823 no aniversário), e é ela que o código
pode mandar ao provedor: o cliente que abriu o aplicativo há anos e o desinstalou ainda gera o pedido (o OneSignal
responde "não inscrito"; o FCM, sem o aparelho no cadastro, nem é chamado). **Por mês**, porém, as rotinas saem por
evento -- o aniversário, o cadastro, a última visita, a venda --, e o teto é o do maior mês de eventos: **7.027 push
por mês pela regra**, até 9.694 se cada cliente do NPS tiver duas vendas a 21 dias uma da outra. O NPS não olha se o
cliente está ativo; as outras três olham.

**Isto corrige a comparação de 09/10**, que dizia "só aos clientes das 8 unidades ativas sem WhatsApp": o push também
sai na unidade com o WhatsApp ligado e **sem o texto de WhatsApp da rotina**, e a regra não olha se a unidade está
ativa. No aniversário, na régua dos 30 dias, os 2.823 são 2.095 de 4 unidades ativas com WhatsApp e sem o texto, 569
de 4 ativas sem WhatsApp e 159 de 13 inativas sem WhatsApp. (Uma unidade tem duas linhas de mensageria, e o `JOIN`
pode contar o cliente dela duas vezes; e três das rotinas guardam um cliente por telefone, o que a contagem não faz:
os números são teto.) Os produtores por evento -- a fidelidade e o cupom no pagamento, o sorteio, o código e o resgate
da fidelidade -- mandam um push por evento, pela mesma regra, e não entram na conta.

**O total**, as campanhas mais as automáticas pela regra: cerca de 16,8 mil a 19,5 mil push por mês na média, e até
23,5 mil a 26,2 mil num mês como julho. É volume de mensagens, e **não** o que o OneSignal cobra.

**O custo.** O OneSignal cobra por usuário ativo por mês (MAU), e não por mensagem. Pela tabela publicada em
https://onesignal.com/pricing (lida em 10/10/2026): o Free vale "For organizations with up to 1,000 monthly active
users"; o Growth "Starts at $19/mo", com "$0.012 per monthly active user" no push, e "OneSignal's paid plans have a
fixed platform fee and variable usage fee"; o Professional é sob consulta, com "Volume discounts available". O MAU é
um registro de inscrição do aplicativo ativo nos últimos 30 dias, **tenha ele aceitado o push ou não**. O FCM, por
https://firebase.google.com/pricing (lida em 10/10/2026), é "No-cost" nos dois planos (Spark e Blaze).

| Usuários ativos por mês (MAU) | OneSignal Growth, por mês | por ano | FCM |
|---:|---:|---:|---:|
| 6.156 | US$ 92,87 | US$ 1.114 | US$ 0 |
| 10.000 | US$ 139,00 | US$ 1.668 | US$ 0 |
| 20.000 | US$ 259,00 | US$ 3.108 | US$ 0 |
| 50.000 | US$ 619,00 | US$ 7.428 | US$ 0 |
| 100.000 | US$ 1.219,00 | US$ 14.628 | US$ 0 |

A conta é US$ 19 + MAU × US$ 0,012, sem desconto de volume (que só o Professional promete, sob consulta) e sem
câmbio. **O MAU de verdade só o painel do OneSignal dá** (ou a exportação com a chave nova, que traz dado pessoal):
os 6.156 são os clientes que abriram o aplicativo nos 30 dias até a última data da cópia, um piso -- o cliente com
dois aparelhos conta dois, e quem recusou o push conta também. (Pela data, e não pela hora, do último acesso, dão
6.287.) Não se sabe o plano contratado, nem se há preço antigo mantido.

**O ritmo do envio, contra um FCM de mentira** (o `php -S` no contêiner, no 127.0.0.1, com 16 workers; HTTP sem TLS),
6.573 destinatários com um aparelho cada, medido em 10/10/2026:

| O FCM de mentira | Em série (o código, como o OneSignal de hoje) | `curl_multi`, 10 no ar | `curl_multi`, 50 no ar |
|---|---:|---:|---:|
| responde na hora | 43,3 s e 40,4 s (6 a 7 ms por envio) | 12,2 s | 4,5 s |
| responde em 50 ms | 363,9 s (55 ms por envio) | 42,9 s | 21,3 s (o teto dos 16 workers do servidor de mentira) |

**Não são números de produção**: a latência até o Google, a partir do servidor, não foi medida, e o TLS não entrou.
O que a tabela diz é a forma: em série, o tempo é o número de destinatários vezes a latência de cada pedido (mais uns
6 ms daqui), e a campanha de 6.573 segura um worker do pool de 5 por 6 minutos a cada 50 ms de latência. O código
manda em série, como o OneSignal já mandava, e reaproveita a conexão entre os pedidos; o `curl_multi` foi medido como
protótipo, e fica para a segunda parte se a latência real pedir.

**E numa queda do FCM** (o 429 ou o 5xx que continua), a retentativa segue a página de escala do Google
(https://firebase.google.com/docs/cloud-messaging/scale-fcm, "Handling retries", lida em 10/10/2026): o `Retry-After`
quando vem, senão 60 s no 429 e 10, 20 e 40 s no 5xx, nunca menos de 10 s, e um sorteio de até 20% a mais. Um
destinatário só gasta essas esperas: o seguinte já encontra o **disjuntor** aberto e sai como falha sem pedido, até
passar o tempo que o FCM pediu (no mínimo um minuto), e a rodada não espera mais de 300 s somados. A primeira versão
(1, 2 e 4 s por destinatário, sem estado entre um e outro) prendia o worker por cerca de 12,8 h numa queda, para os
6.573; e no 429 sem `Retry-After` repetia dentro do mesmo minuto da cota. O push que o disjuntor não tentou se perde,
como o que falha hoje no OneSignal (guardá-lo para a rodada seguinte é da segunda parte, D461).

## A porta do Novu (a opção C), sem código (10/10/2026)

O dono pediu o caminho aberto para o Novu instalado por nós, "se fizer sentido no futuro". **Nada do Novu foi
escrito**; esta seção diz onde ele entraria, e o que mudaria.

**Onde entra: uma terceira implementação de `ISBase\Push\EnviadorDePush`**, um `NovuEnviador` ao lado do
`OneSignalEnviador` e do `FcmEnviador`, falando pela mesma `ClienteHttp` (o cURL com tempo limite e SSL do D67; o
SDK PHP do Novu pede PHP 8.2 e é beta, a comparação acima). E um quarto valor de `push.provedor`, `novu`, que a
`FabricaDoPush` monta com o endereço da instalação e a chave do ambiente do Novu (`push.novu.url`,
`push.novu.chave`, no `local.php`, fora do repositório).

**O que o `NovuEnviador` faz, pela API do Novu** (https://docs.novu.co, lida em 10/10/2026):

- **O assinante é o cliente.** O `subscriberId` do Novu é o id do cliente, em texto — o mesmo valor que o OneSignal
  recebe na marcação `usuario` (`DestinatarioDoPush::$clienteId`).
- **O envio** é `POST /v1/events/trigger`, com o cabeçalho `Authorization: ApiKey <chave>`, o `name` do fluxo
  (um fluxo de push criado no painel do Novu), o `to` com o `subscriberId` e o `payload` com `titulo`, `texto` e
  `link`; o fluxo põe o título e o texto na notificação e o link no `data`, como o `FcmEnviador` faz hoje (até 100
  destinatários por chamada; https://docs.novu.co/api-reference/events/trigger-event).
- **As credenciais do aparelho** ficam no assinante: `PUT /v1/subscribers/{subscriberId}/credentials`, com
  `providerId: fcm` e `credentials.deviceTokens` — e essa chamada **substitui** a lista inteira
  (https://docs.novu.co/api-reference/subscribers/update-provider-credentials). Quem a chama é o ERP: a rota
  `POST /api/v4/clientes/:id/aparelhos` continua a mesma, e o registro (ou um passo depois dele) manda ao Novu a
  lista do cliente lida de `clientes_aparelhos`. O aplicativo não muda.
- **A entrega continua pelo FCM**, com a conta de serviço do mesmo projeto Firebase carregada na integração FCM do
  Novu. E o Novu tira sozinho do assinante o token que o FCM recusa ("Novu automatically removes invalid, stale or
  expired device tokens", só para o FCM e o Expo; https://docs.novu.co/platform/integrations/push).

**O que muda:** o `DespachanteDePush` passa a mandar tudo ao Novu no modo `novu`, sem consultar o cadastro na hora
do envio (o Novu guarda os aparelhos) -- e por isso a regra do telefone (o aparelho só vale enquanto o telefone do
cliente for o da sessão que o registrou, `ClienteAparelhoRepository::aparelhosDosClientes`) passa a pedir que o ERP
refaça a lista do assinante no Novu quando o telefone do cliente mudar, e no logout; a retirada dos mortos sai do ERP e vai para o Novu (o `message.failed` do
webhook dele diz quais); o registro de cada envio e a reentrega passam a morar no Novu; e entra a infraestrutura da
opção C (a máquina, o MongoDB, o Redis e o S3, com cópia e monitoramento; a tabela de opções acima).

**O que fica:** a interface `EnviadorDePush` e quem a chama (`marketingPush`, as campanhas); o `push.provedor` e a
convivência pela configuração; a tabela `clientes_aparelhos` como a lista de verdade dos aparelhos (contar, auditar
e apagar pela LGPD continua no nosso banco, e o Novu é cópia dela); a rota do aparelho e o login V4; o aplicativo.

**O que não se sabe**, e decide se faz sentido: se o volume justifica uma segunda base para operar (os 16,8 a 26,2
mil push por mês medidos acima cabem no FCM direto), e se alguém vai querer o que o Novu traz além do push — a
caixa de entrada no aplicativo, o e-mail, as preferências por canal. Sem isso, o Novu é mais um serviço no plantão
para fazer o que o `FcmEnviador` já faz.

## Fontes (lidas em 09/10/2026)

- Código: `module/ISBase/src/Util/OneSignal.php`; `module/ISApi/src/Service/CronJobService.php` (`marketingPush`,
  `marketingEnvios`); `module/ISMarketing/src/Service/NotificacaoService.php`; `module/ISMarketing/src/Form/NotificacaoEnvio.php`;
  `module/ISApi/src/Controller/V4/SessoesController.php`; `composer.json`; `scripts/conferir-provedores-no-loopback.php`.
- Registro: D3, D5, D11, D59, D67, D456, D457 em `docs/debitos-tecnicos.md`.
- OneSignal: https://onesignal.com/pricing ; https://documentation.onesignal.com/docs/keys-and-ids ;
  https://documentation.onesignal.com/reference/csv-export
- Firebase: https://firebase.google.com/docs/cloud-messaging/send/v1-api ;
  https://firebase.google.com/docs/cloud-messaging/manage-tokens ;
  https://firebase.google.com/docs/cloud-messaging/understand-delivery ; https://firebase.google.com/pricing
- Lojas: https://developer.apple.com/news/upcoming-requirements/ ;
  https://developer.android.com/google/play/requirements/target-sdk ;
  https://support.google.com/googleplay/android-developer/answer/9859751
- Novu: https://github.com/novuhq/novu ; https://docs.novu.co/platform/integrations/push ;
  https://docs.novu.co/community/self-hosting-novu/overview ; https://novu.co/pricing ; https://novu.co/dpa
- **Lidas de novo em 10/10/2026**, para a decisão e a medição: https://onesignal.com/pricing ;
  https://firebase.google.com/pricing ; https://documentation.onesignal.com/reference/push-notification (a API nova
  aceita o mesmo corpo, o filtro por marcação inclusive) ; https://firebase.google.com/docs/cloud-messaging/manage-tokens
  (o UNREGISTERED e o INVALID_ARGUMENT, e o aviso de que o segundo só vale com a mensagem válida) ;
  https://firebase.google.com/docs/cloud-messaging/scale-fcm (as esperas do 429 e do 5xx, o mínimo de 10 s, o sorteio,
  e o "abort, and do not retry" do 400, 401, 403 e 404; atualizada em 07/10/2026) ;
  https://firebase.google.com/docs/cloud-messaging/error-codes (o THIRD_PARTY_AUTH_ERROR é HTTP 401, a credencial da
  APNs ou do web push; o QUOTA_EXCEEDED pede espera inicial de um minuto). O código da primeira parte:
  `module/ISBase/src/Push/`; os casos: `tests/Caracterizacao/EnvioDePushTest.php` e
  `tests/Caracterizacao/PushNasRotinasTest.php`.
