Pourquoi nous avons construit notre propre système d'email froid avec Claude, Instantly & Supabase
Chaque agence dit qu'elle déteste les emails froids. Nous aussi — jusqu'à ce que nous réalisions que le problème n'était pas les emails froids eux-mêmes, c'était chaque outil que nous avons essayé d'utiliser pour cela. Les templates génériques. L'énergie « Salut {firstName} ». Les plateformes à 300 $/mois qui nécessitaient toujours des heures de travail manuel. Alors nous avons fait ce que les développeurs font : nous avons construit le nôtre.
Ce n'est pas un post d'architecture théorique. Nous avons exécuté ce système en production pendant des mois, envoyant des milliers d'emails personnalisés qui reçoivent réellement des réponses. Je vais vous montrer exactement pourquoi nous l'avons construit, comment les pièces s'emboîtent, et ce que nous avons appris à la dure.
Table des matières
- Le problème avec les outils de prospection standard
- Notre pile technologique et pourquoi nous l'avons choisie
- Aperçu de l'architecture
- Trouver et enrichir les prospects avec Hunter
- Personnalisation IA avec Claude
- Supabase comme couche d'orchestration
- Envoi à l'échelle avec Instantly
- Le ciment d'automatisation
- Résultats et ce que nous avons appris
- Répartition des coûts
- FAQ

Le problème avec les outils de prospection standard
Nous avons essayé les suspects habituels. Lemlist. Apollo. Woodpecker. Ce sont de bons outils pour de nombreux cas d'usage. Mais en tant qu'agence de développement web sans tête, nos besoins de prospection étaient spécifiques d'une manière que ces plateformes ne pouvaient pas gérer.
Voici ce qui s'est toujours cassé :
Les champs de personnalisation génériques ne sont pas de la personnalisation. Insérer le nom d'une entreprise et le titre d'une personne dans un template ne trompe personne en 2025. Nous avions besoin d'emails qui référencent la véritable pile technologique d'un prospect, ses problèmes de performance du site, ou des décisions architecturales spécifiques visibles sur son site public.
L'étape de recherche était le goulot d'étranglement. Notre prospection la plus performante impliquait toujours que quelqu'un d'une équipe regarde réellement le site d'un prospect, le lance à travers PageSpeed Insights, vérifie son framework, et écrive quelque chose de spécifique. Cela a pris 10-15 minutes par prospect. À l'échelle, c'est un travail à temps plein.
Les données vivaient dans trop de places. Les prospects dans une feuille de calcul, les séquences d'emails dans une autre plateforme, les résultats dans un troisième tableau de bord. Nous ne pouvions pas construire de boucles de rétroaction parce que rien ne communiquait avec rien d'autre.
Les intégrations IA étaient superficielles. Certaines plateformes ajoutaient des fonctionnalités « d'écriture IA », mais c'étaient essentiellement des wrappers GPT qui généraient les mêmes copies fades que tout le monde d'autre envoyait. Aucune capacité à alimenter des contextes personnalisés, aucun contrôle sur les prompts, aucun moyen de construire des chaînes de raisonnement multi-étapes.
Nous avions besoin d'un système où l'IA faisait la recherche, pas seulement l'écriture.
Notre pile technologique et pourquoi nous l'avons choisie
Voici ce sur quoi nous nous sommes installés après quelques itérations :
| Composant | Outil | Rôle | Coût mensuel |
|---|---|---|---|
| Recherche de prospects et vérification d'emails | Hunter.io | Trouver et vérifier les adresses email | $49 (Starter) |
| Recherche et rédaction IA | Claude (API Anthropic) | Analyser les prospects, générer des emails personnalisés | ~$30-60 |
| Base de données et orchestration | Supabase | Stocker les prospects, gérer l'état, déclencher les workflows | $25 (Pro) |
| Envoi d'emails et échauffement | Instantly.ai | Livrabilité, infrastructure d'envoi, échauffement | $30 (Growth) |
| Ciment d'automatisation | Edge Functions personnalisées + Cron | Connecter tout ensemble | $0 (inclus dans Supabase) |
Nous avons évalué un tas d'alternatives. Voici la version courte de pourquoi nous avons choisi ce que nous avons choisi :
Claude au lieu de GPT-4 : Nous avons testé les deux largement. Claude 3.5 Sonnet (et maintenant Claude 4 Sonnet en 2025) a produit de manière constante des emails qui sonnaient plus naturels et moins « générés par IA ». C'était également meilleur pour suivre les prompts système complexes sans dévier. La tarification était comparable, mais la fenêtre de contexte plus longue de Claude signifiait que nous pouvions alimenter plus de données de recherche par prospect.
Supabase au lieu d'Airtable ou une configuration Postgres personnalisée : Nous avions besoin d'une vraie base de données avec sécurité au niveau des lignes, mais nous ne voulions pas gérer l'infrastructure. Supabase nous a donné Postgres, Edge Functions, des tâches Cron, et un dashboard décent — tout en un seul endroit. Nous utilisons largement Supabase pour les projets clients aussi, donc l'équipe le connaissait déjà bien.
Instantly au lieu de Lemlist ou Smartlead : Le réseau d'échauffement d'Instantly est véritablement bon, leur API est propre, et la tarification avait du sens pour notre volume. Nous n'avons pas besoin du générateur de séquences intégré d'Instantly parce que nous gérons nous-mêmes la logique de séquençage.
Hunter au lieu d'Apollo ou Snov.io : La vérification d'emails de Hunter est systématiquement la plus précise que nous ayons testée. Leur API de recherche de domaine est rapide et la qualité des données est élevée. Apollo a plus de points de données, mais nous avons constaté que la précision de leurs emails était inférieure, ce qui tue la livrabilité.
Aperçu de l'architecture
Le système fonctionne en cinq étapes, chacune s'exécutant indépendamment :
[Sources de prospects] → [Enrichissement Hunter] → [Base de données Supabase] → [Recherche Claude + Rédaction] → [Envoi Instantly]
↑ ↑ |
| | |
+----------- Boucle de rétroaction -----+-------------------------------------------+
- Ingestion : Nous alimentons les domaines des prospects à partir de diverses sources (listes manuelles, scrapers, données de référence)
- Enrichissement : Hunter trouve les contacts et vérifie les emails
- Stockage : Tout est stocké dans Supabase avec le suivi du statut
- Recherche + Rédaction : Claude analyse chaque prospect et génère une copie personnalisée
- Envoi : Les emails approuvés se poussent vers les campagnes Instantly
- Apprentissage : Les données de réponse reviennent à Supabase, informant la personnalisation future
Chaque étape est découplée. Si l'API de Hunter s'arrête, la file d'enrichissement s'accumule simplement — elle ne casse pas l'envoi. Si nous voulons remplacer Claude par un modèle différent, nous changeons une fonction.

Trouver et enrichir les prospects avec Hunter
Hunter.io gère deux travaux critiques : trouver la bonne personne dans une entreprise et vérifier que son email fonctionne réellement.
Voici une version simplifiée de notre fonction d'enrichissement :
import { createClient } from '@supabase/supabase-js';
const HUNTER_API_KEY = Deno.env.get('HUNTER_API_KEY');
async function enrichLead(domain: string) {
// Recherche de domaine pour trouver les décideurs
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 contacts par domaine
// Vérifier chaque 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()
});
}
}
}
Nous filtrons les départements executive et it parce que ce sont nos acheteurs — CTOs, VPs d'ingénierie, fondateurs techniques. Le filtrage de département de Hunter n'est pas parfait, mais cela élimine beaucoup de bruit.
Une chose que nous avons apprise : ne jamais sauter la vérification d'emails. Même avec les scores de confiance de Hunter, nous vérifions toujours chaque adresse. Un taux de bounce supérieur à 3% ruinera la réputation du domaine d'envoi de votre domaine. Nous avons vu des domaines passer de 95% de placement à la boîte de réception à 40% de dossier spam en raison d'un seul mauvais lot.
Nous exécutons environ 500 crédits de recherches Hunter par semaine, ce qui rentre confortablement dans leur plan Starter.
Personnalisation IA avec Claude
C'est là que les choses deviennent intéressantes. L'intégration Claude n'est pas juste « écris-moi un email froid ». C'est un pipeline de recherche et de rédaction multi-étapes.
Étape 1 : Analyse du site
Avant que Claude n'écrive quoi que ce soit, nous lui alimentons des données sur le site du prospect. Nous raclons les informations de base en utilisant une fonction légère :
async function analyzeProspectSite(domain: string) {
// Récupérer la page d'accueil et les pages clés
const homepage = await fetch(`https://${domain}`);
const html = await homepage.text();
// Extraire les signaux technologiques du 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,
// ... plus de signaux
};
// Lancer la vérification PageSpeed via l'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
};
}
Cela donne à Claude des données réelles avec lesquelles travailler. Pas « Salut, j'ai remarqué que votre entreprise fait X » — plutôt « Votre page d'accueil LCP est de 4,2 secondes et vous exécutez toujours jQuery aux côtés de React, ce qui ajoute 90 KB à votre bundle initial. »
Étape 2 : Prompt de recherche Claude
Nous utilisons l'API Claude avec un prompt système soigneusement élaboré. Voici une version simplifiée :
const researchPrompt = `Vous êtes un développeur web senior analysant le site d'un prospect pour une agence de développement sans tête. Données les données technologiques suivantes sur son site, identifiez :
1. Leur pile technologique actuelle (soyez spécifique)
2. 2-3 problèmes concrets de performance ou d'architecture
3. Ce qu'une migration vers une architecture sans tête moderne pourrait améliorer
4. Une observation spécifique et non-évidente qui montre une analyse genuine
NE PAS être générique. Si vous ne pouvez pas trouver quelque chose de spécifique, dites-le.
NE PAS mentionner « dans le paysage numérique d'aujourd'hui » ou un remplissage similaire.
Soyez direct et technique.
Données du site :
${JSON.stringify(siteAnalysis, null, 2)}
Prospect : ${lead.first_name} ${lead.last_name}, ${lead.position} à ${lead.domain}`;
const research = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 1000,
messages: [{ role: 'user', content: researchPrompt }]
});
Étape 3 : Génération d'email
La sortie de recherche alimente un deuxième appel Claude qui écrit l'email réel. Séparer la recherche de la rédaction a été une perspective clé — quand nous avons essayé de faire les deux dans un seul prompt, les emails étaient pires. Claude saisirait la recherche pour aller plus vite vers la rédaction.
const emailPrompt = `Écrivez un email froid d'un développeur senior dans une agence web sans tête.
Notes de recherche :
${research.content[0].text}
Règles :
- Maximum 4-6 phrases. Chaque phrase doit justifier sa place.
- Commencez par l'observation technique la plus spécifique.
- Pas de flaterie. Pas « J'adore ce que vous faites. »
- Un CTA clair : demandez s'ils aimeraient voir un audit de performance.
- Sonnez comme un développeur, pas comme un vendeur.
- Utilisez leur prénom. Pas de nom de famille dans la salutation.
- Ligne d'objet : courte, spécifique à leur problème technologique, minuscule.`;
Le résultat ? Des emails qui s'ouvrent avec des choses comme « Votre boutique Shopify Plus effectue une rendu côté serveur des pages de produits qui pourraient être générées statiquement — cela ajoute 2+ secondes à chaque vue de produit » au lieu de « J'ai remarqué votre entreprise impressionnante et je voulais vous contacter. »
Supabase comme couche d'orchestration
Supabase est le cerveau de l'opération. Voici notre schéma 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);
Le champ status conduit tout. Les tâches Cron Supabase s'exécutent toutes les 15 minutes, récupérant les prospects à chaque étape et les poussant à la suivante :
-- Cron : Traiter les prospects enrichis via la recherche 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
)$$
);
Nous traitons par lots 20 prospects par exécution pour rester dans les limites de taux de Claude et maintenir les coûts prévisibles.
La colonne JSONB site_analysis est incroyablement utile. Nous pouvons interroger l'ensemble de nos prospects pour trouver des motifs — comme « montrez-moi tous les prospects exécutant WordPress avec un score de performance inférieur à 50 » — et construire des campagnes ciblées à partir de ces segments.
Envoi à l'échelle avec Instantly
Instantly gère la livraison d'emails réelle. Nous envoyons les emails approuvés via leur 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);
}
}
Les templates de campagne d'Instantly utilisent les variables {{personalization_1}} et {{personalization_2}}, qui correspondent à notre sujet et corps générés par Claude. La campagne elle-même est juste une coquille — toute l'intelligence vit dans notre système.
Nous exécutons 3 comptes d'envoi via le préchauffage d'Instantly pendant au moins 2 semaines avant d'envoyer toute prospection. Le préchauffage de domaine n'est pas facultatif. Nous l'avons appris à la dure avec notre premier domaine signalé en une semaine.
Configuration de la livrabilité
Notre infrastructure d'envoi :
- 3 domaines (variations de notre marque, pas notre domaine principal)
- SPF, DKIM et DMARC configurés sur tous
- Comptes Google Workspace (pas Outlook — Google gère mieux les emails froids selon nos tests)
- Préchauffage Instantly fonctionnant en continu, même les jours d'envoi actifs
- Maximum 35 emails par compte par jour
- Intervalles d'envoi aléatoires entre 3-7 minutes
Le ciment d'automatisation
Les Edge Functions Supabase connectent tout. Voici le flux en pseudocode :
Toutes les 15 minutes :
1. Récupérer les prospects avec status='new', lancer l'enrichissement Hunter → status='enriched'
2. Récupérer les prospects avec status='enriched', lancer l'analyse du site → status='analyzed'
3. Récupérer les prospects avec status='analyzed', lancer la recherche Claude + génération d'email → status='drafted'
4. (Un humain examinepour examineles emails brouillons dans le dashboard Supabase)
5. Récupérer les prospects avec status='approved', pousser vers Instantly → status='sent'
6. Tirer les données d'engagement de l'API Instantly → mettre à jour opened_at, replied_at
L'étape 4 est importante. Nous n'automatisons pas complètement l'envoi. Chaque email est examiné par un humain avant d'être envoyé. Cela capture l'hallucination occasionnelle (Claude a une fois affirmé qu'un site était construit avec Remix alors qu'il était clairement Next.js) et nous permet d'ajouter des touches personnelles.
L'étape d'examen prend environ 2-3 secondes par email puisque Claude fonctionne correctement 95% du temps. Nous approuvons en lots en utilisant une vue de dashboard Supabase simple.
Résultats et ce que nous avons appris
Nous exécutons ce système depuis le Q1 2025. Voici les chiffres réels :
| Métrique | Notre système | Moyenne de l'industrie (2025) |
|---|---|---|
| Taux d'ouverture | 62% | 24% |
| Taux de réponse | 8.4% | 1-3% |
| Taux de réponse positive | 4.1% | 0.5-1% |
| Taux de rebond | 0.8% | 3-5% |
| Coût par prospect contacté | $0.18 | $0.50-2.00 |
| Temps par prospect (humain) | ~5 secondes (examen) | 10-15 minutes |
Le taux d'ouverture est élevé parce que les lignes d'objet sont spécifiques. « votre shopify lcp est 4.2s » est ouvert. « Question rapide » ne l'est pas.
Le taux de réponse est élevé parce que les emails démontrent une véritable connaissance technique. Quand un CTO lit un email qui identifie correctement sa pile technologique et un vrai problème de performance, il est plus susceptible de s'engager — même s'il sait que c'est une prospection.
Ce qui n'a pas fonctionné
Envoi entièrement automatisé (sans examen humain) : Nous avons essayé ceci pendant deux semaines. Claude a hallucine les détails de la pile technologique environ 5% du temps. C'est un taux d'erreur faible pour un LLM, mais envoyer un email qui dit « votre app React » à quelqu'un exécutant Vue est pire qu'envoyer un email générique. Les dommages à la confiance sont réels.
Emails longs : Nos premiers prompts Claude généraient des emails de 8-10 phrases. Les taux de réponse étaient la moitié de ce que nous voyons maintenant avec 4-6 phrases. Plus court c'est mieux. Toujours.
Envoyer plus de 40 emails par jour par compte : La livrabilité baisse d'une falaise. 30-35 est le sweet spot en 2025.
Utiliser Claude pour les suites basées sur les ouvertures : Nous avons essayé de générer des emails de suivi déclenchés par des ouvertures. Les suites semblaient pressantes et la conversion ne valait pas le coût. Nous envoyons maintenant un simple suivi non-IA trois jours plus tard.
Répartition des coûts
Voici ce que cela nous coûte mensuellement, en traitant environ 2 000 prospects :
| Service | Coût mensuel | Remarques |
|---|---|---|
| Hunter.io (Starter) | $49 | 500 recherches + vérifications |
| Anthropic API (Claude) | $45 | ~2 000 recherches + générations d'emails |
| Supabase (Pro) | $25 | Base de données, Edge Functions, Cron |
| Instantly (Growth) | $30 | Envoi, préchauffage, analytique |
| Google Workspace (3 comptes) | $21 | Infrastructure d'envoi |
| Domaines (3) | $10 | Coût annuel amorti |
| Total | ~$180 | $0.09 par prospect traité |
Comparez cela au plan de $79/mois d'Apollo (enrichissement limité, séquences basiques) ou au $69/mois par siège de Lemlist. Nous dépensons moins et obtenons des résultats dramatiquement meilleurs parce que la personnalisation est réelle, pas basée sur un template.
À titre informatif, ce système a directement généré des prospects qui se sont transformés en projets de développement Next.js et développement Astro valant 50-100x le coût mensuel. Le ROI est absurde.
FAQ
Combien de temps a-t-il fallu pour construire ce système ? La première version fonctionnelle a pris environ deux semaines de travail à temps partiel — peut-être 40 heures au total. Nous l'avons itéré en continu depuis, surtout en ajustant les prompts Claude et en ajoutant une gestion des cas limites. Si vous êtes à l'aise avec les Edge Functions Supabase et les API REST, vous pouvez mettre en place une version basique en un weekend.
Ce n'est que du spam avec des étapes supplémentaires, non ? Bonne question. La différence est que chaque email contient une observation technique réelle sur le site du destinataire. Nous ne bombardons pas « parlons au téléphone » à 10 000 personnes. Nous envoyons des informations spécifiques et utiles à une liste ciblée de personnes qui ont réellement les problèmes que nous résolvons. Notre taux de désinscription est inférieur à 0,5%, ce qui suggère que les destinataires ne le voient pas comme du spam non plus.
Pourquoi Claude au lieu de GPT-4 ou Gemini ? Nous avons testé les trois. Claude a suivi nos prompts système plus fiablement — surtout les contraintes comme « ne pas être générique » et « ne pas utiliser des phrases de remplissage ». GPT-4 dériverait vers un langage commercial même avec des instructions explicites de ne pas le faire. Gemini était rapide mais la qualité de la sortie était incohérente. Cela peut changer à mesure que les modèles évoluent, et notre système est conçu pour échanger facilement les modèles.
Comment gérez-vous la conformité RGPD et CAN-SPAM ? Tous nos prospections ciblent les emails professionnels (pas personnels), incluent notre adresse physique, et ont une désabonnement clair dans chaque email. Pour le RGPD, nous traitons les données sous l'intérêt légitime pour la prospection B2B, maintenons des registres des activités de traitement, et honorons immédiatement les demandes de suppression via un webhook automatisé. Nous purgeons également automatiquement les prospects antérieurs à 90 jours de notre base de données. Parlez à un avocat pour votre situation spécifique — ce n'est pas un conseil juridique.
Que se passe-t-il quand un prospect répond ? Les réponses reviennent de l'API d'Instantly dans Supabase. Nous recevons une notification Slack pour chaque réponse, et un humain prend immédiatement la conversation. Nous n'utilisons jamais l'IA pour la gestion des réponses. Une fois que quelqu'un s'engage, il mérite une vraie personne. Les prospects intéressés sont dirigés vers notre page de contact ou directement vers un lien de réservation d'appel.
Cette approche peut-elle fonctionner pour les services non-techniques ? L'analyse du site est spécifique au développement web, mais le modèle architectural — enrichir les prospects, utiliser l'IA pour rechercher et personnaliser, envoyer via un outil dédié — fonctionne pour toute prospection B2B. Vous auriez juste besoin d'entrées de recherche différentes. Une agence de design analyserait les patterns de conception visuelle et d'UX. Une agence de marketing analyserait les métriques SEO. La clé est d'alimenter Claude avec des données réelles, pas de lui demander de deviner.
Quelle est la partie la plus difficile de maintenir ce système ? La maintenance des prompts. À mesure que les modèles Claude se mettent à jour, les prompts qui ont fonctionné parfaitement nécessitent parfois un ajustement. Nous passons également du temps à surveiller la livrabilité des emails — vérifier les outils Google Postmaster, surveiller les pics de taux de spam, faire pivoter les comptes d'envoi. C'est peut-être 2-3 heures par semaine de maintenance au total.
Vendriez-vous cela en tant que produit ? Nous y avons pensé, mais honnêtement l'avantage compétitif est trop important. Si chaque agence exécutait ce système exact, l'efficacité baisserait parce que les destinataires commenceraient à voir des emails recherchés par IA partout. Pour l'instant, nous le gardons comme un outil interne. Si vous voulez de l'aide pour construire quelque chose de similaire pour votre entreprise, contactez-nous — nous avons aidé quelques clients à mettre en place des systèmes similaires dans le cadre de notre travail développement de CMS sans tête.