Motor de outbound
No arRodar cold outreach por e-mail, LinkedIn ou os dois, com ritmo seguro por caixa e por conta, parada automática quando o lead responde e sem depender de lemlist ou n8n
- Situação
- 28 campanhas ativas, 15 pausadas e 1 arquivada (07/10/2026)
- Dono
- Karime
- Onde mora
- Repositório outbound-machine (documentação e cópias do que foi aplicado) e Supabase nearsmart
pbvjsixlqnuzcnqahbxu(fonte da verdade). A campanha Talent Pool tem repositório próprio: talent-pool
Fluxo
O time cria a campanha pelo plugin no Claude, sobe a lista e os leads recebem e-mails e mensagens no LinkedIn até responder.
- Pelo plugin, o time cria a campanha e sobe a lista
- O time revisa a mensagem e coloca a campanha no ar
- Enviamos a sequência de e-mails no ritmo de cada caixa
- Convidamos e mandamos mensagens no LinkedIn
- O lead responde
- O time recebe o aviso no Discord e assume a conversa
Recebe de
- Plugin outbound-campaigns cria e opera as campanhas
- Bloqueio de empresas barra empresas protegidas
- Comentários no LinkedIn roda na cadência do motor
- Talent Pool US envia pelo motor
- Warm-up machine aquece as caixas de e-mail
- CTOs em transição envia pelo motor
Entrega para
- Engajamento no LinkedIn passa os leads da campanha
- Report outbound executivo alimenta o painel por dono
A Outbound Machine (motor ob_*) é o motor de prospecção multicanal da Luby: dispara cadências de e-mail pelo Resend e de LinkedIn pela Unipile, e roda inteiro dentro do Supabase nearsmart, sem tela própria. Ele é o backend: o time cria, abastece e acompanha as campanhas pelo plugin outbound-campaigns, que funciona como a tela do motor dentro do Claude. Está no ar: em 07/10/2026 eram 28 campanhas ativas e 15 pausadas.
Como funciona
Fluxo
- A campanha nasce
pausedemob_campaigns(janela de envio, fuso, dias, parar ao responder, parar no bounce), com remetentes emob_campaign_senderse uma sequência ativa emob_sequenceseob_steps. - Os leads chegam por CSV no endpoint
ob-ingest-leads:create-upload, upload do arquivo no Storage,processem simulação econfirm. O servidor valida, deduplica contra todo oob_leads(por e-mail ou LinkedIn) e só então matricula. - Entre o
processe oconfirm,ob_block_check_jobcruza o lote com a lista de empresas bloqueadas e o usuário decide o destino de cada empresa. - Opcional: enriquecimento via
ob-enrich-leads(Apollo). A simulação mostra o custo em créditos antes de gastar. - O
confirmchamaob_import_enroll, que usaob_load_campaign: criaob_enrollmentscom statusactiveenext_run_at=now(), fixa a caixa de e-mail (rodízio entre as caixas ativas da campanha) e a conta de LinkedIn (rodízio ponderado porweekly_invite_cap) e pula pessoa repetida. - Alguém ativa a campanha. O
ob_tické global: os crons por canal varrem todas as campanhas ativas em fila justa (rodízio entre campanhas, ordem de chegada dentro de cada uma). - Toque de e-mail: dentro da janela da campanha, o tick confere a supressão (
ob_is_suppressed), reserva um envio no balde da caixa (ob_email_reserve), cria a linha ememail_outboxe chamaemail-push. Toque com IA passa antes porgen-email-copy(linha emawaiting_copy). - Campanha com
batch_send=truenão chamaemail-push: a linha fica embatchinge oemail-push-batchenvia até 100 e-mails numa chamada ao Resend. Hoje 2 campanhas usam o lote. email-pushmanda texto e HTML, anexa a assinatura (email_senders.signature_title), o cabeçalho de descadastro One-Click e uma chave de idempotência.resend-webhookrecebe entregue, aberto, clique, bounce, reclamação e falha; atualizaemail_outbox, para a sequência em hard bounce e reclamação (com supressão global) e alimentaoutbound_events.- Resposta de e-mail: o Resend Receiving recebe no MX dos domínios cadastrados em
ob_inbound_domainse chamaemail-inbound, que casa pelo Message-ID do provedor (com fallback por endereço), rodaob_stop_on_reply, grava emmessages_received, encaminha para oforward_toda caixa e avisa no Discord do mercado na primeira resposta. - Toque de LinkedIn: o tick faz o lookup do grau de conexão, reserva no balde da conta, gera a copy com
gen-li-copye envia comunipile-push(convite seco, mensagem ou InMail).unipile-webhookrecebe aceite e resposta;unipile-accept-reconcilerecupera aceite perdido a cada 6 horas. waitagenda N dias úteis dentro da janela;endconclui. O link de descadastro (email-unsubscribe) suprime o endereço em todas as campanhas.- Vigilância:
ob_engine_alertsposta no Discord a cada 15 minutos (modelo "caiu, voltou");ob_bounce_guardpausa campanhas específicas acima de 3% de bounce;ob_bounce_watchsó avisa acima de 2%.
Peças
| Peça | Tipo | O que faz |
|---|---|---|
ob_campaigns |
tabela | Campanha: status, janela (start_hour, end_hour, timezone, active_days), stop_on_reply, batch_send, li_engagement_steps |
ob_sequences e ob_steps |
tabela | A cadência; cada toque tem type e content em jsonb |
ob_campaign_senders |
tabela | Quais caixas e contas de LinkedIn servem a campanha (is_active) |
ob_leads |
tabela | Pool de leads, sem campaign_id; colunas extras do CSV viram variables |
ob_enrollments |
tabela | Lead dentro de uma campanha: status, próximo toque, caixa e conta fixadas |
email_senders |
tabela | Caixas de envio; daily_limit é o único botão de ritmo |
email_outbox |
tabela | Uma linha por e-mail, do queued ao delivered |
email_events |
tabela | Eventos de e-mail; o gatilho email_events_apply_v2_after_insert aplica cada um |
li_senders, li_outbox, li_market_config |
tabela | Contas de LinkedIn e tetos, registro da cadência de LinkedIn, janela e Discord por mercado |
ob_suppression |
tabela | Supressão global por endereço (bounce, reclamação, descadastro) |
ob_inbound_domains |
tabela | Domínios com recebimento por webhook (5 hoje) |
ob_reply_mailboxes |
tabela | Caixas do leitor IMAP antigo; mantida só para restauração |
ob_activities |
tabela | Log de eventos, só inserção |
ob_campaign_registry |
tabela | Registro de governança: quem criou, canais, lote, landing page |
ob_import_jobs, ob_import_staging, ob_alert_state, ob_alert_log |
tabela | Jobs de ingestão de CSV; estado e histórico dos alertas |
ob_campaign_metrics, ob_campaign_health, ob_sender_health, ob_email_capacity |
view | Funil, fluxo da fila, saúde por remetente, capacidade real por caixa |
ob_tick |
função SQL | O motor: processa toques vencidos por canal |
ob_load_campaign |
função SQL | Matricula leads numa campanha |
ob_email_reserve |
função SQL | Balde de envio por caixa (recarga por hora + teto diário) |
trg_email_senders_pace e ob_email_pace_recalc |
gatilho e função | Derivam o ritmo por hora a partir do daily_limit e da janela |
ob_stop_on_reply |
função SQL | Para a sequência de quem respondeu |
ob_bounce_guard, ob_bounce_guard_since, ob_bounce_watch |
função SQL | Pausa ou avisa por taxa de bounce |
ob_engine_alerts e ob_engine_alert_reconcile |
função SQL | Detector de saúde e diferença contra o estado anterior |
email-push e email-push-batch |
edge function | Envio unitário e em lote pelo Resend |
gen-email-copy, gen-li-copy, gen-li-copy-preview |
edge function | Copy por IA (OpenRouter) ou template; a preview só testa prompt, sem gravar |
resend-webhook |
edge function | Eventos de entrega do Resend |
email-inbound |
edge function | Recebimento de resposta por webhook |
email-unsubscribe |
edge function | Descadastro One-Click com token assinado |
email-reply-check |
edge function | Leitor IMAP antigo; publicado, sem cron |
unipile-push, unipile-webhook, unipile-accept-reconcile |
edge function | Envio, retorno e conciliação de aceite no LinkedIn |
unipile-accounts e unipile-admin |
edge function | Conectar e reconectar contas; administração da Unipile |
ob-profile-insights |
edge function | Análise opcional do perfil do prospect antes da copy de LinkedIn |
ob-ingest-leads e ob-enrich-leads |
edge function | Ingestão de CSV no servidor e enriquecimento via Apollo |
ob-engine-alert |
edge function | Posta os alertas do motor no Discord |
Agendamentos
Os crons rodam em UTC. "Seg a sex" vale para o dia em UTC, ou seja, de domingo 21h a sexta 20h59 em BRT. O envio em si ainda respeita a janela de cada campanha ou conta.
| Job | Quando (BRT) | O que faz |
|---|---|---|
ob_tick_email |
A cada 2 min, seg a sex | ob_tick do e-mail, lote de 200 |
ob_tick_linkedin |
A cada 2 min, seg a sex | ob_tick do LinkedIn, lote de 100 |
ob_drain_email |
A cada 10 min (minutos 3, 13, 23...), seg a sex | Chama email-push-batch para as campanhas em lote |
ob_engine_alert |
A cada 15 min, todos os dias | Alertas de saúde no Discord |
ob_bounce_guard_* (4 jobs) |
De hora em hora, minuto 5 | Pausa 4 campanhas específicas se o bounce passar de 3%. O da "Talent Pool US (email)" (ob_bounce_guard_tp_email) chama ob_bounce_guard_since(<campanha>, <momento da reativação>, 3.0, 100) e só conta bounces a partir da reativação de 08/10/2026 |
ob_bounce_watch |
De hora em hora, minuto 10 | Avisa no Discord campanha ativa acima de 2%, sem pausar, com intervalo de 12h |
ob_li_accept_reconcile |
21h, 3h, 9h e 15h, seg a sex | Recupera aceites de convite que o webhook perdeu |
ob_reply_mailboxes_sync |
De hora em hora, minuto 7 | Mantém a tabela do leitor IMAP pronta para restauração |
Jobs de campanha (ob_tp_*, ob_load_tp_email_daily, cto-monthly-ob-us, webinar-*) |
Variados | Alimentação da Talent Pool, carga mensal de CTOs e jobs pontuais do webinar; não fazem parte do motor |
Regras de negócio
- A campanha nasce pausada.
draftnão trava envio: o tick só pulapaused,archivedecompleted. Carregar leads numa campanha ativa dispara no próximo ciclo. - Teto de 60 e-mails por dia em cada caixa desde out/2026. É o valor de
daily_limit; o gatilho divide pelas horas da janela da campanha para chegar ao ritmo por hora. Não há trava no banco que impeça um valor maior. - Caixas @nearsmarter.com só servem às campanhas "Talent Pool US (email)" e "Wagner Signals - Nearsmarter". Os vínculos antigos dessas caixas com outras campanhas estão com
is_active=false. É regra operacional, sem trava no banco. - Resposta de e-mail é capturada pelo webhook de inbound do Resend. O leitor IMAP (
email-reply-check) é legado e está sem cron. - Idioma é por toque. E-mail sem
content.languagesai em inglês; e-mail em português só comlanguage='pt-BR'. - Remetente é fixado na matrícula. Mudar teto depois vale; trocar caixa só vale para matrículas novas.
- Convite de LinkedIn é sempre seco (sem nota).
- LinkedIn usa a janela do mercado da conta: 9h às 18h, seg a sex, em Nova York ou São Paulo. São 9 contas ativas (8 US, 1 BR), com 975 convites por semana somados.
- Abertura não decide nada. O motor decide por entrega, resposta e bounce; abertura é inflada por scanners.
- Passos de visita, curtida e comentário dentro da cadência só rodam em campanha com
li_engagement_steps=true(hoje 1 piloto, pausada). Nas demais viram skip. Em fase final de análise; ver Comentários no LinkedIn. - Talent Pool usa o carregador próprio
ob_load_from_peoplee tem runbook no repo dela. Campanha nova nunca usa esse caminho.
Integrações e credenciais
| Serviço | Credencial (só o nome) | Onde mora |
|---|---|---|
| Resend (envio, eventos, recebimento) | RESEND_API_KEY, RESEND_WEBHOOK_SECRET, RESEND_INBOUND_SECRET |
Edge Function Secrets |
| Unipile (LinkedIn) | UNIPILE_API_KEY, UNIPILE_DSN |
Edge Function Secrets |
| OpenRouter (copy por IA) | OPENROUTER_API_KEY |
Edge Function Secrets |
| Apollo (enriquecimento) | APOLLO_API_KEY |
Edge Function Secrets |
| Portão das edges de e-mail e ingestão | OB_PUSH_SECRET (cabeçalho x-ob-secret; também assina o token de descadastro) |
Supabase Vault e Edge Function Secrets, com o mesmo valor |
| Portão das edges de LinkedIn, warm-up e alertas | LI_PUSH_SECRET (cabeçalho x-li-secret) |
Supabase Vault e Edge Function Secrets, com o mesmo valor |
| Discord | DISCORD_WEBHOOK_URL, DISCORD_WEBHOOK_URL_US, OB_ENGINE_ALERT_DISCORD_WEBHOOK |
Edge Function Secrets |
| Discord por mercado (respostas) | coluna discord_webhook_url |
tabela li_market_config |
| Discord do vigia de bounce | linha growth-notification |
tabela ads_secrets |
| IMAP legado | imappw-<email> e IMAP_REPLY_* |
Vault e Edge Function Secrets |
Todas as edges rodam com verify_jwt=false e recusam qualquer chamada sem o cabeçalho certo. Trocar OB_PUSH_SECRET ou LI_PUSH_SECRET num lugar só faz tudo responder 401.
Como monitorar
-- funil e saúde por campanha
select * from public.ob_campaign_metrics;
select * from public.ob_campaign_health;
-- saúde e capacidade por remetente
select * from public.ob_sender_health;
select * from public.ob_email_capacity where limite_e_ficcao;
-- e-mails travados
select status, count(*) from public.email_outbox
where (status in ('failed','awaiting_copy') and created_at < now() - interval '15 minutes')
or (status = 'batching' and created_at < now() - interval '45 minutes')
group by 1;
-- os crons do motor rodaram?
select j.jobname, d.status, d.start_time
from cron.job_run_details d join cron.job j on j.jobid = d.jobid
where j.jobname in ('ob_tick_email','ob_tick_linkedin','ob_drain_email','ob_engine_alert')
order by d.start_time desc limit 20;
-- alertas abertos e histórico
select * from public.ob_alert_state order by opened_at;
select ts, transition, alert_key, detail from public.ob_alert_log order by ts desc limit 30;
Como usar
O que pedir ao Claude
O time opera o motor pelo plugin outbound-campaigns (página própria). Exemplos:
- "Cria uma campanha de e-mail para essa lista de CTOs nos EUA."
- "Sobe esse CSV na campanha AI Legacy Modernization."
- "Como está a campanha Wagner Signals - Nearsmarter?"
- "Me dá o report executivo de todas as campanhas."
- "Pausa a campanha X agora."
- "A empresa Y virou cliente, bloqueia ela."
Passo a passo
- Instale o plugin
outbound-campaignse confirme que o conector do Supabase aponta para o projeto nearsmart. - Peça a campanha nova. O Claude pergunta objetivo, canal, mercado, idioma, responsável, janela, remetentes, cadência e copy, e cria tudo pausado.
- Envie a lista em CSV (não Excel, não link). O Claude mostra quantos entram, quantos ficam de fora e por quê, e confere empresas bloqueadas.
- Revise a prévia da mensagem e diga "pode colocar no ar". Só então a campanha começa a enviar, nos dias e horários escolhidos.
- Acompanhe com "como está a campanha X" ou com o report executivo.
Cuidados
- Adicionar leads numa campanha ativa faz as pessoas receberem em minutos, sem revisão.
- Respeite o teto de 60 e-mails por dia por caixa e a regra das caixas @nearsmarter.com.
- Domínio do remetente é marca: @lubysoftware e @nearsmarter são empresas diferentes para quem recebe.
- Para parar qualquer campanha na hora, peça "pausa a campanha X". Pausar é sempre seguro.
- Não peça mudanças na Talent Pool pelo plugin; ela tem runbook próprio.
Fontes
- README do outbound-machine
- CLAUDE.md do outbound-machine
- CREDENCIAIS.md
- v2/README.md
- INDEX de implementação
- ARQUITETURA-v3-outbound-email.md (repo) e cópia no Drive
- ALERTAS-MOTOR-RUNBOOK.md (repo) e cópia no Drive
- webhook-recebimento-RUNBOOK.md (repo) e cópia no Drive
- MANUAL-configurar-campanha.md no Drive
- PLANO-ob-enrich-leads.md no Drive
- contrato-motor.md
- ingestao-leads-endpoint.md
- Supabase nearsmart (08/10/2026):
cron.job,pg_get_functiondefdeob_tick,ob_bounce_guard,ob_bounce_watch,ob_engine_alertse contagens das tabelas citadas