docs/DEPLOY_FREEMIUM_F4.md — passo a passo ordenado: - PRE-REQUISITO #0: schema-per-tenant precisa estar no hosted antes (freemium depende de tenant_schemas/clone/is_saas_admin/pgrst schemas) - migrations 05/06/07 + 5 manual supabase_admin (ordem + nota de permissoes hosted) - Auth dashboard (confirm email + redirect URLs + SMTP GoTrue) - deploy das edges recover-access/send-welcome-email + secrets SMTP - rebuild front + smoke test (8 passos) + rollback/kill-switch Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8.5 KiB
Deploy F4 — Freemium / PLG (hosted)
Runbook de produção do épico freemium/PLG (branch
feat/freemium-plg). Gerado em 2026-06-13. Faça um passo de cada vez e valide antes de seguir.
⛔ PRÉ-REQUISITO #0 (bloqueante) — schema-per-tenant no hosted
O freemium foi construído em cima da migração schema-per-tenant. As RPCs
(auto_provision_free_tenant, slug_disponivel, enforcement de limite, etc.)
dependem de infra que só existe se a schema-per-tenant já estiver no hosted:
tenant_schemas,_tenant_template,clone_tenant_template,seed_*- helpers
tenant_id_for_schema,tenant_schema_name,is_saas_admin,is_tenant_member - exposição dinâmica de schemas no PostgREST (
pgrst.db_schemas) v_tenant_active_subscription,notifications_sistema,tenant_members.slug
Se o hosted ainda está no modelo RLS (branch main), NÃO aplique o freemium —
ele vai quebrar. Ordem obrigatória:
- Deployar e validar a schema-per-tenant no hosted (migrations
20260612*+20260613000001..000004+ osmanual/f5*..f6_2h*+f6_4), sem o F6.3 DROP num primeiro momento (dados espelhados em public — verdatabase-novo/manual/f6_3_ROLLBACK.md). - Só então seguir este runbook do freemium.
Enquanto a schema-per-tenant não estiver no hosted + testada no browser (task #7 / DROP F6.3 pendente), este deploy fica em espera.
Inventário do que vai pro hosted (freemium)
Migrations (rodam como postgres via supabase db push ou SQL Editor)
| Ordem | Arquivo | O quê |
|---|---|---|
| 1 | database-novo/migrations/20260613000005_freemium_f1_therapist_free_patient_limit.sql |
max_patients=20 no therapist_free |
| 2 | database-novo/migrations/20260613000006_fix_audit_global_tables.sql |
fix regressão audit em tenant_members (aplicar SEMPRE) |
| 3 | database-novo/migrations/20260613000007_freemium_f2_vitrine_free.sql |
cartão "Grátis" na vitrine (plan_public + bullets) |
Manual supabase_admin (rodam com role elevada — ver nota de permissões)
Aplicar nesta ordem (idempotentes; BEGIN/COMMIT internos):
database-novo/manual/freemium_f1_plan_limits.supabase_admin.sqldatabase-novo/manual/freemium_f2_provisioning.supabase_admin.sqldatabase-novo/manual/freemium_f3a_blacklist.supabase_admin.sqldatabase-novo/manual/freemium_f3b_saas_owners_notify.supabase_admin.sqldatabase-novo/manual/freemium_f3c_app_config.supabase_admin.sql
Edge functions
supabase/functions/recover-access(esqueci-email por slug → magic link)supabase/functions/send-welcome-email(boas-vindas ao dono — best-effort)
Config
- Auth → Confirm email = ON + Site/Redirect URLs
- Secrets de SMTP do
send-welcome-email(+APP_URL)
Passo a passo
1) Banco — migrations
Com a CLI apontando pro projeto hosted (supabase link já feito):
supabase db push # aplica as migrations pendentes (inclui as 3 do freemium)
Ou, se preferir manual, cole cada arquivo migrations/2026061300000[567]_*.sql no
SQL Editor do dashboard (roda como postgres), na ordem da tabela acima.
⚠️ A
20260613000006_fix_audit_global_tables.sqlé obrigatória — sem ela, qualquer novotenant_members(provisionamento, convite) falha no hosted também.
2) Banco — manual supabase_admin
Nota de permissões (hosted): no Supabase hosted, o postgres da connection
string tem mais privilégio que o local, mas o schema auth é de supabase_admin.
A blacklist (freemium_f3a) cria trigger em auth.users e vários objetos são
ALTER FUNCTION ... OWNER TO supabase_admin. Caminhos:
- SQL Editor do dashboard roda como
postgres(costuma conseguir criar trigger emauth.usersno hosted) — tente por aí primeiro. - Se algum
OWNER TO supabase_adminou o trigger emauth.usersfalhar por permissão, rode via a connection string de serviço (Settings → Database → Connection string), ou abra ticket de acesso. OsOWNER TO supabase_adminpodem ser trocados porOWNER TO postgresno hosted se necessário (sem perda funcional).
Aplicar os 5 arquivos manual/freemium_f*.supabase_admin.sql na ordem, colando
no SQL Editor (cada um é uma transação). Verifique a saída sem erro a cada um.
Smoke SQL pós-aplicação (no SQL Editor):
select public.slug_disponivel('teste_slug_livre'); -- {ok:true}
select public.get_root_redirect(); -- 'landing'
-- como saas_admin (logado no dashboard você é postgres; teste a RPC existe):
select proname from pg_proc where proname in
('auto_provision_free_tenant','processar_pos_signup','slug_disponivel',
'saas_list_account_owners','notify_all_devs','is_email_blacklisted','get_root_redirect');
-- trigger de limite presente nos schemas:
select count(*) from pg_trigger where tgname='enforce_patient_plan_limit';
3) Auth — dashboard
Authentication → Providers / Email:
- Confirm email = ON (equivale ao
enable_confirmations=truedo config.toml local). - Site URL = origem do app em produção (ex.:
https://app.seudominio.com). - Redirect URLs — adicionar (magic link + confirmação caem aqui):
https://app.seudominio.com/onboardinghttps://app.seudominio.com/auth/loginhttps://app.seudominio.com/**(se preferir curinga)
- SMTP do GoTrue (o que manda confirmação + magic link): garantir que está configurado com um provedor real (não Mailpit) em Authentication → Emails → SMTP.
4) Edge functions — deploy + secrets
supabase functions deploy recover-access
supabase functions deploy send-welcome-email
recover-access usa só envs já injetadas (SUPABASE_URL / SERVICE_ROLE_KEY / ANON_KEY).
send-welcome-email usa um SMTP de sistema (defaults = Mailpit local). Em produção,
configure os secrets pra um provedor real (pode ser o mesmo do GoTrue):
supabase secrets set \
SMTP_HOST="smtp.seuprovedor.com" \
SMTP_PORT="587" \
SMTP_USER="..." \
SMTP_PASS="..." \
SMTP_FROM="no-reply@seudominio.com" \
SMTP_FROM_NAME="Agência PSI" \
APP_URL="https://app.seudominio.com"
É best-effort: se faltar SMTP, o welcome só não envia — o onboarding/login segue.
5) Frontend — rebuild + deploy
Build apontando pras envs do hosted (Supabase URL + anon key de produção):
npm run build
Publique o dist/ no hosting de sempre. (A confirmação de e-mail é resolvida
server-side; o front já trata o caso "sem sessão" → tela "confirme seu e-mail".)
6) Smoke test no hosted (fluxo completo)
/lp→ o cartão Grátis aparece na vitrine.- Criar conta grátis → escolher slug (disponibilidade ao vivo) → enviar.
- Cai na tela "confirme seu e-mail" (não loga ainda).
- Abre o e-mail (provedor real) → clica no link → entra →
/onboardingprovisiona → painel do tenant. Welcome email chega (se SMTP configurado). - Cadastrar pacientes até passar do limite → toast PLAN_LIMIT_REACHED + Upgrade PRO.
- Logar como dev (saas_admin) →
/saas/usuarioslista o novo cliente com selo "Novo"; o sino recebeu "Nova assinatura". /auth/login→ "Esqueci meu e-mail" com o slug → recebe magic link, dica mascarada./saas/app-config→ adicionar um e-mail na blacklist → tentar cadastrar com ele → bloqueado. Trocar root_redirect e conferir o destino de/.
Rollback / kill-switch (se algo der errado)
- Confirmação de e-mail: desligar "Confirm email" no dashboard volta ao signup sem confirmação (mas o signup novo já espera confirmação — prefira corrigir a frente).
- Enforcement de limite:
DROP TRIGGER enforce_patient_plan_limit ON <schema>.patients(ou ajustarplan_features.limitspra um número alto — vale em runtime, sem deploy). - Blacklist:
DROP TRIGGER trg_enforce_email_blacklist ON auth.users; - notify devs:
DROP TRIGGER trg_subscriptions_notify_devs ON public.subscriptions; - root_redirect:
UPDATE public.saas_app_config SET root_redirect='login';(ou 'landing'). - Tudo é aditivo — nenhuma tabela/coluna existente foi removida pelo freemium.
Checklist rápido
- schema-per-tenant já está no hosted e validada (PRÉ-REQUISITO #0)
- migrations 05/06/07 aplicadas (
supabase db push) - 5 manual/freemium_f*.supabase_admin.sql aplicados na ordem
- Confirm email = ON + Site/Redirect URLs + SMTP do GoTrue
recover-accessesend-welcome-emaildeployadas- secrets SMTP do
send-welcome-email+APP_URL - frontend rebuildado e publicado
- smoke test (8 passos) ✅