-
Notifications
You must be signed in to change notification settings - Fork 580
Expand file tree
/
Copy path.env.example
More file actions
405 lines (369 loc) · 22.1 KB
/
Copy path.env.example
File metadata and controls
405 lines (369 loc) · 22.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
# =============================================================================
# DeskcommCRM — variáveis de ambiente
# =============================================================================
# Copie para .env.local e preencha. NUNCA commitar .env*.
# Variáveis NEXT_PUBLIC_* são expostas ao browser; o resto fica server-side.
# =============================================================================
# --- Supabase ----------------------------------------------------------------
# URL pública do projeto Supabase (ex: https://xxxx.supabase.co)
NEXT_PUBLIC_SUPABASE_URL=
# anon key (safe pra browser; RLS é o gatekeeper real)
NEXT_PUBLIC_SUPABASE_ANON_KEY=
# service role key — BYPASSA RLS. Apenas server. Nunca commitar. Rotação trimestral.
SUPABASE_SERVICE_ROLE_KEY=
# --- Cron / interno ----------------------------------------------------------
# Bearer secret pros endpoints /api/v1/cron/*. DIFERENTE do service role key.
INTERNAL_SECRET=
# Secret dedicado opcional pros endpoints de cron. Se vazio, cai pra INTERNAL_SECRET.
INTERNAL_CRON_SECRET=
# Relógio local (`pnpm dev:crons`). Só bate em NEXT_PUBLIC_APP_URL; não instala
# crontab. NÃO rode contra o mesmo Supabase da VPS — disputa a fila com o prod.
# DEV_CRON_INTERVAL_MS=15000
# --- Janela do retorno agendado ----------------------------------------------
# Quão cedo e quão tarde o agente pode marcar um retorno ao cliente. Lidas pelo
# WORKER (agent-engine) e pelo APP (a capacidade que o dono liga na tela) — as
# duas leituras têm de bater, senão o mesmo horário seria aceito num caminho e
# recusado no outro. Defaults em lib/followup/janela.ts; deixe vazio para usá-los.
# FOLLOWUP_MIN_AHEAD_MS=300000 # 5 min
# FOLLOWUP_MAX_AHEAD_MS=15552000000 # 180 dias
# CRON_STAGGER_WINDOW_MS=60000 # espalha disparos do mesmo minuto (0 desliga)
# --- Encryption keys (pgcrypto) ----------------------------------------------
# Chave separada pra criptografar CPF em contacts (PII sensível LGPD).
CPF_ENCRYPTION_KEY=
# Chave pra criptografar tokens OAuth Nuvemshop em tenant_integrations.
# Agenda · Google Calendar (BYO) — opcional.
# Sem as duas, a Agenda funciona inteira: some o botão "Conectar Google".
# Onde obter: console.cloud.google.com > APIs e Serviços > Credenciais >
# ID do cliente OAuth (tipo "Aplicativo da Web").
#
# ⚠️ REGISTRE TAMBÉM O ENDEREÇO DE RETORNO, em "URIs de redirecionamento
# autorizados": a URL do app MAIS `/api/v1/agenda/google/callback`. O Google
# compara byte a byte, e registrar só a origem é o palpite natural — ele recusa
# com `redirect_uri_mismatch`, que aponta para o Google e não para a divergência.
GOOGLE_CALENDAR_CLIENT_ID=
GOOGLE_CALENDAR_CLIENT_SECRET=
NUVEMSHOP_OAUTH_ENCRYPTION_KEY=
# Chave pra criptografar credenciais BYO-WAHA (cliente que roda WAHA próprio).
WAHA_BYO_ENCRYPTION_KEY=
# Chave AES-256 (32 bytes em base64) pra criptografar API keys de provedores LLM
# (ai_provider_credentials). Gerar: openssl rand -base64 32
AI_CRED_AES_KEY=
# --- WAHA --------------------------------------------------------------------
# URL base da instância WAHA (ex: http://localhost:3000 em dev; https://waha.deskcomm.com em prod)
WAHA_API_BASE_URL=http://localhost:3000
# Plaintext da API key do WAHA. O ENV do container WAHA recebe o hash SHA512 hex desta.
# Gerar: echo -n "minha-chave-aqui" | shasum -a 512
WAHA_API_KEY=
# Segredo com que o WAHA assina os webhooks (header X-Webhook-Hmac). O app usa
# para CONFERIR a assinatura; o container do WAHA recebe o mesmo valor.
WAHA_HMAC_SECRET=
# ─── Chamada de voz WhatsApp (WaCalls, spec 18) — DESLIGADA POR PADRÃO ───────
#
# Ligá-la vincula um SEGUNDO APARELHO ao mesmo número que já atende, por um
# caminho que não é o oficial: o risco é a CONTA ser bloqueada pelo WhatsApp,
# não só o aparelho. Por isso nada aqui vem preenchido, e cada organização
# ainda precisa aceitar o risco na tela (Configurações › Segurança).
#
# Em produção o serviço vive num profile do compose que nasce desligado; ligar
# é `COMPOSE_PROFILES=voz` no .env. Vazio aqui = a feature não aparece na tela
# (banner, nunca erro).
WACALLS_API_BASE_URL=
# Bearer que o app usa para falar com o serviço. O WaCalls autenticado NÃO tem
# modo aberto: sem token, a API só aceita o cookie de login do navegador, que um
# processo server-to-server não tem. URL sem token = 401 em toda chamada.
WACALLS_API_TOKEN=
# O contêiner precisa de DUAS coisas para o áudio sair da rede local — e sem
# elas a ligação toca, conecta e fica MUDA na VPS (o WaCalls anuncia como
# candidato o IP interno do contêiner, 172.x, que navegador nenhum alcança):
# WACALLS_PUBLIC_IP o IP público da VPS, anunciado como candidato ICE
# WACALLS_WEBRTC_UDP_PORT a porta UDP fixa (sem ela, portas efêmeras
# aleatórias, impossíveis de publicar no compose)
# As duas são do CONTÊINER, não do app — por isso vivem aqui e não em lib/env.ts.
WACALLS_PUBLIC_IP=
WACALLS_WEBRTC_UDP_PORT=7881
# "true" exige assinatura válida em TODO webhook do WAHA. Desligado por padrão
# porque o WAHA Core não assina — exigir derrubaria a ingestão de mensagens.
# Ligue se roda WAHA Plus (ou um proxy que assine).
WAHA_WEBHOOK_REQUIRE_SIGNATURE=false
# URL pública do app que o WAHA chama em webhooks (precisa ser HTTPS público; use ngrok em dev).
WAHA_WEBHOOK_BASE_URL=
# Retomar as sessões já pareadas quando o contêiner do WAHA reinicia. É lido
# pelo WAHA, não pelo app (por isso não está em lib/env.ts). O default do WAHA é
# False: o volume guarda a credencial, mas a sessão não sobe, e o número fica
# "conectado" no disco e mudo na prática até alguém abrir o front e clicar
# Reconectar. Só mexa aqui se quiser o comportamento antigo de propósito.
WHATSAPP_RESTART_ALL_SESSIONS=True
# --- OpenRouter: atribuição OPCIONAL ----------------------------------------
# `HTTP-Referer` e `X-Title` que a OpenRouter usa para atribuir o consumo e
# montar o ranking público do site dela. São OPCIONAIS — a doc deles diz
# "optional headers to identify your app", e a chamada funciona sem. Vazios,
# nenhum header é enviado. Preencha com o SEU domínio e o SEU nome se quiser
# que o consumo desta instalação apareça atribuído a você.
OPENROUTER_APP_URL=
OPENROUTER_APP_TITLE=
# --- Upstash Redis (rate limit, idempotency) ---------------------------------
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
# --- AI providers ------------------------------------------------------------
# Vercel AI Gateway (preferencial — fallback de provedor + observability + ZDR)
# AI_GATEWAY_API_KEY: chave do gateway. Quando ausente, ai-response-worker
# pula com skip="ai_gateway_key_missing" (não bloqueia boot do app).
AI_GATEWAY_API_KEY=
AI_GATEWAY_BASE_URL=
VERCEL_AI_GATEWAY_URL=
# Fallback direto Anthropic (caso AI Gateway indisponível ou desabilitado pra um tenant)
ANTHROPIC_API_KEY=
# OpenRouter (OPCIONAL) — alternativa ao gateway da Vercel, compatível com a API
# da OpenAI, com catálogo grande de modelos num único faturamento.
# Ordem de resolução do chat: AI_GATEWAY_API_KEY > OPENROUTER_API_KEY > provider
# direto (ANTHROPIC_API_KEY / OPENAI_API_KEY). Vazio = nada muda.
#
# ALCANCE — leia antes de trocar. Esta chave alcança a classificação de
# sentimento, o bot de resposta antigo E, desde que a organização esteja em
# OpenRouter, o AGENTE do CRM: quando não há credencial cadastrada na tela, o
# agente cai nesta chave como credencial de plataforma. É o caso normal de quem
# instalou pelo kit escolhendo OpenRouter e nunca abriu a tela de Provedores.
#
# O que ela NÃO faz é ESCOLHER o provedor. Quem escolhe é a instalação
# (`AI_PROVIDER`, gravado em settings.llm.provider) ou a tela Agente de IA →
# Provedores. Preencher a chave aqui com a organização em Anthropic não migra
# nada; migrar é trocar o provedor, e aí esta chave passa a ser a que paga.
#
# ATENÇÃO ao escolher o modelo do agente na tela: ele opera por FERRAMENTAS
# (contatos, leads, funil, catálogo, transferência para humano). Modelo sem
# suporte a ferramentas NÃO dá erro — responde um texto plausível ao cliente e
# nunca cria o lead nem move o card. Por isso o painel RECUSA salvar um modelo
# sem ferramentas nos pontos "Responder o cliente" e "Trabalhar o funil", usando
# o que o próprio fabricante declara no catálogo.
# O invariante que segura estas afirmações: tests/unit/openrouter-alcance.test.ts
OPENROUTER_API_KEY=
OPENROUTER_BASE_URL= # vazio = https://openrouter.ai/api/v1
# Pra embeddings (text-embedding-3-small) usados no RAG por tenant e pra transcrever áudio.
# OPCIONAL: é o ÚLTIMO degrau. Quem preferir pode cadastrar a chave PELA TELA, em
# IA › Credenciais (ou no próprio passo de cadastrar material) — vale por organização,
# fica cifrada no banco e dispensa mexer neste arquivo.
OPENAI_API_KEY=
# --- Teto de gasto de IA: a alavanca de emergência ----------------------------
# Quem escolhe o teto é cada organização, na tela Uso de IA › Orçamento (teto,
# aviso, e se a IA deve PARAR ao chegar nele). Esta chave não escolhe nada: ela
# só sabe AFROUXAR o que já foi escolhido.
# on (default) respeite o que cada organização escolheu
# avisar rebaixa qualquer "parar a IA" para apenas avisar
# off desliga a proteção nesta instalação inteira
# Também valem, como off: false, 0, no, nao, não, disabled. Valor não
# reconhecido cai em `on` — de propósito: a alavanca de emergência NUNCA pode
# derrubar o app por um typo. Mudou? reinicie o app e o worker.
AI_BUDGET_ENFORCEMENT=on
# --- Workers -----------------------------------------------------------------
# Ritmo com que o WORKER roda os handlers do event_log (mídia, branding,
# follow-up…). Antes destes knobs os handlers só rodavam pelo cron
# `event-log-drain`, 1×/min — e a cadeia persist→derive de um áudio levava
# 103-188s contra os 45s que o turno espera pela transcrição. O cron continua
# ligado como rede de segurança. Vazio = os defaults abaixo.
EVENT_LOG_DRAIN_INTERVAL_MS=2000
EVENT_LOG_DRAIN_IDLE_INTERVAL_MS=10000
EVENT_LOG_DRAIN_BATCH_SIZE=50
# --- Elegibilidade da IA por origem do lead ----------------------------------
# Vale SÓ nos canais em que você ligou o modo "só atende quem eu autorizei"
# (Configurações do canal › ai_gate = allowlist). Nesses canais, um contato fica
# elegível para a IA quando vem de uma origem conhecida (formulário do Respondi,
# campanha, liberação manual). Este número é por quantos DIAS essa autorização
# vale — depois disso, uma submissão antiga não reativa a IA sozinha. Uma
# conversa que segue viva renova o prazo a cada turno. Vazio = 21 dias.
AI_ALLOWLIST_TTL_DAYS=21
# Stub do endpoint de teste do agente: 'true' devolve trace fabricado em vez de
# executar o agente. Deixe false — o runtime real já existe. Só ligue para
# exercitar o render da UI sem gastar token.
INTERNAL_AGENT_RUN_STUB=false
# --- Sentry ------------------------------------------------------------------
SENTRY_DSN=
# --- E-mail transacional (Resend) ---------------------------------------------
# Convite de time, entrega de export LGPD e alarme de SLA saem por aqui.
#
# RESEND_FROM_EMAIL precisa ser um endereço de um domínio VERIFICADO na SUA
# conta Resend. Vazio = e-mail desligado (o convite mostra o link de aceite na
# tela e o export de LGPD fica em `pending_review`), e isso é de propósito:
# herdar um domínio que não é seu faz todo envio falhar lá na Resend, com uma
# mensagem que não diz que o problema é configuração.
RESEND_API_KEY=
RESEND_FROM_EMAIL=
# --- Impersonate / suporte ----------------------------------------------------
# Segredo HMAC do cookie de impersonate. Mínimo 32 chars quando a feature é usada.
IMPERSONATE_COOKIE_SECRET=
# Endereço de suporte mostrado ao CLIENTE FINAL (conta suspensa, cobrança).
# Quem instala para terceiros põe o próprio endereço aqui. Vazio = a tela não
# mostra endereço nenhum, e nunca cai num endereço do produto.
SUPPORT_EMAIL=
# --- LGPD ---------------------------------------------------------------------
# Chave usada para assinar exports de dados LGPD.
LGPD_SIGNING_KEY=
# E-mail do Encarregado (DPO). Impresso no rodapé do relatório entregue ao
# titular (`lib/lgpd/pdf-renderer.tsx`) quando a organização não tem
# `dpo_email` própria, e é o destinatário do alarme de SLA.
LGPD_DPO_EMAIL=
# Prazo, em horas, antes de um export LGPD expirar.
LGPD_EXPORT_EXPIRES_HOURS=72
# --- Nuvemshop OAuth ---------------------------------------------------------
# Obtenha em https://partners.tiendanube.com/ (criar app → copiar credenciais).
# Deixe vazio em dev/staging se ainda não tiver app aprovado — a integração
# degrada graciosamente e a UI mostra "Integração não configurada".
# Liga a integração Nuvemshop. Quando true, exige as credenciais abaixo.
NUVEMSHOP_ENABLED=false
# APP_ID aparece na URL do painel do app no portal de parceiros.
NUVEMSHOP_APP_ID=
NUVEMSHOP_CLIENT_ID=
NUVEMSHOP_CLIENT_SECRET=
# Para testar webhooks em dev exponha o app via ngrok/cloudflared (HTTPS) e
# aponte NEXT_PUBLIC_APP_URL para o túnel antes de clicar "Conectar".
# --- App URLs ----------------------------------------------------------------
# URL canônica do app (tenant). Ex: https://app.deskcomm.com
NEXT_PUBLIC_APP_URL=http://localhost:3000
# URL canônica do super-admin. Ex: https://admin.deskcomm.com
NEXT_PUBLIC_ADMIN_URL=http://localhost:3000
# =============================================================================
# AGENT ENGINE (fusão Vendaval) — worker 24/7 do agente SDR
# =============================================================================
# Connection string do Postgres do Supabase (Settings → Database). O worker usa
# Postgres direto (fila FOR UPDATE SKIP LOCKED, locks, FTS). Recomendado: role
# dedicada com login (ex.: agent_worker) — nunca a service_role key aqui.
SUPABASE_DB_URL=
# A conexão que MEXE NO SCHEMA — e SÓ o kit (install.sh/update.sh/backup.sh) a
# usa; nenhum código do app lê esta chave. OPCIONAL: vazia, tudo roda pela de
# cima, que é o caso da nuvem (a string do pooler já vem privilegiada).
# Preencha quando o banco for um Supabase PRÓPRIO e a de cima for a role menor:
# `create extension`, o `baseline.sql` e a promoção do dono exigem o dono do
# banco. Ver docs/deploy-selfhost/README.md §2.
SUPABASE_DB_ADMIN_URL=
# --- Ritmo da fila do worker (custo de banco) --------------------------------
# Os dois governam quanto o worker conversa com o Postgres quando NÃO há trabalho
# — a conta que estourou a cota de egress do plano free do Supabase na issue #258.
# Ambos são opcionais: sem eles valem os defaults abaixo, que são o comportamento
# recomendado. Como ver o que está em vigor: a linha `agent-engine pronto` no log
# do worker imprime os dois. Quando mexer e o que medir:
# docs/runbooks/custo-e-cota-do-supabase.md
#
# TETO de espera do laço da fila. Com a fila vazia, é quanto o worker dorme entre
# uma consulta e a próxima; com job agendado, ele acorda no vencimento e este
# valor só limita a soneca. Não passe de 10000: acima disso a conexão ociosa
# expira entre as rodadas e cada consulta volta a pagar TCP+TLS — gasta MAIS.
#QUEUE_POLL_INTERVAL_MS=2000
# Ritmo do "havia trabalho e eu não peguei" (todas as vagas de
# QUEUE_MAX_CONCURRENCY ocupadas, ou outro turno rodando para o mesmo contato).
# Aqui há job vencido esperando vaga, então este valor é curto de propósito:
# aumentá-lo não economiza nada no ocioso e custa throughput no pico.
#QUEUE_CLAIM_RETRY_INTERVAL_MS=250
# ── Retenção do histórico (poda diária, cron `data-retention`) ───────────────
# Espaço em disco é uma cota diferente da de tráfego: o plano free do Supabase
# limita 500 MB de BANCO, e `job_queue` + `api_audit_log` são as duas que crescem
# sozinhas. Os dois valores abaixo JÁ SÃO os defaults do código: apagar as linhas
# de um `.env` existente não muda nada, e quem atualiza sem tocar no arquivo
# recebe exatamente este comportamento. Quanto cada tabela ocupa hoje e o que
# fazer com o número: docs/runbooks/custo-e-cota-do-supabase.md
#
# Idade a partir da qual um job TERMINAL (done/failed/dead) é apagado. Job
# `pending`/`running` NUNCA é tocado — tem trabalho dentro. Apagar um job leva
# junto o `send_ledger` e o `before_send_traces` daquele run. Piso de 7 dias,
# aplicado dentro da função do banco (valor menor é elevado, com aviso no log).
JOB_QUEUE_RETENTION_DAYS=90
# Idade a partir da qual uma linha de auditoria é expurgada. O default é a
# retenção de 5 anos da regra L-10. Piso de 90 dias: nem com a chave de serviço
# esta poda alcança rastro recente. Diminuir aqui é a alavanca de quem está
# apertado de espaço e aceita guardar menos histórico.
AUDIT_LOG_RETENTION_DAYS=1825
# Chave LLM de plataforma (fallback quando a org não tem credencial BYOK
# cadastrada em /app/ai/credentials). Opcional com BYOK.
ANTHROPIC_API_KEY=
# Dono ÚNICO dos eventos de despacho do agente. 'engine' (default) = o worker
# consome; 'native' = o dispatcher interno do app consome (deploy sem worker).
# NUNCA os dois — dois consumidores = turno duplicado ou perdido.
AGENT_DISPATCH_CONSUMER=engine
# Watchdog de sessão: reconcilia channel_sessions × WAHA e reenvia mensagens
# presas em queued. Usa WAHA_API_BASE_URL/WAHA_API_KEY acima (no compose o
# worker já recebe http://waha:3000). Knobs opcionais:
#WATCHDOG_INTERVAL_MS=60000
#WATCHDOG_REDRIVE_BATCH_SIZE=10
# Flywheel agendado (judge → proposta de melhoria; promoção SEMPRE manual na
# tela de Agentes IA). 0 = desligado; default 6h.
#FLYWHEEL_INTERVAL_MS=21600000
#FLYWHEEL_BATCH_LIMIT=10
# ── Marca da instalação (white-label) ───────────────────────────────────────
# Quem instala o CRM para clientes (agência, revendedor) troca a marca aqui, sem
# editar código — patch em código se perde no próximo update. Vazio = padrão.
# APP_LOGO_URL aceita qualquer URL pública de imagem; sem ela, o nome aparece
# como texto. Ver lib/branding.ts.
APP_NAME=
APP_LOGO_URL=
# Cor da marca, em hex (ex.: #506d48). Dela sai a rampa de 11 tons que pinta o
# produto inteiro — botão, anel de foco, chip, hover — nos dois temas. Vazio = a
# cor do produto. Cor sem contraste suficiente é DESLOCADA em vez de recusada, e
# marca acromática (cinza/preto/branco) não vira accent: em ambos os casos o
# motivo fica registrado. Ver lib/branding/contraste.ts.
APP_ACCENT_HEX=
# ── Idioma com que a instalação NASCE ───────────────────────────────────────
# `pt-BR` (padrão) ou `es`. Quem pergunta é o install.sh; quem grava é ele
# mesmo (ou o scripts/bootstrap-owner.ts, no caminho local), em
# `organizations.locale` E na preferência do usuário dono.
#
# ⚠️ NÃO é lido pelo app em runtime, e por isso não está em `lib/env.ts`: mudar
# esta linha depois da instalação não muda a interface de ninguém. Trocar o
# idioma de quem já está instalado é pela TELA — no seletor do topo (só para
# você) ou em Configurações › Organização (para quem entrar sem preferência).
APP_LOCALE=pt-BR
# --- WhatsApp Cloud API (Meta) — Fase 3a/3b do seam de canais ---
# Opcionais: sem elas o canal oficial simplesmente não é configurado, e o WAHA
# segue funcionando. Nada aqui adiciona container ao compose — o servidor é a
# nuvem da Meta.
META_APP_ID=
# App Secret: chave do HMAC SHA-256 que valida os webhooks (X-Hub-Signature-256).
META_APP_SECRET=
META_WABA_ID=
META_PHONE_NUMBER_ID=
META_SYSTEM_USER_TOKEN=
# Token que VOCÊ escolhe; a Meta o devolve no handshake (hub.verify_token) para
# provar que o endpoint é seu. Sem ele o GET de verificação responde 403.
META_WEBHOOK_VERIFY_TOKEN=
# Versão da Graph API. Explícita de propósito: bump é decisão, não deriva.
META_GRAPH_VERSION=v22.0
# --- Canal via intermediário (BSP) — opcional ---
# Deixe em branco se você não usa este canal; nada quebra sem estas três.
#
# As DUAS de baixo andam juntas: o adapter só se considera configurado com as
# duas preenchidas. Preencher só uma não é meio-caminho — é o estado que fazia a
# mensagem ser marcada como enviada sem sair, e por isso a checagem agora exige
# o par. A credencial também pode viver na SESSÃO (cifrada), e aí estas ficam
# vazias.
ZERNIO_ACCOUNT_ID=
ZERNIO_API_KEY=
# Só para apontar para homologação. Vazio usa a produção do provedor.
ZERNIO_API_BASE_URL=
# --- Teto de login por IP (SÓ para CI de e2e) ---
# NÃO defina isto em produção nem numa VPS. O default (60 tentativas por IP a
# cada 5 min) é o valor correto para uso real e já é folgado por causa de NAT.
#
# Existe porque no CI todos os testes saem de UM IP (o runner): 28 specs de e2e
# estouram o teto e o login passa a responder "Muitas tentativas", reprovando
# specs que não têm nada a ver com autenticação.
#
# O teto por CONTA (5 falhas na mesma conta em 5 min) NÃO é configurável de
# propósito — é ele que barra brute force, inclusive distribuído por vários IPs.
# Valor inválido ou ausente cai no default: a falha é fechada.
# AUTH_RATE_LIMIT_LOGIN_IP=1000
# Retenção do arquivo do corpo cru dos webhooks (webhook_events_log).
# 7 dias mantém o banco dentro dos 500 MB do plano gratuito do Supabase; o
# arquivo era 86% do banco numa instalação real, crescendo ~23 MB/dia.
WEBHOOK_LOG_BODY_RETENTION_DAYS=7
# Quando a LINHA some (não só o corpo). Até aqui ela custa ~200 B e ainda
# responde quantos eventos chegaram, de que tipo e se a assinatura conferia.
WEBHOOK_LOG_ROW_RETENTION_DAYS=90
# --- Web Push (bandeja do SO com a aba fechada) ------------------------------
# Sem estas chaves, alertas só disparam com o site aberto (outra aba/minimizado).
# Gerar: npx web-push generate-vapid-keys
VAPID_PUBLIC_KEY=
VAPID_PRIVATE_KEY=
# Retenção do HISTÓRICO de leads captados (webhook_lead_captures) — a aba
# "Leads recebidos". Bem mais longa que a do arquivo acima porque aqui a linha
# É o produto: é ela que responde de qual campanha veio o cliente que fechou.
# ~1 kB por formulário preenchido, então 300/dia por um ano dão ~110 MB.
# O código que apaga aplica um piso de 30 dias — número menor não desarma a
# evidência de origem de um contato.
LEAD_CAPTURE_RETENTION_DAYS=365