Visão geral

A área de Followup permite que o gestor ative um serviço de reengajamento automático. Nesta primeira versão, o sistema considera apenas o cenário de inatividade (idle) para retomar o contato com o usuário.

Quando ativo, o sistema acompanha cada conversa e programa um horário (notify_at) para recontato. Ao atingir esse horário, uma mensagem é enviada (via WhatsApp por padrão ou via webhook externo se configurado), respeitando regras de deduplicação, segurança e limite de tentativas.


Como funciona

  • Ativação por host: se public.host_config.followup = true, o serviço entra em operação para o host.
  • Criação de acompanhamento: ao criar uma conversa, é criada uma linha em public.conversations_followup ligada a ela.
  • Reagendamento: sempre que public.conversations.last_message_at muda, recalculamos notify_at.
  • Envio: quando notify_at <= now() e regras ok, o sistema publica um job na fila SMART_FEEDBACK para envio.
  • Contagem: a cada contato realizado, incrementa hit. Ao chegar em hit = 3, a conversa é encerrada (fechada/desativada).

Cálculo do notify_at (idle)

  • Base: última atividade do lead (public.conversations.last_message_at da conversa mais recente do lead).
  • Atraso: definido em public.host_config.followup_config.idle_minutes (ou variantes legadas: iddle_minutes, delay_minutes, idle_minutes[], schedule[], delays[], per_hit).
  • Fallback: se ausente/inválido, usa-se 30 minutos.

Regras de banco de dados (por host)

  • Trigger cria/reativa public.conversations_followup ao criar conversa (com o mesmo host_id).
  • Se existir registro inativo da conversa, cria uma nova linha ativa.
  • Ao fechar a conversa (public.conversations.closed = true), desativa o followup correspondente.
  • Trigger atualiza notify_at ao alterar public.conversations.last_message_at.
  • Limpeza diária: remove registros de public.conversations_followup com created_at > 30 dias.

Pooling e publicação

  • Processo de pooling a cada 1 minuto busca public.conversations_followup com active = true, hit < 3 e notify_at <= now().
  • Para cada item elegível, publica payload na fila SMART_FEEDBACK com retryCount = 0, rebounce = true e rebounceInstructions vindas de general_instructions.

Deduplicação por lead e ativação

  • Apenas a conversa mais recente do lead (maior last_message_at) é elegível.
  • Conversas antigas do mesmo lead têm active = false no followup.
  • Debounce por lead evita múltiplos enfileiramentos no mesmo ciclo.

Unicidade por telefone (idle)

  • Telefone único por host_id e wpp_id no contexto de idle.
  • Normalização com E.164 sem + (ex.: 5511999998888).
  • Matching com variantes (com/sem dígito 9).
  • Atualiza conversations_followup.wpp_id da conversa e desativa duplicatas do mesmo telefone mantendo a mais recente (por last_message_at, fallback created_at).
  • Se mais de um lead_id tiver o mesmo telefone, prevalece a conversa com movimentação mais recente.

Agregação de hits por telefone (24h)

  • O hit efetivo é no mínimo o total de conversas criadas nas últimas 24h para o mesmo telefone/host (considerando variantes com/sem 9), respeitando max_notifications do host.

Propagação de general_instructions (24h)

  • Se a conversa atual (mais recente para o telefone) estiver sem general_instructions e existir outra com valor preenchido nas últimas 24h, o valor é propagado.

Smart Feedback e SmartDecision

O consumer de SMART_FEEDBACK executa um health check, chama a IA de SmartDecision e aplica guardrails de segurança:

  • Decisão da IA: decision em send | block | delay, com confidence, reason_code, risk_flags, manager_rule, channel e suppression_until quando aplicável.
  • Compatibilidade com legado é suportada e normalizada pelo servidor.
  • Guardrails (fail-closed):
    • user_opt_out/legal_restriction → força block.
    • manager_rule = hard_blockblock.
    • quiet_hours ou recent_duplicate sem priority_send → converte send para delay.
    • Campos obrigatórios: channel para send (padrão “whatsapp”), suppression_until para delay.
    • Qualquer erro de template/parsing → block com reason_code = system_error.

Reagendamento por delay

  • Se decision = delay, atualiza notify_at = suppression_until e mantém active = true.
  • Se suppression_until ausente e quiet_hours = true, calcula próxima janela útil às 09:05 (horário de São Paulo). Fallback: +60 minutos.

Combinação de instruções por hit

  • general_instructions combina instruções do hit com smartDecisionInstructions e é persistido em conversations_followup.general_instructions.

Logging

Registrar no globalData.tolkyRequestLog:

  • Contexto (host, rebounce, runtimeGlobalData).
  • Resultado bruto da IA e o resultado pós-guardrails.
  • Ação tomada: envio, bloqueio (com motivo) ou reagendamento (notify_at).

RabbitMQ

  • Fila principal: SMART_FEEDBACK.
  • Dead-letter manual: SMART_FEEDBACK_DLQ.
  • Tentativas: até 2 processamentos no total (1ª + 1 retry). Em indisponibilidade, usa nack para adiar.

Envio via webhook externo (webhook_config)

Se public.conversations_followup.webhook_config estiver preenchido (JSONB), o envio é feito por HTTP ao invés de WhatsApp.

  • url obrigatório. type padrão POST.
  • headers: objeto ou array de strings ("Header: Valor").
  • queryParams: envia body como query string quando true.
  • isAsyncCall: dispara sem aguardar resposta.
  • multipleCalls: quando algum campo do body é array, faz múltiplas chamadas.
  • cacheResults, insertDialogue, errorMessage, instructions, model, isFixed, includeDefaults.
  • schema opcional em JSON Schema simplificado ou mapa direto.
  • Defaults no body: message, conversation_id, host_id, lead_id, wpp_id (quando includeDefaults = true).
  • Em erro na chamada REST, trata como falha de envio.

Configuração

Você pode gerenciar a feature por dois meios principais:

  • Ativar/desativar na host_config (followup e followup_config).
  • Endpoints de configuração dedicados: /api/config/followup (GET/PUT).

Consulte a seção Desenvolvedor → API para esquemas e exemplos de requisição.


API de Configuração de Host e Followup

Esta documentação descreve os endpoints disponíveis para configurar hosts e o sistema de followup automático.

Autenticação

Todos os endpoints requerem autenticação via Bearer Token no header:

Authorization: Bearer <seu-token>

O token deve ser válido e associado ao host que você deseja configurar.

Endpoints de Configuração do Host

GET /api/config/hostConfig

Recupera todas as configurações do host autenticado.

Resposta de Sucesso (200):

{
  "id": "uuid",
  "host_id": "uuid",
  "general_level": 1,
  "model": "gpt-5",
  "max_tokens": 1500,
  "max_chunks": 5,
  "full_context": true,
  "persistent_conversation": true,
  "followup": false,
  "followup_config": null,
  "conversation_analysis": true,
  "leads": true,
  "online_search_allowed": false,
  "img_transc_allowed": true,
  "site_transc_allowed": true,
  "video_transc_allowed": null,
  "max_anonymous_messages": 15,
  "notification_email_l1": null,
  "heat_score_description": "...",
  "sentiment_score_criteria": "...",
  "voice": "echo",
  "created_at": "2024-01-01T00:00:00.000Z",
  "updated_at": "2024-01-01T00:00:00.000Z"
}

POST /api/config/hostConfig

Cria uma nova configuração para o host. Falha se já existir uma configuração ativa.

Body (opcional):

{
  "general_level": 1,
  "model": "gpt-5",
  "max_tokens": 1500,
  "max_chunks": 5,
  "full_context": true,
  "persistent_conversation": true,
  "followup": false,
  "conversation_analysis": true,
  "leads": true,
  "online_search_allowed": false,
  "img_transc_allowed": true,
  "site_transc_allowed": true,
  "video_transc_allowed": false,
  "max_anonymous_messages": 15,
  "notification_email_l1": "admin@exemplo.com",
  "heat_score_description": "Critérios customizados...",
  "sentiment_score_criteria": "Critérios customizados...",
  "voice": "echo"
}

Resposta de Sucesso (201):

{
  "id": "uuid",
  "host_id": "uuid",
  "message": "Configuração criada com sucesso",
  "created_at": "2024-01-01T00:00:00.000Z"
}

PUT /api/config/hostConfig

Atualiza configurações existentes do host.

Body:

{
  "general_level": 2,
  "max_tokens": 2000,
  "followup": true,
  "online_search_allowed": true,
  "notification_email_l1": "novo@exemplo.com"
}

Resposta de Sucesso (200):

{
  "message": "Configurações atualizadas com sucesso",
  "updated_fields": ["general_level", "max_tokens", "followup", "online_search_allowed", "notification_email_l1"],
  "updated_at": "2024-01-01T00:00:00.000Z"
}

Endpoints de Configuração do Followup

GET /api/config/followup

Recupera as configurações de followup do host, incluindo valores padrão se não configurado.

Resposta de Sucesso (200):

{
  "triggers": {
    "idle": {
      "active": true,
      "auto": false,
      "minutes": 15,
      "max_notifications": 3,
      "hits": [
        {
          "hit": 0,
          "generalInstructions": "Retome a conversa com o usuário",
          "dialogueInserts": "",
          "smartDecisionInstructions": "NUNCA envie uma mensagem repetida para o usuário.",
          "agentCalling": [],
          "minutes": 15
        }
      ]
    },
    "schedule": {
      "active": false,
      "hits": []
    },
    "conditionalAgents": {
      "active": false,
      "preCheck": [],
      "decisions": []
    }
  }
}

PUT /api/config/followup

Atualiza as configurações de followup do host.

Body:

{
  "followup_active": true,
  "triggers": {
    "idle": {
      "active": true,
      "auto": false,
      "minutes": 30,
      "max_notifications": 5,
      "hits": [
        {
          "hit": 0,
          "generalInstructions": "Olá! Notei que você estava interessado em nossos produtos. Posso ajudar com alguma dúvida?",
          "smartDecisionInstructions": "Apenas envie se o usuário demonstrou interesse real no produto.",
          "agentCalling": [
            {
              "schemaName": "public",
              "agentName": "buscar_produtos",
              "payload": {}
            }
          ],
          "minutes": 30
        },
        {
          "hit": 1,
          "generalInstructions": "Ainda estou aqui para ajudar! Tem alguma dúvida sobre nossos serviços?",
          "smartDecisionInstructions": "NUNCA envie uma mensagem repetida para o usuário.",
          "agentCalling": [],
          "minutes": 60
        }
      ]
    },
    "conditionalAgents": {
      "active": true,
      "preCheck": [
        {
          "schemaName": "public",
          "agentName": "verificar_contexto",
          "payload": {}
        }
      ],
      "decisions": [
        {
          "description": "Quando o usuário pediu ajuda ou demonstrou interesse",
          "content": "Vamos continuar nossa conversa! Como posso ajudar?",
          "agents": [
            {
              "schemaName": "public",
              "agentName": "assistente_vendas",
              "payload": {}
            }
          ]
        }
      ]
    }
  }
}

Resposta de Sucesso (200):

{
  "message": "Configuração de followup atualizada com sucesso",
  "updated_at": "2024-01-01T00:00:00.000Z",
  "config": {
    "triggers": {
      // Configuração normalizada aplicada
    }
  }
}

POST /api/config/followup/reset

Reseta as configurações de followup para os valores padrão.

Body (opcional):

{
  "keep_active": true,
  "custom_minutes": 20,
  "custom_max_notifications": 4
}

Resposta de Sucesso (200):

{
  "message": "Configuração de followup resetada com sucesso",
  "config": {
    // Configuração padrão aplicada
  },
  "followup_active": true,
  "updated_at": "2024-01-01T00:00:00.000Z"
}

Configuração de Followup - Detalhamento dos Campos

Estrutura Principal

{
  "triggers": {
    "idle": { /* Trigger por inatividade */ },
    "schedule": { /* Trigger agendado (futuro) */ },
    "conditionalAgents": { /* Agentes condicionais */ }
  }
}

Trigger Idle (Inatividade)

O trigger idle é ativado quando não há atividade na conversa por um tempo determinado.

Campos:

  • active (boolean): Ativa/desativa o trigger idle
  • auto (boolean): Se true, o followup é completamente automático
  • minutes (integer, 1-10080): Intervalo em minutos entre tentativas
  • max_notifications (integer, 1-50): Número máximo de tentativas antes de desistir
  • hits (array): Lista de configurações por tentativa
Configuração de Hits

Cada hit representa uma tentativa de followup com configurações específicas:

  • hit (integer): Número sequencial da tentativa (0, 1, 2…)
  • generalInstructions (string): Instruções gerais para a IA sobre como abordar o usuário
  • dialogueInserts (string): Texto adicional a ser inserido no diálogo
  • smartDecisionInstructions (string): Instruções específicas para o SmartDecision avaliar se deve enviar
  • agentCalling (array): Lista de agentes a serem chamados durante este hit
  • minutes (integer): Minutos específicos para este hit (sobrescreve o global)
Exemplo de Hit Completo:
{
  "hit": 0,
  "generalInstructions": "Retome a conversa de forma amigável, referenciando o contexto anterior da conversa. Seja útil e não insistente.",
  "dialogueInserts": "",
  "smartDecisionInstructions": "APENAS envie se: 1) O usuário demonstrou interesse genuíno, 2) Não há sinais de irritação, 3) A conversa não foi encerrada explicitamente pelo usuário.",
  "agentCalling": [
    {
      "schemaName": "public",
      "agentName": "buscar_produtos",
      "payload": {
        "categoria": "interesse_demonstrado"
      }
    }
  ],
  "minutes": 30
}

Agentes Condicionais

Os conditionalAgents permitem executar agentes antes de tomar decisões de followup.

Campos:

  • active (boolean): Ativa/desativa agentes condicionais
  • preCheck (array): Agentes executados antes das decisões
  • decisions (array): Lista de decisões condicionais baseadas no contexto
Estrutura de Decisões:
{
  "description": "Descrição da condição",
  "content": "Conteúdo da mensagem a ser enviada",
  "agents": [
    {
      "schemaName": "public",
      "agentName": "nome_do_agente",
      "payload": {}
    }
  ]
}

SmartDecision - Sistema de Decisão Inteligente

O SmartDecision é executado antes de cada envio de followup para avaliar se é seguro e apropriado contactar o usuário.

Campos de Resposta da IA:

  • shouldSend (boolean): Se deve enviar (compatibilidade)
  • decision (enum): “send”|“block”|“delay”
  • confidence (0-10): Confiança na decisão
  • explanation (string ≤200): Explicação da decisão
  • manager_rule (enum): “none”|“priority_send”|“soft_block”|“hard_block”
  • risk_flags (object): Checklist de riscos
  • reason_code (enum): Código da razão
  • channel (string): Canal de envio (obrigatório se decision=send)
  • suppression_until (datetime): Até quando adiar (obrigatório se decision=delay)

Risk Flags:

{
  "recent_duplicate": false,
  "user_opt_out": false,
  "quiet_hours": false,
  "not_aligned_to_brief": false,
  "spam_risk": false,
  "legal_restriction": false
}

Exemplos de Uso

1. Ativar Followup Básico

# Primeiro, ative o followup no host
curl -X PUT "https://api.exemplo.com/api/config/hostConfig" \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"followup": true}'

# Configure o followup com 2 tentativas a cada 60 minutos
curl -X PUT "https://api.exemplo.com/api/config/followup" \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "followup_active": true,
    "triggers": {
      "idle": {
        "active": true,
        "minutes": 60,
        "max_notifications": 2,
        "hits": [
          {
            "hit": 0,
            "generalInstructions": "Retome a conversa educadamente",
            "smartDecisionInstructions": "Apenas envie se o usuário demonstrou interesse"
          }
        ]
      }
    }
  }'

2. Configuração Avançada com Agentes

curl -X PUT "https://api.exemplo.com/api/config/followup" \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "triggers": {
      "idle": {
        "active": true,
        "minutes": 30,
        "max_notifications": 3,
        "hits": [
          {
            "hit": 0,
            "generalInstructions": "Primeira tentativa - seja sutil",
            "agentCalling": [
              {
                "schemaName": "public",
                "agentName": "verificar_interesse",
                "payload": {}
              }
            ]
          },
          {
            "hit": 1,
            "generalInstructions": "Segunda tentativa - ofereça ajuda específica",
            "minutes": 120
          }
        ]
      },
      "conditionalAgents": {
        "active": true,
        "decisions": [
          {
            "description": "Usuário pediu ajuda anteriormente",
            "content": "Vi que você precisava de ajuda. Estou aqui!",
            "agents": []
          }
        ]
      }
    }
  }'

3. Reset para Configuração Padrão

curl -X POST "https://api.exemplo.com/api/config/followup/reset" \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "keep_active": true,
    "custom_minutes": 45,
    "custom_max_notifications": 3
  }'

Códigos de Erro

  • 400: Dados inválidos ou validação falhou
  • 401: Token de autenticação inválido
  • 403: Acesso negado
  • 404: Recurso não encontrado (host ou configuração)
  • 409: Conflito (tentativa de criar configuração duplicada)
  • 500: Erro interno do servidor

Validações e Limites

Host Config:

  • general_level: 1-10
  • max_tokens: 100-32000
  • max_chunks: 1-20
  • max_anonymous_messages: ≥0
  • notification_email_l1: formato de email válido

Followup Config:

  • minutes: 1-10080 (1 minuto a 1 semana)
  • max_notifications: 1-50
  • hits: máximo 50 hits por configuração
  • Campos de string: tamanho razoável para evitar spam

Webhook Externo

O sistema suporta envio via webhook externo quando configurado no campo webhook_config de uma conversa específica. Consulte a documentação do followupV1.md para detalhes sobre esta funcionalionalidade.

Cache

As configurações são automaticamente cacheadas para melhor performance. O cache é limpo automaticamente quando configurações são atualizadas.

Logs e Auditoria

Todas as operações são logadas no sistema tolkyRequestLog para auditoria e troubleshooting. Os logs incluem:

  • Parâmetros de entrada
  • Resultados de validação
  • Decisões do SmartDecision
  • Ações executadas (envio, bloqueio, reagendamento)

Suporte

Para dúvidas ou problemas com a API, consulte os logs do sistema ou entre em contato com a equipe de desenvolvimento.