Sales funnel on WhatsApp
A team project can carry a funnel: the checkpoints a lead goes through, what is due at each one and the exact message for it. The team prepares the day, the founder approves in Your decisions, and the office sends what was approved from the project's WhatsApp line: spaced, in business hours, one batch at a time. Replies come back to the founder with the conversation and the checkpoints to move to. Everything is a project file, so the cloud, a desktop and a Claude Code session read the same funnel.
| File | What it holds |
|---|---|
funil/funil.yaml (or funnel.yaml) | Checkpoints, deadlines, messages per checkpoint, rules, variables, checks, API actions, API reads |
team/shared/leads.csv | One row per lead: checkpoint, last contact, next date, codes from connected systems |
knowledge/sales/funil.md | The messages: a ## section per checkpoint; each > quote block is one WhatsApp message |
team/shared/contatos.csv | Every message in and out (written by the WhatsApp line); the funnel's memory |
team/conversas/<lead>.md | The same conversation, readable |
team/shared/optout.csv | Numbers that asked not to be contacted (column telefone): never written to |
team/lotes/<file>.json | First messages written in advance for a group of leads ([{ "lead", "texts" }]) |
connections/<api>.yaml | kind: http-api: the connected system the funnel reads and (with approval) writes |
The funnel file
regras:
primeiras_mensagens_por_dia: 25 # first contacts planned per day
intervalo_entre_envios_s: 75 # pause between first contacts (a number that sends too fast is flagged)
intervalo_outros_s: 8 # pause between other messages
horario_envio: "08:00-20:00" # planned messages leave only in this window (the funnel's time zone)
preparar_as: "08:00" # the office prepares the day at this time, on weekdays
sincronizar_a_cada_min: 60 # how often the office reads the connected API
fuso: America/Sao_Paulo
nosso_nome: Missão CNH # how our messages appear in team/conversas
api:
conexao: missao-cnh-api # connections/missao-cnh-api.yaml
variaveis: # {nome} in a message → the lead's nome_curto, and so on
nome: "{nome_curto}"
nota: "{nota|virgula}" # transforms: virgula, maiusculas, codigo, compacto
link_convite:
valor: "https://www.missaocnh.com.br/parceria?codigo={convite_codigo}"
requer: convite_codigo # without it the message shows `falta` and the check holds it
falta: "[link do convite: aprove o convite na API]"
primeira_mensagem: # the first checkpoint's message, chosen per lead
secao: Abordado primeira mensagem
requer: { whatsapp: sim }
variantes:
- { se: { segmento: "!instrutor" }, usar: autoescola }
- { se: { nota: "*" }, usar: instrutor com nota }
- { usar: instrutor sem nota }
checagem: # what keeps a message from reaching the founder
sem_emoji: true
max_caracteres: 1200
valores_permitidos: [0, 10, 20, 47, 57] # R$ values the messages may state
proibido:
- { padrao: "garant\\w* (a )?aprova", motivo: promete aprovação }
acoes: # writes to the connected API, each one an approval
criar-convite:
titulo: Criar o convite na Missão CNH
operacao: criarConvite
corpo: { rotulo: "{nome} · {cidade}-{uf}", codigo: "{nome_curto|codigo}-{uf}" }
grava: { convite_codigo: codigo } # lead column ← field of the answer
feito_quando: convite_codigo # already done when the lead has it
sincronizar: # reads (no approval) that move leads with evidence
- se: convite_codigo
ler: { operacao: interessados, consulta: { codigo: "{convite_codigo}" }, primeiro: itens }
grava: { interessado_id: id }
avanca:
- { se: formulario_preenchido, de: [aceitou, convidado], para: pre_cadastro, porque: preencheu os dados }
checkpoints:
- id: novo
grupo: prospeccao # groups fora and saida are out of the funnel
prazo: 0 # business days without news until the next action is due
depois_do_envio: abordado # where the lead goes once its message left
- id: abordado
mensagem: abordado-lembrete # the ## section of funil.md
prazo: 3
lembretes_max: 1 # reminders without an answer, then…
sem_resposta_depois: sem_resposta # …the lead moves here
- id: aceitou
mensagem: aceitou-convite
ao_responder: seguir # a reply here is the expected one: no classification card
acao_api: criar-convite # the API write that comes before the message
depois_do_envio: convidado
depois_do_envio_requer: convite_codigo
A lead in the first checkpoint enters the plan only when it has a date (proxima_data, or Agendar a primeira mensagem on the board): the founder chooses whom to approach and when.
The day
- The plan. At
preparar_ason weekdays (or Preparar o dia agora in Team → Funil) the office reads the API, then turns what is due into approvals: one per message (with every text, editable, and the number) and one per API write. Messages with an unfilled variable, an emoji, a price outside the table or a forbidden promise are held, with the reason. The founder gets one notification for the plan, not one per message. - Approval. What the founder approves goes to the send queue, exactly as approved. Approving everything at once is safe: the office sends one batch at a time, waits
intervalo_entre_envios_sbetween first contacts and keeps planned messages insidehorario_envio. - Just before sending the lead is checked again: a reply that arrived after the plan, a new checkpoint or an opt-out retires the message (it no longer fits). A batch cut in the middle is marked failed and never resent by itself: the first messages may already be on the person's phone.
- After sending the conversation is written, the last contact updated and the lead moved (
depois_do_envio), or its reminders counted. - Replies. A lead's message reaches the founder as a card with the conversation: answer it (each paragraph one message, sent through the queue), move the lead to another checkpoint, ask the team for a draft, or file it. A message the founder sends from the phone (an audio, a call follow-up) is recorded and answers the card. Moving a lead plans its next step at once (an invite to create, then the message with its link).
- The API. An approved action runs once, with an idempotency key, stores what it returns on the lead and plans the message that waited for it. Reads run every
sincronizar_a_cada_minand move leads only with evidence.
WhatsApp lines
On app.seifprotocol.com the platform runs the Evolution server and gives each project a line (Conectar WhatsApp: scan the QR code with the phone). The project never holds the server's key: its processes ask the host to send, and the host sends only from that project's line. On a desktop the team sends from the WhatsApp connection chosen in Data & go live (your own Evolution server).
From a terminal
seif funnel --project . [--date 2026-10-26] --json shows the board and what is due on a day, from this copy (read only). seif cloud link, seif cloud attach <project id> and seif cloud sync keep this folder and the cloud project the same files (see cli.md).