Pular para conteúdo

Tutorial: Automação de Outreach para Influencers no LinkedIn

Aprenda a automatizar todo o processo de descoberta, qualificação e contato com influencers e parceiros em potencial no LinkedIn, incluindo follow-up inteligente e tracking de respostas.

O Que Você Vai Construir

Um sistema completo de influencer outreach que: 1. Busca influencers baseado em critérios (nicho, seguidores, engajamento) 2. Enriquece perfis com dados adicionais 3. Qualifica e pontua automaticamente 4. Envia mensagens personalizadas de conexão 5. Faz follow-up automático em sequências 6. Rastreia respostas e engajamento 7. Organiza pipeline no CRM

Tempo estimado: 50 minutos Nível: Intermediário Impacto esperado: 10x mais outreach com mesma equipe, +40% taxa de resposta

O Que Você Vai Aprender

  • Integração com LinkedIn via PhantomBuster ou Apify
  • Web scraping ético e compliant
  • Qualificação automática de leads
  • Personalização em escala
  • Sequências de follow-up
  • Pipeline management

Pré-requisitos

  • ✅ Conta LinkedIn Sales Navigator (recomendado) ou Premium
  • ✅ PhantomBuster ou Apify account
  • ✅ CRM (HubSpot, Pipedrive, ou similar)
  • ✅ PostgreSQL

Parte 1: Estrutura de Dados

CREATE TABLE influencers (
  id SERIAL PRIMARY KEY,
  linkedin_url VARCHAR(500) UNIQUE NOT NULL,
  full_name VARCHAR(255),
  headline VARCHAR(500),
  location VARCHAR(255),

  -- Métricas
  followers_count INT,
  connections_count INT,
  posts_per_week INT,
  avg_engagement_rate DECIMAL(5,2),

  -- Qualificação
  relevance_score INT DEFAULT 0,
  quality_score INT DEFAULT 0,
  total_score INT DEFAULT 0,
  tier VARCHAR(20), -- A, B, C

  -- Enriquecimento
  company VARCHAR(255),
  job_title VARCHAR(255),
  industry VARCHAR(100),
  topics TEXT[],
  recent_posts JSONB,
  email VARCHAR(255),
  phone VARCHAR(50),

  -- Outreach
  status VARCHAR(50) DEFAULT 'new', -- new, contacted, responded, partnership, declined
  outreach_stage INT DEFAULT 0,
  first_contact_at TIMESTAMP,
  last_contact_at TIMESTAMP,
  responded_at TIMESTAMP,

  -- CRM
  crm_id VARCHAR(255),
  assigned_to VARCHAR(255),

  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE outreach_messages (
  id SERIAL PRIMARY KEY,
  influencer_id INT REFERENCES influencers(id),
  sequence_stage INT,
  message_type VARCHAR(50), -- connection_request, message, follow_up
  subject VARCHAR(500),
  content TEXT,
  sent_at TIMESTAMP,
  read_at TIMESTAMP,
  replied_at TIMESTAMP,
  reply_content TEXT,
  metadata JSONB
);

CREATE INDEX idx_influencers_status ON influencers(status);
CREATE INDEX idx_influencers_score ON influencers(total_score DESC);

Parte 2: Descoberta de Influencers

2.1. Buscar no LinkedIn (PhantomBuster)

// HTTP Request: "Buscar Influencers no LinkedIn"
{
  "method": "POST",
  "url": "https://api.phantombuster.com/api/v2/agents/launch",
  "headers": {
    "X-Phantombuster-Key": "{{ $credentials.phantombuster.apiKey }}"
  },
  "body": {
    "id": "{{ $vars.linkedinSearchAgentId }}",
    "argument": {
      "sessionCookie": "{{ $credentials.linkedin.sessionCookie }}",
      "searches": [
        "https://www.linkedin.com/search/results/people/?keywords=marketing%20automation&network=%5B%22S%22%5D",
        "https://www.linkedin.com/search/results/people/?keywords=growth%20hacking"
      ],
      "numberOfProfiles": 100
    },
    "saveResult": true
  }
}

2.2. Aguardar Conclusão

// Delay: 2 minutos para processar

// HTTP Request: "Buscar Resultados"
{
  "method": "GET",
  "url": "https://api.phantombuster.com/api/v2/containers/fetch-result-object",
  "headers": {
    "X-Phantombuster-Key": "{{ $credentials.phantombuster.apiKey }}"
  },
  "params": {
    "id": "{{ $nodes['buscar-influencers'].output.containerId }}"
  }
}

2.3. Processar Resultados

// Function Node: "Normalizar Dados"
{{
  (() => {
    const results = $nodes['buscar-resultados'].output.resultObject || [];

    return results.map(profile => ({
      linkedinUrl: profile.profileUrl,
      fullName: profile.fullName,
      headline: profile.headline,
      location: profile.location,
      followersCount: profile.connectionsCount || 0,
      company: profile.companyName,
      jobTitle: profile.jobTitle,
      industry: profile.industry
    })).filter(p => p.linkedinUrl); // Apenas perfis com URL
  })()
}}

Parte 3: Enriquecimento de Perfil

3.1. Extrair Dados Detalhados

// Loop/ForEach: Para cada influencer encontrado

// HTTP Request: "Scrape Profile Details"
{
  "method": "POST",
  "url": "https://api.phantombuster.com/api/v2/agents/launch",
  "headers": {
    "X-Phantombuster-Key": "{{ $credentials.phantombuster.apiKey }}"
  },
  "body": {
    "id": "{{ $vars.linkedinProfileScraperAgentId }}",
    "argument": {
      "sessionCookie": "{{ $credentials.linkedin.sessionCookie }}",
      "profileUrls": ["{{ $item.linkedinUrl }}"],
      "includeLastPosts": true
    }
  }
}

3.2. Calcular Métricas de Engajamento

// Function Node: "Calcular Engagement"
{{
  (() => {
    const posts = $nodes['scrape-profile'].output.posts || [];

    if (posts.length === 0) {
      return {
        postsPerWeek: 0,
        avgEngagementRate: 0,
        topics: []
      };
    }

    // Posts por semana (últimos 30 dias)
    const recentPosts = posts.filter(p => {
      const postDate = new Date(p.date);
      const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
      return postDate >= thirtyDaysAgo;
    });

    const postsPerWeek = (recentPosts.length / 30) * 7;

    // Taxa de engajamento média
    const totalEngagement = recentPosts.reduce((sum, post) => {
      return sum + (post.likes || 0) + (post.comments || 0) + (post.shares || 0);
    }, 0);

    const followersCount = $item.followersCount || 1;
    const avgEngagementRate = (totalEngagement / (recentPosts.length * followersCount)) * 100;

    // Extrair tópicos dos posts
    const allText = recentPosts.map(p => p.text || '').join(' ').toLowerCase();
    const topicKeywords = ['marketing', 'automation', 'saas', 'growth', 'sales', 'ai', 'tech'];
    const topics = topicKeywords.filter(keyword => allText.includes(keyword));

    return {
      postsPerWeek: Math.round(postsPerWeek * 10) / 10,
      avgEngagementRate: Math.round(avgEngagementRate * 100) / 100,
      topics,
      recentPosts: recentPosts.slice(0, 5).map(p => ({
        text: p.text?.substring(0, 200),
        likes: p.likes,
        comments: p.comments,
        date: p.date
      }))
    };
  })()
}}

3.3. Enriquecer Email (Hunter.io)

// HTTP Request: "Buscar Email"
{
  "method": "GET",
  "url": "https://api.hunter.io/v2/email-finder",
  "params": {
    "domain": "{{ $item.company ? $item.company.toLowerCase().replace(/\s/g, '') + '.com' : '' }}",
    "first_name": "{{ $item.fullName?.split(' ')[0] }}",
    "last_name": "{{ $item.fullName?.split(' ').slice(-1)[0] }}",
    "api_key": "{{ $credentials.hunter.apiKey }}"
  },
  "errorHandling": "continueOnFail"
}

Parte 4: Qualificação e Scoring

4.1. Calcular Score

// Function Node: "Calcular Score de Relevância"
{{
  (() => {
    let relevanceScore = 0;
    let qualityScore = 0;

    const profile = {
      ...$item,
      ...$nodes['calc-engagement'].output
    };

    // RELEVANCE SCORE (0-100)
    // Baseado em alinhamento com ICP

    // Tópicos relevantes (0-30 pontos)
    const targetTopics = ['marketing', 'automation', 'saas', 'growth'];
    const matchingTopics = (profile.topics || []).filter(t => targetTopics.includes(t));
    relevanceScore += Math.min(matchingTopics.length * 10, 30);

    // Headline relevante (0-20 pontos)
    const headline = (profile.headline || '').toLowerCase();
    if (headline.includes('cmo') || headline.includes('marketing director')) {
      relevanceScore += 20;
    } else if (headline.includes('marketing') || headline.includes('growth')) {
      relevanceScore += 15;
    }

    // Indústria target (0-20 pontos)
    const targetIndustries = ['software', 'technology', 'saas', 'internet'];
    if (targetIndustries.some(ind => (profile.industry || '').toLowerCase().includes(ind))) {
      relevanceScore += 20;
    }

    // Localização (0-15 pontos)
    const location = (profile.location || '').toLowerCase();
    if (location.includes('brazil') || location.includes('brasil')) {
      relevanceScore += 15;
    } else if (location.includes('latin america')) {
      relevanceScore += 10;
    }

    // Empresa conhecida (0-15 pontos)
    const knownCompanies = ['google', 'microsoft', 'amazon', 'meta', 'salesforce'];
    if (knownCompanies.some(comp => (profile.company || '').toLowerCase().includes(comp))) {
      relevanceScore += 15;
    }

    // QUALITY SCORE (0-100)
    // Baseado em alcance e engajamento

    // Seguidores (0-40 pontos)
    const followers = profile.followersCount || 0;
    if (followers >= 100000) qualityScore += 40;
    else if (followers >= 50000) qualityScore += 35;
    else if (followers >= 10000) qualityScore += 30;
    else if (followers >= 5000) qualityScore += 25;
    else if (followers >= 1000) qualityScore += 15;
    else qualityScore += 5;

    // Engajamento (0-40 pontos)
    const engagement = profile.avgEngagementRate || 0;
    if (engagement >= 5) qualityScore += 40;
    else if (engagement >= 3) qualityScore += 35;
    else if (engagement >= 2) qualityScore += 30;
    else if (engagement >= 1) qualityScore += 20;
    else qualityScore += 10;

    // Frequência de posts (0-20 pontos)
    const postsPerWeek = profile.postsPerWeek || 0;
    if (postsPerWeek >= 5) qualityScore += 20;
    else if (postsPerWeek >= 3) qualityScore += 15;
    else if (postsPerWeek >= 1) qualityScore += 10;
    else qualityScore += 5;

    // Score total (média ponderada)
    const totalScore = Math.round((relevanceScore * 0.6) + (qualityScore * 0.4));

    // Tier
    let tier;
    if (totalScore >= 80) tier = 'A';
    else if (totalScore >= 60) tier = 'B';
    else tier = 'C';

    return {
      relevanceScore,
      qualityScore,
      totalScore,
      tier,
      reasoning: {
        topicsMatch: matchingTopics.length,
        followers,
        engagement,
        postsPerWeek
      }
    };
  })()
}}

4.2. Filtrar por Score Mínimo

// If/Else: "Score Suficiente?"
{{
  $nodes['calcular-score'].output.totalScore >= 40 // Apenas B e A
}}

Parte 5: Salvar e Criar no CRM

5.1. Inserir no Banco

// Database Insert: "Salvar Influencer"
{
  "table": "influencers",
  "data": {
    "linkedin_url": "{{ $item.linkedinUrl }}",
    "full_name": "{{ $item.fullName }}",
    "headline": "{{ $item.headline }}",
    "location": "{{ $item.location }}",
    "followers_count": "{{ $item.followersCount }}",
    "posts_per_week": "{{ $nodes['calc-engagement'].output.postsPerWeek }}",
    "avg_engagement_rate": "{{ $nodes['calc-engagement'].output.avgEngagementRate }}",
    "relevance_score": "{{ $nodes['calcular-score'].output.relevanceScore }}",
    "quality_score": "{{ $nodes['calcular-score'].output.qualityScore }}",
    "total_score": "{{ $nodes['calcular-score'].output.totalScore }}",
    "tier": "{{ $nodes['calcular-score'].output.tier }}",
    "company": "{{ $item.company }}",
    "job_title": "{{ $item.jobTitle }}",
    "industry": "{{ $item.industry }}",
    "topics": "{{ JSON.stringify($nodes['calc-engagement'].output.topics) }}",
    "recent_posts": "{{ JSON.stringify($nodes['calc-engagement'].output.recentPosts) }}",
    "email": "{{ $nodes['buscar-email']?.output.data?.email || null }}",
    "status": "new"
  },
  "onConflict": "ignore",
  "returning": ["id"]
}

5.2. Criar Contato no HubSpot

// HTTP Request: "Criar no HubSpot"
{
  "method": "POST",
  "url": "https://api.hubapi.com/crm/v3/objects/contacts",
  "headers": {
    "Authorization": "Bearer {{ $credentials.hubspot.accessToken }}",
    "Content-Type": "application/json"
  },
  "body": {
    "properties": {
      "firstname": "{{ $item.fullName?.split(' ')[0] }}",
      "lastname": "{{ $item.fullName?.split(' ').slice(-1)[0] }}",
      "email": "{{ $nodes['buscar-email']?.output.data?.email }}",
      "company": "{{ $item.company }}",
      "jobtitle": "{{ $item.jobTitle }}",
      "linkedin_url": "{{ $item.linkedinUrl }}",
      "hs_lead_status": "{{ $nodes['calcular-score'].output.tier === 'A' ? 'OPEN' : 'NEW' }}",
      "lifecyclestage": "lead",

      // Custom properties
      "influencer_tier": "{{ $nodes['calcular-score'].output.tier }}",
      "influencer_score": "{{ $nodes['calcular-score'].output.totalScore }}",
      "linkedin_followers": "{{ $item.followersCount }}",
      "engagement_rate": "{{ $nodes['calc-engagement'].output.avgEngagementRate }}"
    }
  }
}

Parte 6: Outreach Automático

6.1. Flow de Outreach (Schedule Trigger - diário)

// Database Query: "Buscar Influencers Pendentes"
SELECT *
FROM influencers
WHERE status = 'new'
  AND tier IN ('A', 'B')
  AND outreach_stage = 0
ORDER BY total_score DESC
LIMIT 20; -- Limitar para não fazer spam

6.2. Personalizar Mensagem de Conexão

// Function Node: "Gerar Mensagem Personalizada"
{{
  (() => {
    const profile = $item;
    const firstName = profile.full_name?.split(' ')[0] || 'profissional';
    const topics = JSON.parse(profile.topics || '[]');
    const recentPosts = JSON.parse(profile.recent_posts || '[]');

    // Escolher gancho baseado no perfil
    let hook = '';

    if (recentPosts.length > 0) {
      const latestPost = recentPosts[0];
      hook = `Vi seu post sobre "${latestPost.text?.substring(0, 50)}..." e adorei a perspectiva!`;
    } else if (topics.includes('marketing')) {
      hook = 'Percebi que você tem grande expertise em marketing automation.';
    } else if (topics.includes('saas')) {
      hook = 'Vejo que você é referência no universo SaaS.';
    } else {
      hook = `Seu trabalho em ${profile.company || 'sua área'} chamou minha atenção.`;
    }

    // Templates por tier
    const templates = {
      A: `Olá, ${firstName}! ${hook} Gostaria de trocar ideias sobre automação e inovação. Vamos conectar?`,
      B: `Oi, ${firstName}! ${hook} Seria ótimo ter você na minha rede para trocarmos experiências.`,
      C: `Olá, ${firstName}! Gostaria de conectar com profissionais da área de ${profile.industry || 'tecnologia'}. Vamos?`
    };

    return {
      connectionMessage: templates[profile.tier] || templates.C,
      hook
    };
  })()
}}

6.3. Enviar Connection Request

// HTTP Request: "Enviar Convite no LinkedIn"
{
  "method": "POST",
  "url": "https://api.phantombuster.com/api/v2/agents/launch",
  "headers": {
    "X-Phantombuster-Key": "{{ $credentials.phantombuster.apiKey }}"
  },
  "body": {
    "id": "{{ $vars.linkedinConnectAgentId }}",
    "argument": {
      "sessionCookie": "{{ $credentials.linkedin.sessionCookie }}",
      "profileUrls": ["{{ $item.linkedin_url }}"],
      "message": "{{ $nodes['gerar-mensagem'].output.connectionMessage }}",
      "onlySecondCircle": false
    }
  }
}

6.4. Registrar Envio

// Database Insert: "Registrar Mensagem"
{
  "table": "outreach_messages",
  "data": {
    "influencer_id": "{{ $item.id }}",
    "sequence_stage": 0,
    "message_type": "connection_request",
    "content": "{{ $nodes['gerar-mensagem'].output.connectionMessage }}",
    "sent_at": "{{ new Date().toISOString() }}"
  }
}

// Database Update: "Atualizar Status"
{
  "table": "influencers",
  "data": {
    "status": "contacted",
    "outreach_stage": 1,
    "first_contact_at": "{{ new Date().toISOString() }}",
    "last_contact_at": "{{ new Date().toISOString() }}"
  },
  "where": {
    "id": "{{ $item.id }}"
  }
}

Parte 7: Sequência de Follow-up

7.1. Follow-up Automático (7 dias depois)

// Database Query: "Influencers Para Follow-up"
SELECT *
FROM influencers
WHERE status = 'contacted'
  AND outreach_stage = 1
  AND first_contact_at <= NOW() - INTERVAL '7 days'
  AND NOT EXISTS (
    SELECT 1 FROM outreach_messages
    WHERE influencer_id = influencers.id
      AND sequence_stage = 1
  )
LIMIT 10;

7.2. Mensagem de Follow-up

// Function Node: "Mensagem Follow-up"
{{
  const firstName = $item.full_name?.split(' ')[0];

  return {
    message: `Oi, ${firstName}! Espero que esteja bem. Gostaria de conversar sobre uma parceria que pode ser interessante para nós dois. Quando tiver um tempinho, me avise! 😊`
  };
}}

7.3. Enviar Mensagem Direta

// HTTP Request: "Enviar Mensagem LinkedIn"
{
  "method": "POST",
  "url": "https://api.phantombuster.com/api/v2/agents/launch",
  "headers": {
    "X-Phantombuster-Key": "{{ $credentials.phantombuster.apiKey }}"
  },
  "body": {
    "id": "{{ $vars.linkedinMessageAgentId }}",
    "argument": {
      "sessionCookie": "{{ $credentials.linkedin.sessionCookie }}",
      "profileUrls": ["{{ $item.linkedin_url }}"],
      "message": "{{ $nodes['mensagem-followup'].output.message }}"
    }
  }
}

Parte 8: Tracking de Respostas

8.1. Webhook para Respostas (Manual)

Como o LinkedIn não fornece webhooks, você pode:

  1. Usar PhantomBuster para scraping de mensagens recebidas
  2. Atualizar manualmente via interface
  3. Integrar com ferramentas como Lemlist
// Webhook Trigger: Resposta Recebida
// Processar e atualizar status

// Database Update: "Marcar Como Respondido"
{
  "table": "influencers",
  "data": {
    "status": "responded",
    "responded_at": "{{ new Date().toISOString() }}"
  },
  "where": {
    "linkedin_url": "{{ $trigger.body.profile_url }}"
  }
}

Teste e Métricas

-- Performance do outreach
SELECT
  tier,
  COUNT(*) as total,
  COUNT(*) FILTER (WHERE status = 'contacted') as contacted,
  COUNT(*) FILTER (WHERE status = 'responded') as responded,
  COUNT(*) FILTER (WHERE status = 'partnership') as partnerships,
  ROUND(
    COUNT(*) FILTER (WHERE status = 'responded')::DECIMAL /
    NULLIF(COUNT(*) FILTER (WHERE status = 'contacted'), 0) * 100,
    2
  ) as response_rate
FROM influencers
GROUP BY tier
ORDER BY tier;

Próximos Passos

  1. Campanha Multicanal - Engaje além do LinkedIn
  2. Lead Scoring - Qualifique parcerias
  3. Content Distribution - Distribua conteúdo via influencers

Última atualização: Janeiro 2025