AI-powered cold outreach pipeline: CSV contacts β automated company research β LLM-generated personalized emails β review UI β batch send via Gmail API.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Frontend (React + Vite) :5173 β
β ββββββββββββββββ ββββββββββββββββ β
β β CSVManager β β EmailReview β β
β β (contacts) β β (review/send)β β
β ββββββββ¬ββββββββ ββββββββ¬ββββββββ β
β ββββββββββ¬βββββββββ β
β β axios β
ββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββ€
β Backend (FastAPI) :8000 β
β β β
β βββββββββββββββββΌββββββββββββββββ β
β β main.py (API) β β
β ββββ¬ββββββββ¬ββββββββ¬βββββββ¬βββββ β
β β β β β β
β ββββΌβββ ββββΌββββ βββΌβββ ββΌβββββββββββββββ β
β β CSV β βEmail β βLLM β β Company β β
β βProc.β βSenderβ βGen.β β Enrichment β β
β ββββ¬βββ ββββ¬ββββ βββ¬βββ ββ¬βββββββββββββββ β
β β β β β β
β contacts Gmail Cloud Web Scraper β
β .csv API LLM (requests + β
β OAuth2 API trafilatura + β
β DuckDuckGo) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- CSV contacts β Upload, edit, add, delete; sections: Emailed, Generated (not sent), No emails yet
- Company research β DuckDuckGo search β web scraping β LLM metadata extraction (summary, product, industry, news)
- AI email generation β Cloud LLM (Gemini/OpenRouter/OpenAI) writes personalized cold emails using company research + your resume/skills. Template fallback if no LLM key is set.
- Review UI β Accept/trash before sending; inline edit subject & body; attach resume (2028 or 2029)
- Batch send β Gmail API with rate limits, response tracking, and follow-up reminders
- Follow-ups β Auto-detect no-response contacts; generate and send follow-up emails
./start.shThen open http://localhost:5173. Backend: http://localhost:8000 Β· API docs: http://localhost:8000/docs
- Python 3.12 (3.13 has compatibility issues; use
python3.12 -m venv venvif needed) - Node.js 18+
- LLM API key β At least one of: Google AI (Gemini), OpenRouter, or OpenAI. See
.env.example. - Ollama (optional) β ollama.ai for local follow-up generation:
ollama pull llama3.2
Copy .env.example to .env and fill in your API key(s):
cp .env.example .envKey settings:
GOOGLE_AI_API_KEYβ Gemini (recommended, free tier available). Provider auto-detected by key.OPENROUTER_API_KEY/OPENAI_API_KEYβ alternativesOLLAMA_BASE_URL,OLLAMA_MODELβ local Ollama for follow-ups- Rate limits:
MAX_EMAILS_PER_DAY,MAX_EMAIL_GENERATIONS_PER_MINUTE, etc. RESUME_PATHβ resume PDF for attachments (optional; app also checksresume28.pdf/resume29.pdfin project root)
- Google Cloud Console β create project β enable Gmail API
- APIs & Services β Credentials β Create credentials β OAuth client ID
- Configure OAuth consent screen if prompted (External, app name, add scope
https://www.googleapis.com/auth/gmail.send, add your email as test user) - Application type: Desktop app β Create β Download JSON
- Save as
credentials.jsonin project root - First send will open the browser for sign-in;
token.jsonis created automatically
Troubleshooting: "Credentials not found" β ensure credentials.json is in project root. "Access denied" β add your Gmail as test user in OAuth consent screen. To switch account β delete token.json and send again.
Put a PDF in project root (e.g. resume.pdf, resume28.pdf, resume29.pdf) or set RESUME_PATH in .env. When sending, you can attach 2028 resume (default) or 2029 resume from the dropdown.
One command: ./start.sh (starts backend on 8000, frontend on 5173).
Manual (two terminals):
# Terminal 1
cd backend && source venv/bin/activate && uvicorn main:app --reload --port 8000
# Terminal 2
cd frontend && npm run devOpen http://localhost:5173.
- Contacts β Upload CSV (columns:
name,company,email) or add manually - Review Emails β "Generate Emails" for contacts with no email; review and accept/trash
- Settings (βοΈ) β Name, email, background (used in generation)
- Send β "Send Accepted Emails"; attach 2028 or 2029 resume (default 2028)
- Emailed tab β Sent dates, response status, follow-up reminders (e.g. after 1 week)
- Company enrichment β DuckDuckGo search finds the company website;
trafilatura+BeautifulSoupscrape and clean the page; an LLM extracts structured metadata (summary, product, industry, recent news). - Email drafting β The cloud LLM (Gemini by default) receives your resume/skills + company metadata and writes a personalized cold email. Falls back to a fixed template if no LLM key is configured.
- Gemini model fallback β On quota exhaustion (429), automatically cycles through:
gemini-2.5-flashβgemini-3-flashβgemini-3.1-flash-liteβ lighter Gemma models.
Configured in .env; restart backend after changes.
| Limit | Default | Env variable |
|---|---|---|
| Emails per day (send) | 50 | MAX_EMAILS_PER_DAY (Gmail free tier up to 500/day) |
| Generations per day | 500 | MAX_EMAIL_GENERATIONS_PER_DAY |
| Generations per minute | 10 | MAX_EMAIL_GENERATIONS_PER_MINUTE |
| Company researches per minute | 5 | MAX_COMPANY_RESEARCH_PER_MINUTE |
| Delay between sends | 3 s | EMAIL_SEND_DELAY_SECONDS |
- Required:
name,company,email - Optional:
id,status(e.g. pending, trashed, sent)
Personalization: The email body uses your skills.md file (project root) for the "about you" sentence. Add a ## Email one-liner section with one sentence, or the app uses the first sentence of your first ## Experience block.
- Do not commit
.env,credentials.json, ortoken.json(they are in.gitignore) - Use
.env.exampleas a template (no real secrets) - CORS origins: set
CORS_ORIGINSin.envfor production
- backend/ β FastAPI, CSV/contact handling, LLM client, Gmail send, rate limiting
- frontend/ β React + Vite UI
- stress/ β Load/concurrency tests
- tests/ β Unit tests
| Layer | Tech |
|---|---|
| Frontend | React 18, Vite, react-hot-toast |
| Backend | Python 3.12, FastAPI, Uvicorn |
| LLM | Google Gemini (primary), OpenRouter, OpenAI, Ollama (local fallback) |
| Gmail API (OAuth2) | |
| Scraping | requests, trafilatura, BeautifulSoup4, duckduckgo-search |
| Data | CSV (contacts), JSON (emails, company cache) |
MIT