Toda agencia dice que odia el email en frío. Nosotros también — hasta que nos dimos cuenta de que el problema no era el email en frío en sí, sino cada herramienta que intentamos usar para hacerlo. Las plantillas genéricas. La energía de "Hola {firstName}". Las plataformas de $300/mes que seguían requiriendo horas de trabajo manual. Así que hicimos lo que hacen los desarrolladores: construimos la nuestra.

Este no es un post de arquitectura teórica. Hemos estado ejecutando este sistema en producción durante meses, enviando miles de emails personalizados que realmente obtienen respuestas. Te voy a mostrar exactamente por qué lo construimos, cómo encajan las piezas y qué aprendimos de la manera difícil.

Tabla de Contenidos

Why We Built Our Own Cold Email System with Claude, Instantly & Supabase

El Problema Con el Outreach Listo para Usar

Intentamos los sospechosos habituales. Lemlist. Apollo. Woodpecker. Son herramientas bien para muchos casos de uso. Pero como una agencia de desarrollo web headless, nuestras necesidades de outreach eran específicas de formas que estas plataformas no podían manejar.

Aquí está lo que seguía rompiendo:

Los campos de personalización genéricos no son personalización. Insertar el nombre de la empresa de alguien y el título del trabajo en una plantilla no engaña a nadie en 2025. Necesitábamos emails que hicieran referencia al stack tecnológico real de un prospecto, sus problemas de rendimiento del sitio, o decisiones arquitectónicas específicas visibles en su sitio web público.

El paso de investigación fue el cuello de botella. Nuestro outreach con mejor desempeño siempre involucraba que alguien del equipo mirara realmente el sitio de un prospecto, lo ejecutara a través de PageSpeed Insights, verificara su framework, y escribiera algo específico. Eso tomó 10-15 minutos por lead. A escala, eso es un trabajo de tiempo completo.

Los datos vivían en demasiados lugares. Leads en una hoja de cálculo, secuencias de email en otra plataforma, resultados en un tercer dashboard. No podíamos construir bucles de retroalimentación porque nada hablaba con nada más.

Las integraciones de IA eran superficiales. Algunas plataformas agregaron características de "escritura por IA", pero eran básicamente wrappers de GPT que generaban la misma copia insípida que todos los demás estaban enviando. Sin capacidad de ingerir contexto personalizado, sin control sobre prompts, sin forma de construir cadenas de razonamiento multietapa.

Necesitábamos un sistema donde la IA hiciera la investigación, no solo la escritura.

Nuestro Tech Stack y Por Qué Lo Elegimos

Aquí está lo que terminamos después de algunas iteraciones:

Componente Herramienta Rol Costo Mensual
Búsqueda de leads y verificación de email Hunter.io Encontrar y verificar direcciones de email $49 (Starter)
Investigación de IA y redacción Claude (Anthropic API) Analizar prospectos, generar emails personalizados ~$30-60
Base de datos y orquestación Supabase Almacenar leads, gestionar estado, activar workflows $25 (Pro)
Envío de email y warmup Instantly.ai Capacidad de entrega, infraestructura de envío, warmup $30 (Growth)
Pegamento de automatización Custom Edge Functions + Cron Conectar todo junto $0 (incluido en Supabase)

Evaluamos un montón de alternativas. Aquí está la versión corta de por qué elegimos lo que elegimos:

Claude sobre GPT-4: Probamos ambos extensamente. Claude 3.5 Sonnet (y ahora Claude 4 Sonnet en 2025) produjo consistentemente emails que sonaban más naturales y menos "estilo IA". También fue mejor siguiendo prompts de sistema complejos sin desviarse. El precio fue comparable, pero la ventana de contexto más larga de Claude significaba que podíamos ingerir más datos de investigación por prospecto.

Supabase sobre Airtable o una configuración Postgres personalizada: Necesitábamos una base de datos real con seguridad a nivel de fila, pero no queríamos gestionar infraestructura. Supabase nos dio Postgres, Edge Functions, trabajos Cron y un dashboard decente — todo en un lugar. Usamos Supabase extensamente para proyectos de clientes también, así que el equipo ya lo conocía bien.

Instantly sobre Lemlist o Smartlead: La red de warmup de Instantly es genuinamente buena, su API es limpia y el precio tiene sentido para nuestro volumen. No necesitamos el constructor de secuencias incorporado de Instantly porque manejamos la lógica de secuenciación nosotros mismos.

Hunter sobre Apollo o Snov.io: La verificación de email de Hunter es consistentemente la más precisa que hemos probado. Su API de búsqueda de dominio es rápida y la calidad de datos es alta. Apollo tiene más puntos de datos, pero encontramos su precisión de email más baja, lo que mata la capacidad de entrega.

Descripción General de la Arquitectura

El sistema funciona en cinco etapas, cada una ejecutándose independientemente:

[Lead Sources] → [Hunter Enrichment] → [Supabase DB] → [Claude Research + Copy] → [Instantly Sending]
     ↑                                       ↑                                           |
     |                                       |                                           |
     +----------- Feedback Loop -------------+-------------------------------------------+
  1. Ingerir: Alimentamos dominios de prospectos desde varias fuentes (listas manuales, scrapers, datos de referencia)
  2. Enriquecer: Hunter encuentra contactos y verifica emails
  3. Almacenar: Todo aterriza en Supabase con seguimiento de estado
  4. Investigar + Escribir: Claude analiza cada prospecto y genera copia personalizada
  5. Enviar: Los emails aprobados se envían a campañas de Instantly
  6. Aprender: Los datos de respuesta fluyen de vuelta a Supabase, informando personalización futura

Cada etapa está desacoplada. Si la API de Hunter se cae, la cola de enriquecimiento simplemente se acumula — no rompe el envío. Si queremos intercambiar Claude por un modelo diferente, cambiamos una función.

Why We Built Our Own Cold Email System with Claude, Instantly & Supabase - architecture

Encontrando y Enriqueciendo Leads con Hunter

Hunter.io maneja dos trabajos críticos: encontrar a la persona correcta en una empresa y verificar que su email realmente funcione.

Aquí está una versión simplificada de nuestra función de enriquecimiento:

import { createClient } from '@supabase/supabase-js';

const HUNTER_API_KEY = Deno.env.get('HUNTER_API_KEY');

async function enrichLead(domain: string) {
  // Búsqueda de dominio para encontrar tomadores de decisiones
  const searchRes = await fetch(
    `https://api.hunter.io/v2/domain-search?domain=${domain}&department=executive,it&api_key=${HUNTER_API_KEY}`
  );
  const searchData = await searchRes.json();
  
  const contacts = searchData.data.emails
    .filter((e: any) => e.confidence > 70)
    .slice(0, 3); // Top 3 contactos por dominio
  
  // Verificar cada email
  for (const contact of contacts) {
    const verifyRes = await fetch(
      `https://api.hunter.io/v2/email-verifier?email=${contact.value}&api_key=${HUNTER_API_KEY}`
    );
    const verifyData = await verifyRes.json();
    
    if (verifyData.data.status === 'valid') {
      await supabase.from('leads').insert({
        domain,
        email: contact.value,
        first_name: contact.first_name,
        last_name: contact.last_name,
        position: contact.position,
        confidence: contact.confidence,
        status: 'enriched',
        enriched_at: new Date().toISOString()
      });
    }
  }
}

Filtramos los departamentos de executive e it porque esos son nuestros compradores — CTOs, VPs de Ingeniería, fundadores técnicos. El filtrado de departamento de Hunter no es perfecto, pero elimina mucho ruido.

Una cosa que aprendimos: nunca omitas la verificación de email. Incluso con los scores de confianza de Hunter, seguimos verificando cada dirección. Una tasa de rechazo por encima del 3% destrozará la reputación de tu dominio de envío. Hemos visto dominios pasar de 95% de colocación en bandeja de entrada a 40% carpeta de spam por un lote malo.

Ejecutamos alrededor de 500 créditos de búsquedas de Hunter por semana, que encaja cómodamente en su plan Starter.

Personalización de IA con Claude

Aquí es donde las cosas se ponen interesantes. La integración de Claude no es solo "escribe un email en frío." Es un pipeline multietapa de investigación y escritura.

Paso 1: Análisis del Sitio Web

Antes de que Claude escriba nada, le alimentamos datos sobre el sitio web del prospecto. Raspamos información básica usando una función ligera:

async function analyzeProspectSite(domain: string) {
  // Obtener homepage y páginas clave
  const homepage = await fetch(`https://${domain}`);
  const html = await homepage.text();
  
  // Extraer señales tecnológicas del HTML
  const signals = {
    hasNextJs: html.includes('__next') || html.includes('_next/static'),
    hasReact: html.includes('react') || html.includes('__REACT'),
    hasWordPress: html.includes('wp-content') || html.includes('wp-includes'),
    hasShopify: html.includes('shopify') || html.includes('cdn.shopify'),
    hasGatsby: html.includes('gatsby'),
    usesJQuery: html.includes('jquery'),
    metaGenerator: extractMeta(html, 'generator'),
    pageSize: html.length,
    // ... más señales
  };
  
  // Ejecutar verificación de PageSpeed vía API
  const psiData = await fetchPageSpeedInsights(domain);
  
  return {
    ...signals,
    performanceScore: psiData.lighthouseResult.categories.performance.score * 100,
    lcp: psiData.lighthouseResult.audits['largest-contentful-paint'].numericValue,
    cls: psiData.lighthouseResult.audits['cumulative-layout-shift'].numericValue,
    fid: psiData.lighthouseResult.audits['max-potential-fid'].numericValue
  };
}

Esto le da a Claude datos reales con los que trabajar. No "Hola, notí que tu compañía hace X" — más como "Tu homepage LCP es 4.2 segundos y seguía ejecutando jQuery junto con React, que está agregando 90KB a tu bundle inicial."

Paso 2: Prompt de Investigación de Claude

Usamos la API de Claude con un prompt de sistema cuidadosamente elaborado. Aquí está una versión simplificada:

const researchPrompt = `Eres un desarrollador web senior analizando el sitio web de un prospecto para una agencia de desarrollo headless. Dado los siguientes datos técnicos sobre su sitio, identifica:

1. Su stack tecnológico actual (sé específico)
2. 2-3 problemas concretos de rendimiento o arquitectura
3. Lo que una migración a una arquitectura headless moderna podría mejorar
4. Una observación específica y no obvia que demuestre análisis genuino

NO seas genérico. Si no puedes encontrar algo específico, dilo.
NO menciones "en el panorama digital de hoy" o frases similares de relleno.
Sé directo y técnico.

Datos del sitio:
${JSON.stringify(siteAnalysis, null, 2)}

Prospecto: ${lead.first_name} ${lead.last_name}, ${lead.position} en ${lead.domain}`;

const research = await anthropic.messages.create({
  model: 'claude-sonnet-4-20250514',
  max_tokens: 1000,
  messages: [{ role: 'user', content: researchPrompt }]
});

Paso 3: Generación de Email

La salida de investigación se alimenta a una segunda llamada de Claude que escribe el email real. Separar investigación de escritura fue un insight clave — cuando intentamos hacer ambas en un prompt, los emails eran peores. Claude omitía la investigación para llegar más rápido a la escritura.

const emailPrompt = `Escribe un email en frío de un desarrollador senior en una agencia de desarrollo web headless.

Notas de investigación:
${research.content[0].text}

Reglas:
- 4-6 oraciones máximo. Cada oración debe merecer su lugar.
- Comienza con la observación técnica más específica.
- Sin halagos. Sin "Me encanta lo que estás haciendo."
- Un CTA claro: pregunta si querrían ver una auditoría de rendimiento.
- Suena como un desarrollador, no como un vendedor.
- Usa su primer nombre. Sin apellido en el saludo.
- Línea de asunto: corta, específica para su problema técnico, minúsculas.`;

¿El resultado? Emails que abren con cosas como "Tu tienda Shopify Plus está renderizando en el servidor páginas de producto que podrían generarse estáticamente — eso está agregando 2+ segundos a cada vista de producto" en lugar de "Noté tu impresionante compañía y quería comunicarme."

Supabase como Capa de Orquestación

Supabase es el cerebro de la operación. Aquí está nuestro esquema principal:

create table leads (
  id uuid primary key default gen_random_uuid(),
  domain text not null,
  email text,
  first_name text,
  last_name text,
  position text,
  confidence int,
  status text default 'new', -- new, enriched, researched, drafted, approved, sent, replied, bounced
  site_analysis jsonb,
  research_notes text,
  email_subject text,
  email_body text,
  instantly_campaign_id text,
  sent_at timestamptz,
  opened_at timestamptz,
  replied_at timestamptz,
  created_at timestamptz default now(),
  updated_at timestamptz default now()
);

create index idx_leads_status on leads(status);
create index idx_leads_domain on leads(domain);

El campo status lo dirige todo. Los trabajos Cron de Supabase se ejecutan cada 15 minutos, recogiendo leads en cada etapa y empujándolos al siguiente:

-- Cron: Procesar leads enriquecidos a través de investigación Claude
select cron.schedule(
  'process-research',
  '*/15 * * * *',
  $$select net.http_post(
    'https://your-project.supabase.co/functions/v1/process-research',
    '{}',
    '{"Authorization": "Bearer your-service-key"}'::jsonb
  )$$
);

Procesamos por lotes 20 leads por ejecución para mantenernos dentro de los límites de velocidad de Claude y mantener costos predecibles.

La columna site_analysis JSONB es increíblemente útil. Podemos hacer consultas en todos nuestros leads para encontrar patrones — como "muéstrame todos los leads ejecutando WordPress con un score de rendimiento por debajo de 50" — y construir campañas dirigidas a partir de esos segmentos.

Enviando a Escala con Instantly

Instantly maneja la entrega real de email. Empujamos emails aprobados vía su API:

async function pushToInstantly(lead: Lead) {
  const response = await fetch('https://api.instantly.ai/api/v1/lead/add', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      api_key: INSTANTLY_API_KEY,
      campaign_id: lead.instantly_campaign_id,
      skip_if_in_workspace: true,
      leads: [{
        email: lead.email,
        first_name: lead.first_name,
        last_name: lead.last_name,
        company_name: lead.domain,
        personalization_1: lead.email_subject,
        personalization_2: lead.email_body
      }]
    })
  });
  
  if (response.ok) {
    await supabase
      .from('leads')
      .update({ status: 'sent', sent_at: new Date().toISOString() })
      .eq('id', lead.id);
  }
}

Las plantillas de campaña de Instantly usan variables {{personalization_1}} y {{personalization_2}}, que se asignan a nuestro asunto y cuerpo generados por Claude. La campaña en sí es solo una cáscara — toda la inteligencia vive en nuestro sistema.

Ejecutamos 3 cuentas de envío a través del warmup de Instantly durante al menos 2 semanas antes de enviar cualquier outreach. El warmup de dominio no es opcional. Aprendimos esto de la manera difícil con nuestro primer dominio siendo marcado en una semana.

Configuración de Capacidad de Entrega

Nuestra infraestructura de envío:

  • 3 dominios (variaciones de nuestra marca, no nuestro dominio principal)
  • SPF, DKIM y DMARC configurados en todos ellos
  • Cuentas de Google Workspace (no Outlook — Google maneja mejor el email en frío en nuestras pruebas)
  • Warmup de Instantly ejecutándose continuamente, incluso en días de envío activo
  • Máximo 35 emails por cuenta por día
  • Intervalos de envío aleatorios entre 3-7 minutos

El Pegamento de Automatización

Supabase Edge Functions conectan todo. Aquí está el flujo en pseudocódigo:

Cada 15 minutos:
  1. Recoger leads con status='new', ejecutar enriquecimiento Hunter → status='enriched'
  2. Recoger leads con status='enriched', ejecutar análisis de sitio → status='analyzed'
  3. Recoger leads con status='analyzed', ejecutar investigación Claude + generación de email → status='drafted'
  4. (Humano revisa emails redactados en dashboard de Supabase)
  5. Recoger leads con status='approved', empujar a Instantly → status='sent'
  6. Extraer datos de engagement de API de Instantly → actualizar opened_at, replied_at

El paso 4 es importante. No automatizamos completamente el envío. Cada email recibe una revisión humana antes de salir. Esto atrapa la ocasional alucinación (Claude una vez afirmó que un sitio fue construido con Remix cuando claramente era Next.js) y nos permite agregar toques personales.

El paso de revisión toma alrededor de 2-3 segundos por email ya que Claude hace correctamente el 95% del trabajo. Aprobamos en lotes usando una vista de dashboard de Supabase simple.

Resultados y Lo Que Aprendimos

Hemos estado ejecutando este sistema desde Q1 2025. Aquí están números reales:

Métrica Nuestro Sistema Promedio Industrial (2025)
Tasa de Apertura 62% 24%
Tasa de Respuesta 8.4% 1-3%
Tasa de Respuesta Positiva 4.1% 0.5-1%
Tasa de Rechazo 0.8% 3-5%
Costo Por Lead Contactado $0.18 $0.50-2.00
Tiempo Por Lead (humano) ~5 segundos (revisión) 10-15 minutos

La tasa de apertura es alta porque las líneas de asunto son específicas. "tu shopify lcp es 4.2s" se abre. "pregunta rápida" no.

La tasa de respuesta es alta porque los emails demuestran conocimiento técnico genuino. Cuando un CTO lee un email que identifica correctamente su stack tecnológico y un problema de rendimiento real, es más probable que se involucre — incluso si sabe que es outreach.

Lo Que No Funcionó

Envío completamente automatizado (sin revisión humana): Intentamos esto durante dos semanas. Claude alucinó detalles de stack tecnológico alrededor del 5% del tiempo. Esa es una tasa de error baja para un LLM, pero enviar un email que dice "tu app React" a alguien ejecutando Vue es peor que enviar un email genérico. El daño de confianza es real.

Emails largos: Nuestros primeros prompts de Claude generaron emails de 8-10 oraciones. Las tasas de respuesta fueron la mitad de lo que vemos ahora con 4-6 oraciones. Más corto siempre es mejor.

Enviar más de 40 emails por día por cuenta: La capacidad de entrega se desploma. 30-35 es el punto ideal en 2025.

Usar Claude para seguimientos basados en aperturas: Intentamos generar emails de seguimiento activados por aperturas. Los seguimientos se sentían insistentes y la conversión no valía el costo. Ahora enviamos un seguimiento simple y no basado en IA tres días después.

Desglose de Costos

Aquí está lo que esto nos cuesta mensualmente, procesando aproximadamente 2,000 leads:

Servicio Costo Mensual Notas
Hunter.io (Starter) $49 500 búsquedas + verificaciones
Anthropic API (Claude) $45 ~2,000 investigaciones + generaciones de email
Supabase (Pro) $25 Base de datos, Edge Functions, Cron
Instantly (Growth) $30 Envío, warmup, analytics
Google Workspace (3 cuentas) $21 Infraestructura de envío
Dominios (3) $10 Costo anual amortizado
Total ~$180 $0.09 por lead procesado

Compara eso con el plan de Apollo de $79/mes (enriquecimiento limitado, secuencias básicas) o Lemlist de $69/mes por asiento. Estamos gastando menos y obteniendo resultados dramáticamente mejores porque la personalización es real, no basada en plantillas.

Para contexto, este sistema ha generado directamente leads que se convirtieron en proyectos de desarrollo Next.js y desarrollo Astro que valen 50-100x el costo mensual. El ROI es absurdo.

Preguntas Frecuentes

¿Cuánto tiempo tomó construir este sistema? La primera versión de trabajo tomó alrededor de dos semanas de esfuerzo a tiempo parcial — tal vez 40 horas en total. Hemos iterado en él continuamente desde entonces, principalmente ajustando prompts de Claude y agregando manejo de casos extremos. Si estás cómodo con Supabase Edge Functions y APIs REST, podrías tener una versión básica ejecutándose en un fin de semana.

¿No es esto simplemente spam con pasos adicionales? Buena pregunta. La diferencia es que cada email contiene una observación técnica genuina sobre el sitio web del receptor. No estamos enviando "saltemos a una llamada" a 10,000 personas. Estamos enviando insights específicos y útiles a una lista dirigida de personas que realmente tienen los problemas que resolvemos. Nuestra tasa de desuscripción es inferior al 0.5%, lo que sugiere que los receptores no lo ven como spam tampoco.

¿Por qué Claude en lugar de GPT-4 o Gemini? Probamos los tres. Claude siguió nuestros prompts de sistema más confiablemente — especialmente las restricciones como "no seas genérico" y "no uses frases de relleno." GPT-4 derivaría hacia lenguaje de ventas incluso con instrucciones explícitas de no hacerlo. Gemini fue rápido pero la calidad de salida fue inconsistente. Esto puede cambiar a medida que los modelos evolucionan, y nuestro sistema está diseñado para intercambiar modelos fácilmente.

¿Cómo manejas el cumplimiento de GDPR y CAN-SPAM? Todo nuestro outreach se dirige a emails comerciales (no personales), incluye nuestra dirección física y tiene un opt-out claro en cada email. Para GDPR, procesamos datos bajo interés legítimo para outreach B2B, mantenemos registros de actividades de procesamiento y honramos solicitudes de eliminación inmediatamente vía un webhook automatizado. También eliminamos leads más antiguos de 90 días de nuestra base de datos automáticamente. Habla con un abogado para tu situación específica — esto no es asesoramiento legal.

¿Qué sucede cuando un lead responde? Las respuestas fluyen de vuelta desde la API de Instantly a Supabase. Recibimos una notificación de Slack para cada respuesta, y un humano toma el control de la conversación inmediatamente. Nunca usamos IA para manejo de respuestas. Una vez que alguien se involucra, merece una persona real. Los prospectos interesados se dirigen a nuestra página de contacto o directamente a un enlace de reserva de llamada.

¿Puede este enfoque funcionar para servicios no técnicos? La pieza de análisis de sitio es específica para desarrollo web, pero el patrón arquitectónico — enriquecer leads, usar IA para investigar y personalizar, enviar a través de una herramienta dedicada — funciona para cualquier outreach B2B. Solo necesitarías diferentes entradas de investigación. Una agencia de diseño podría analizar patrones de diseño visual y UX. Una agencia de marketing podría extraer métricas de SEO. La clave es alimentar a Claude datos reales, no pedirle que invente cosas.

¿Cuál es la parte más difícil de mantener este sistema? Mantenimiento de prompts. A medida que los modelos de Claude se actualizan, los prompts que funcionaban perfectamente a veces necesitan ajuste. También pasamos tiempo monitoreando la capacidad de entrega de email — verificando Google Postmaster Tools, observando picos de tasa de spam, rotando cuentas de envío. Es tal vez 2-3 horas por semana de mantenimiento total.

¿Venderías esto como un producto? Hemos pensado en ello, pero honestamente la ventaja competitiva es demasiado valiosa. Si cada agencia ejecutara este sistema exacto, la efectividad caería porque los receptores comenzarían a ver emails investigados por IA en todas partes. Por ahora, lo estamos manteniendo como una herramienta interna. Si quieres ayuda construyendo algo similar para tu negocio, ponte en contacto — hemos ayudado a algunos clientes a configurar sistemas similares como parte de nuestro trabajo de desarrollo headless CMS.