Prospecção B2B automatizada: 30 empresas industriais num raio de 20km de Campo Comprido (Curitiba) → 30 contatos de RH/Talent Acquisition → 19 e-mails validados via SMTP → carga no Twenty CRM com deduplicação e campo customizado.
🚧 Projeto em desenvolvimento (WIP) — não está finalizado
Pipeline funcional em modo demo (prints emdocs/images/, APIsapi.exemplo.commock, 30 empresas + 19 EXISTS), mas com 11 e-mails pendentes, sem testes unitários automatizados e sem pipeline de follow-up. Uso real requer.api_keydo Twenty e validação dos padrões restantes. Veja## Limitationse## Roadmap.
Empresas B2B precisam prospectar indústrias grandes e decisores de RH num território específico — neste caso, 20km ao redor de Rua Osvaldo Tosato, 200, Campo Comprido (-25.443952, -49.347013), Curitiba/PR. Não existe base pronta de "indústrias no raio + RH com e-mail válido". A prospecção manual é lenta, propensa a duplicatas no CRM e sem validação de e-mail (catch-all, padrões corporativos diversos como @br.bosch.com vs @bosch.com).
Como, sem custo de APIs pagas de IA, (1) descobrir empresas industriais no raio, (2) geocodificar e filtrar por distância, (3) encontrar o responsável de RH para cada uma, (4) gerar e validar e-mails via SMTP (MX + RCPT TO 250/550), e (5) carregar tudo no Twenty CRM sem duplicar empresas, com campo customizado Setor e notas vinculadas?
O Corvina implementa o pipeline completo com APIs mock (https://api.exemplo.com) configuráveis via .env, totalmente local e gratuito:
1. Descoberta geográfica (src/overpass_search.py:1) — Overpass API mock (api.exemplo.com/overpass/interpreter) busca industrial no raio; Nominatim mock (api.exemplo.com/nominatim) geocodifica endereços; filtra 20/23 dentro de 20km + 3 manuais (Zhengchang, EBANX, Horsch) → examples/empresas_raio20.json (30 empresas, 22 setores únicos).
2. Carga Twenty CRM (src/push_twenty.py:1) — cria campo customizado Setor via POST /rest/metadata/fields, POST /rest/companies (30), PATCH /rest/companies/{id} (setor), com deduplicação por name/domainName (evita 7 duplicatas como bosch.com removidas).
3. RH + Validação (src/gen_candidates.py:1, src/smtp_verify.py:1) — pesquisa web manual (30 RHs, 12 cargos diferentes, 100% cobertura) → gen_candidates.py gera 320 candidatos first.last@domain (10 variações) → smtp_verify.py resolve MX via dnspython, abre SMTP porta 25, EHLO, MAIL FROM, RCPT TO, verifica 250 EXISTS vs 550 NOT_FOUND, incremental/resumível → 19 EXISTS (63%), 11 pendentes, 3 catch-all identificados (EBANX, Electrolux, TOTVS retornam 250 para tudo).
4. Contatos e Notas — POST /rest/people (30, vinculados à company), POST /rest/notes com bodyV2.markdown + POST /rest/noteTargets (2 vínculos por nota, 87 notas totais).
Resultado: 30 empresas | 30 people | 30 notas | 1 campo custom | 7 duplicatas removidas — tudo gratuito, em uma sessão (2026-08-07), com logs push_*.json e smtp_results.json.
Interface demo (Corvina B2B Prospecting):
- ✅ Busca geográfica Overpass+Nominatim mock (raio 20km, tag industrial)
- ✅ Campo customizado Twenty CRM
Setorvia metadata API - ✅ Geração de 320 candidatos de e-mail (10 padrões
first.last@domain) - ✅ Validação SMTP real (MX + RCPT TO 250/550, incremental, resume-safe)
- ✅ Deduplicação por
name/domainNameantes de POST (7 removidas) - ✅ Carga via
curlsubprocess (evita Cloudflare 1010 dourllib) - ✅ Notas com cargo/fonte/status e vínculo duplo (person+company)
flowchart LR
A["Overpass OSM - industrial 20km"] --> B["Nominatim - geocode"]
B --> C["empresas_raio20.json - 30 empresas"]
C --> D["Twenty CRM - POST companies"]
D --> E["PATCH setor - metadata API"]
E --> F["Web Research - 30 RH"]
F --> G["gen_candidates.py - 320 emails"]
G --> H["smtp_verify.py - 19 EXISTS"]
H --> I["push_twenty.py - 30 people + 30 notes"]
Componentes: src/overpass_search.py:1, src/gen_candidates.py:1, src/smtp_verify.py:1, src/push_twenty.py:1, docs/architecture.md.
| Camada | Tecnologia | Linguagem | Uso |
|---|---|---|---|
| Linguagem | Python 3.11 (100%) | Python | Scripts CLI (~500 linhas) |
| Geocodificação | api.exemplo.com (mock) |
HTTP/JSON | Overpass + Nominatim |
| CRM | api.exemplo.com/twenty/rest/ (mock) |
REST/JSON | Twenty Cloud (Postgres) |
| Validação | dnspython, asyncio |
Python | MX resolve + SMTP RCPT |
| HTTP | requests, curl (subprocess) |
Python/Shell | Evita Cloudflare 1010 |
Linguagens no repositório: Python detectado pelo GitHub Linguist (todos src/*.py), com Shell auxiliar (curl). Sem JS/TS — scripts puros.
Nota: todas as URLs externas são
https://api.exemplo.compor padrão (mock). AponteTWENTY_API_URL,OVERPASS_URL,NOMINATIM_URLno.envpara endpoints reais.
corvina/
├── src/
│ ├── overpass_search.py # Busca geográfica mock
│ ├── gen_candidates.py # Gera 320 candidatos first.last@domain
│ ├── smtp_verify.py # Valida via MX + RCPT TO
│ └── push_twenty.py # Wrapper REST Twenty via curl
├── docs/
│ ├── images/ # Prints demo (4 telas)
│ ├── architecture.md
│ └── repository-audit.md
├── examples/
│ ├── empresas_raio20.json # 5 empresas exemplo
│ ├── contacts_final.json # 3 contatos exemplo
│ └── smtp_results.json # 5 veredictos exemplo
├── .github/workflows/ci.yml
├── .env.example
├── requirements.txt
└── README.md
git clone https://github.com/PIRANGUEIRO/corvina.git
cd corvina
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# edite TWENTY_API_KEY e aponte TWENTY_API_URL para https://api.exemplo.com/rest/ quando for real
chmod 600 .env # como .api_key original| Variável | Descrição | Obrigatória |
|---|---|---|
TWENTY_API_URL |
Base REST Twenty | não (mock default) |
TWENTY_API_KEY |
Bearer token workspace | sim quando real |
OVERPASS_URL |
Overpass interpreter | não (mock) |
NOMINATIM_URL |
Nominatim search | não (mock) |
Nunca commitar .env ou .api_key.
# 1. Descoberta geográfica (mock)
python src/overpass_search.py --lat -25.443952 --lon -49.347013 --radius 20000
# 2. Gerar candidatos (a partir de rh_empresas.json)
python src/gen_candidates.py # gera candidates.json (320)
# 3. Validar SMTP (incremental, resumível)
python src/smtp_verify.py candidates.json smtp_results.json
# 4. Push para Twenty (mock por padrão)
python src/push_twenty.py --companies examples/empresas_raio20.jsonPOST /rest/companies,PATCH /rest/companies/{id},POST /rest/metadata/fields(campoSetorTEXT)POST /rest/people,POST /rest/notes(bodyV2.markdown+blocknote:null),POST /rest/noteTargets- Filtro:
--data-urlencode "filter=name[eq]:Copel"(escapa[])
- Integração real: 30 companies, 30 people, 30 notas, 320 SMTP verificados via
GETno CRM (ver04-Testes/Testes.md) - E2E: fluxo completo validado, 7 duplicatas removidas via
DELETE - Sem unitários automatizados (scripts one-shot). CI faz
py_compile.
| Decisão | Motivo | Alternativa | Trade-off |
|---|---|---|---|
| Overpass+Nominatim | Gratuito, sem chave | Google Places (pago) | Sem garantia de cobertura total |
| SMTP RCPT | Gratuito, preciso (M365) | Hunter (pago) | Catch-all gera falso positivo |
| curl via subprocess | Evita Cloudflare 1010 | urllib (falhou) | Menos pythônico |
| bodyV2.markdown | Único aceito pela REST Twenty | body (não existe) | Requer blocknote:null |
| .api_key chmod 600 | Segredo fora do código | env var | Manual |
- 11 e-mails pendentes (padrões corporativos não descobertos: Volvo @volvo.com, Bosch @br.bosch.com, etc.)
- 3 catch-all (EBANX, Electrolux, TOTVS) → 10 EXISTS falsos por pessoa
- Sem testes unitários, sem retry/backoff no SMTP (sequencial, lento para 320)
- Sem pipeline de follow-up
- Descobrir padrões das 11 empresas restantes e validar
- Enriquecimento LinkedIn
- Wrapper REST único (
api.py) para substituir curl one-shot - Retry com backoff no SMTP
- Teste de deduplicação automatizado
MIT — ver LICENSE.
- Python (CLI, asyncio, dnspython, subprocess, typing)
- Data Engineering (Overpass/Nominatim, JSON, geocoding, 20km radius)
- API Integration (Twenty REST, Overpass, Nominatim, SMTP)
- Security (chmod 600, Bearer, mock por padrão)
- System Design (deduplicação, incremental resume, curl vs urllib)



