Synaplan
Open-source AI platform for companies: self-hosted AI agents, GDPR-compliant chatbot widget, RAG knowledge base, WhatsApp and Outlook AI, MCP, multi-model routing. Docker, Kubernetes or hosted in Germ
Find who to reach out to at a company, ranked by how well their background matches your resume.
Translation pending
BusinessTypeScript · JavaScriptGitHub @rishav-duttaOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Find the right people to connect with at a company, ranked by how well they match your resume (n8n, Gemini, Supabase).
Loading the full README.
Read the full README ↗Find the right people to reach out to at a company, ranked by how well their background matches your resume.
| Contacts | Ranked matches |
|---|---|
Screenshots use sample data.
flowchart LR
B["React app<br/>(Vercel)"] -- "search / resume" --> P["n8n-proxy<br/>Supabase Edge Function"]
P -- "validated, rate-limited,<br/>secret header" --> N["n8n<br/>(Oracle Cloud, Tailscale Funnel)"]
N --> F["Firecrawl<br/>web search"]
N --> A["Apify<br/>profile scraper"]
N --> G["Gemini 2.5 Flash-Lite<br/>scoring"]
N --> D[("Supabase Postgres")]
B -- "get_leads_for_search()" --> D
Search (find-leads): the gatekeeper function checks the input and rate limits, then calls n8n. n8n runs a web search for public LinkedIn profiles, saves the people it finds and replies to the site straight away. It then scrapes their full profiles with Apify (7–35 s) and saves them, with each person's current profile URL. The site shows contacts immediately and unlocks the resume upload once the profiles have arrived.
Scoring (score-resume): n8n extracts the resume's text and sends it to Gemini with the search's profiles, each labelled by its database ID. Gemini returns a score and reasoning per ID. Only IDs that were sent are accepted, and the scores are saved.
security definer function that returns only one search's rows and only the columns the UI needs. The table itself has no public read policy. Uploaded resumes go to a private storage bucket.n8n/workflows, and CI runs lint, type checks and a build on every pull request.| Layer | Technology |
|---|---|
| Frontend | React 18, TypeScript, Vite 8, Tailwind CSS 4, Framer Motion, on Vercel |
| API gateway | Supabase Edge Function (Deno) |
| Workflows | n8n 2.41, self-hosted in Docker on Oracle Cloud (Ampere A1, Always Free) |
| Data | Supabase Postgres and Storage |
| Search and scraping | Firecrawl (web search), Apify (harvestapi/linkedin-profile-scraper) |
| AI | Google Gemini 2.5 Flash-Lite through the n8n AI Agent, with structured output |
src/ React app
supabase/migrations/ Database schema, policies and the get_leads_for_search function
supabase/functions/n8n-proxy Edge Function between the site and n8n
n8n/workflows/ Exported n8n workflows (see n8n/README.md)
docs/ Project site (GitHub Pages)
You need a Supabase project, an n8n instance, and Firecrawl, Apify and Gemini API keys.
supabase/migrations/ in order (supabase db push, or paste them into the SQL editor).ALLOWED_ORIGINS in supabase/functions/n8n-proxy/index.ts, then deploy it and set its secrets:
supabase functions deploy n8n-proxy
supabase secrets set N8N_BASE_URL=https://your-n8n-host N8N_PROXY_SECRET=a-long-random-string
n8n/README.md.cp .env.example .env.local # add your Supabase URL and anon key
npm ci
npm run dev # http://localhost:5173
npm run lint, npm run typecheck and npm run build are the same checks CI runs.Visits to the app and the docs page are counted with GoatCounter: no cookies, and nothing that identifies a visitor is stored. Vercel Web Analytics also counts app page views. The app sends fixed event names only, never a searched company, a file name or anything typed, from src/lib/analytics.ts:
| Event | When |
|---|---|
search, search-no-results, search-error, search-rate-limited | A search is sent, finds nobody, fails, or is turned away by the hourly limit |
example-picked | An example on the start screen fills the form |
profiles-failed | Full profiles didn't load, so the people can't be ranked |
resume-rejected | A file is refused before upload (not a PDF, or over 5 MB) |
resume-upload, resume-ranked, resume-error, resume-rate-limited | A resume is sent, scored, fails, or is turned away by the hourly limit |
details-opened | A person's row is opened to read the reason and profile summary (not the top match opening by itself) |
linkedin-profile | A person's LinkedIn link is opened |
docs-link, github-link, author-linkedin | Header and footer links |
The docs page counts clicks on Open the app, GitHub, LinkedIn and the PDF with data-goatcounter-click, and docs-read-to-end once per visit when About me scrolls into view. Links with ?campaign=…&ref=… show up as campaigns.