Inbound relationship intelligence system that transforms emails and LinkedIn data into structured contacts, classifies them using embeddings, and enables controlled outreach via EmailOctopus.
- Contact Deduplication: Unique constraint on email, upsert logic
- Email Ingestion: Accept JSON input, extract contact info, store messages
- LinkedIn Import: CSV upload, parse names, upsert contacts
- Message Aggregation: Combine last N messages per contact for analysis
- Embeddings: External embedding service integration with versioning
- Classification: Rule-based classification (investor, founder, vendor, recruiter, other)
- EmailOctopus Integration: Safe export with duplicate prevention
- Reply Tracking: Automatic detection of replies after outreach
- Node.js + TypeScript
- Express.js
- PostgreSQL + Prisma ORM
- No authentication (MVP)
- No queues or background workers
npm installcp .env.example .env
# Edit .env with your API keys and embedding URLnpm run prisma:generate
npm run prisma:migratenpm run devPOST /import/emails- Import emails from JSONPOST /import/linkedin- Import contacts from LinkedIn CSV
GET /contacts- List contacts (filters: label, source, domain)GET /contacts/:id- Get contact detailsGET /contacts/:id/messages- Get contact messages (thread view)
POST /embeddings/generate- Generate embeddings for contactsPOST /classify- Classify contacts
POST /export/emailoctopus- Export contacts to EmailOctopusPOST /export/emailoctopus/webhook- Webhook for status updates
GET /health- Health check
npm installnpm install --prefix embedding-servernode embedding-server/index.jsThe embedding server runs locally on port 3001.
ngrok http 3001Copy the public ngrok URL, for example:
https://xxxx.ngrok.ioUpdate your .env:
EMBEDDING_API_URL=https://xxxx.ngrok.io/embedThis lets the Dockerized API call your local embedding service over HTTP.
docker-compose up --buildThis starts:
apion port3000postgreson port5432
docker-compose exec api npx prisma migrate devcurl http://localhost:3000/healthYou can then import sample data and run embedding/classification flows against the API container.
Sample data files are provided in the data/ directory:
sample-emails.json- Mock email datasample-linkedin.csv- Sample LinkedIn export
curl -X POST http://localhost:3000/import/emails \
-H "Content-Type: application/json" \
-d @data/sample-emails.jsoncurl -X POST http://localhost:3000/import/linkedin \
-F "file=@data/sample-linkedin.csv"curl -X POST http://localhost:3000/classifycurl -X POST http://localhost:3000/export/emailoctopus \
-H "Content-Type: application/json" \
-d '{"contactIds": ["uuid1", "uuid2"]}'/src
/controllers # Request handlers
/services # Business logic
/routes # Express routes
/utils # Helpers and config
/prisma
schema.prisma # Database schema
/data
sample-*.json # Sample data files
/embedding-server
index.js # Local mock embedding service
package.json # Embedding service dependencies
Dockerfile
docker-compose.yml
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
EMAILOCTOPUS_API_KEY |
EmailOctopus API key |
EMAILOCTOPUS_LIST_ID |
EmailOctopus list ID |
EMBEDDING_API_URL |
Public ngrok URL to the local embedding service |
EMBEDDING_VERSION |
Embedding version for cache invalidation |
PORT |
Server port (default: 3000) |
MIT