CLI tool that extracts hiring posts from LinkedIn, uses AI to identify contacts and generate personalized emails, and sends them on a schedule with a dashboard for review.
- Parse LinkedIn feed API responses (JSON) or scraped HTML
- AI-powered hiring post detection (Gemini, AWS Bedrock, or OpenAI)
- Extract and validate emails from posts (including obfuscated formats)
- Generate personalized cold emails with AI
- Web dashboard for reviewing, approving, and rejecting contacts
- Scheduled email sending with background mailer daemon (9 AM - 1 PM IST)
- Email verification before sending (MX lookup + junk filter)
- Test mode for safe email testing
- Resume PDF attachment (required — fails hard if missing)
- Node.js v16+
- AI provider — one of:
- Gemini API key (free)
- AWS credentials for Bedrock
- OpenAI API key
- SMTP credentials (Gmail/Outlook/custom)
npm install
cp .env.example .env # edit with your credentialsEdit .env:
# AI — choose "gemini", "bedrock", or "openai"
AI_PROVIDER=gemini
GEMINI_API_KEY=your_key
# SMTP
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
FROM_NAME=Your Name
RESUME_PATH=./Your_Resume.pdfGmail users: use an App Password, not your regular password.
Create your resume data file:
cp resumeData.example.json resumeData.jsonEdit resumeData.json with your real personal info, skills, and experience. See resumeData.example.json for the expected format — replace all fields with your own details.
1. Parse LinkedIn data → output/extract.json
2. AI extract hiring posts → data/posts.json
3. Score + draft emails → data/users/{name}.json
4. Review in dashboard → approve / reject contacts
5. Mailer daemon sends → scheduled between 9 AM - 1 PM IST
| Command | Description |
|---|---|
npm run parse |
Parse LinkedIn data from clipboard |
npm run parse:file <path> |
Parse from a file |
npm run generate |
Phase 1: AI extract hiring posts → posts DB |
npm run emails |
Phase 2+3: Score posts → draft emails → user DB |
npm run emails:redraft |
Re-draft emails for posts with "drafted" status |
npm run send |
List approved emails ready to send |
npm run dashboard |
Open web dashboard (localhost:3456) |
npm run mailer |
Start background mailer daemon |
npm run mailer:stop |
Stop the mailer daemon |
npm run send:emails |
Send all approved emails immediately |
npm run send:list |
List unsent emails |
npm run send:verify |
Test SMTP connection |
# 1. Copy LinkedIn feed JSON to clipboard, then:
npm run parse
# 2. AI extract hiring posts from parsed data:
npm run generate
# 3. Score posts and draft emails:
npm run emails
# 4. Open dashboard to review and approve contacts:
npm run dashboard
# 5. Start the mailer daemon (sends approved emails on schedule):
npm run mailernpm run dashboard starts a local server at http://localhost:3456 with:
- Contact cards with match scores, job details, and email previews
- Approve / reject / restore actions
- Filters: Sendable, Approved, Sent, Email Only, DM Only, Low Match, Rejected, Verify Failed
- Salary and job type filters
- Search across all fields
Before sending, each email is automatically checked:
- Junk filter — skips role-based addresses (noreply@, info@, abuse@, etc.)
- MX lookup — verifies the domain has mail servers (falls back to A record per RFC 5321)
Failed verifications are logged and visible in the dashboard.
Configure in .env:
VERIFY_MX_TIMEOUT=5000 # DNS lookup timeout in msnpm run mailer starts a background process that:
- Checks every 60 seconds for approved emails whose scheduled time has passed
- Sends them with a 2-second delay between each
- Runs between 9 AM and 1 PM IST, auto-exits after
- Logs to
logs/mailer.log
Stop with npm run mailer:stop.
Redirect all emails to a test inbox:
EMAIL_TEST_MODE=true
EMAIL_TEST_INBOX=test@yopmail.comfeed-email-extractor/
├── src/
│ ├── cli.js # CLI entry point (parse/generate/emails/send)
│ ├── config.js # Configuration loader
│ ├── services/
│ │ ├── ai-bedrock.js # 3-phase pipeline (extract, score, draft)
│ │ ├── ai-provider.js # Unified AI provider (Bedrock/Gemini/OpenAI)
│ │ ├── prompts.js # AI prompt templates
│ │ ├── db.js # JSON file database
│ │ ├── clipboard.js # Clipboard reader
│ │ ├── parse-linkedin-feed.js # Parse LinkedIn API JSON
│ │ ├── parse-linkedin-html.js # Parse LinkedIn HTML
│ │ ├── extract-store.js # Manages output/extract.json
│ │ ├── email-sender.js # SMTP email sending
│ │ ├── email-validator.js # Email format validation
│ │ ├── email-verifier.js # MX lookup + junk filter
│ │ └── location-filter.js # India-only location filter
│ └── scripts/
│ ├── dashboard-server.js # Dashboard HTTP server
│ ├── send-emails.js # CLI for sending emails
│ ├── mailer-daemon.js # Background mailer daemon
│ ├── mailer-stop.js # Stop the daemon
│ └── reset-rejected.js # Reset rejected emails
├── dashboard/
│ ├── index.html # Dashboard UI
│ └── styles.css # Dashboard styles
├── data/ # JSON file DB (gitignored)
│ ├── posts.json
│ └── users/{name}.json
├── output/ # Generated data (gitignored)
│ └── extract.json
├── resumeData.example.json # Resume data template (copy to resumeData.json)
├── resumeData.json # Your personal info (gitignored)
├── .env.example # Environment template
└── logs/ # Logs (gitignored)
└── mailer.log
AI_PROVIDER=gemini
GEMINI_API_KEY=your_key
# GEMINI_MODEL=gemini-2.0-flashAI_PROVIDER=bedrock
AWS_ACCESS_KEY_ID=your_key
AWS_SECRET_ACCESS_KEY=your_secret
AWS_REGION=us-east-1
BEDROCK_MODEL_ID=anthropic.claude-3-5-sonnet-20241022-v2:0AI_PROVIDER=openai
OPENAI_API_KEY=your_key
# OPENAI_MODEL=gpt-4o-mini- Gmail limit: 500 emails/day. Recommend max 20-30/day to avoid spam flags.
- Privacy: Never commit
.envorresumeData.json(both in.gitignore). - Duplicates: The tool warns before sending to the same email twice.
- LinkedIn ToS: Use for personal job search only.
MIT