RoleReach is a MERN-based opportunity discovery application that opens LinkedIn in a visible Playwright-controlled browser, searches recent hiring posts for one or more job roles, extracts public recruiter details, stores the results in MongoDB, and presents them in a polished responsive dashboard.
This is an assignment/demo implementation. LinkedIn may request login, OTP, CAPTCHA or temporarily restrict automated activity. RoleReach never bypasses those controls; the user completes verification manually in visible Chrome.
| Component | URL | Status |
|---|---|---|
| Frontend | role-reach-delta.vercel.app | Live on Vercel |
| API health | Railway API health | Live on Railway |
| Remote browser | Railway noVNC workspace | Authenticated access is initiated from RoleReach |
The hosted frontend, email authentication, API, MongoDB persistence and remote browser are available for review. LinkedIn search automation is resource-intensive: Chromium can exceed Railway Trial's 1 GB memory limit and crash on the hosted instance. Run the Docker setup below for the complete and most reliable demonstration.
- Multiple job-role input and configurable 1–30 day search window
- Visible Chrome automation with a persistent LinkedIn browser session
- Password-protected noVNC workspace for remote login, OTP and CAPTCHA handling
- Manual LinkedIn login/verification with a real pause-and-resume workflow
- Post content, author name, designation, profile URL and post URL extraction
- Canonical LinkedIn permalink matching that rejects ambiguous nested/re-shared post links
- Public email and phone extraction from post text
- MongoDB persistence with owner-scoped duplicate protection
- Passwordless email OTP authentication through Brevo
- Persistent JWT session, sign-out, saved-post shortlist and search activity history
- One-click, one-profile LinkedIn connection request without a note, with persistent status
- Target-profile-scoped Connect actions with post-click Pending/Sent confirmation
- JWT-protected APIs and one-active-browser protection
- API and authentication rate limiting, Helmet, CORS and input limits
- Responsive dark/light interface with loading, empty and error states
- Frontend and backend automated tests
- Dockerfiles, Docker Compose and GitHub Actions CI/CD
| Layer | Technology |
|---|---|
| Frontend | React 19, TypeScript, Vinext, Tailwind/CSS |
| Backend | Node.js, Express 5 |
| Database | MongoDB, Mongoose |
| Browser automation | Playwright, visible Chromium/Chrome |
| Authentication | Email OTP, Brevo Transactional Email, JWT |
| Security | Helmet, CORS, express-rate-limit |
| Testing | Node test runner, rendered frontend tests |
| Remote browser | Xvfb, x11vnc, noVNC, websockify |
| Delivery | Docker, Docker Compose, GitHub Actions, Vercel, Railway |
React dashboard → Express API → Playwright visible Chrome → LinkedIn
│ ↓ ↑
│ MongoDB noVNC workspace
│ ↑ ↑
└────────── Brevo email OTP Human verification
The frontend starts a search and polls its status. The API launches a persistent Playwright context locally, waits for manual login when required, searches each role, extracts public fields, and upserts every result immediately so progress survives an interrupted search. A user can also deliberately click Connect on one result; RoleReach opens that author's profile in the same visible session and selects Send without a note. This action is limited to five attempts per day and is never run in bulk.
RoleReach deliberately uses a full visible browser. LinkedIn may require login, OTP, CAPTCHA or another verification step, and a visible browser lets the user complete that step manually. When Docker runs on another machine, x11vnc shares Playwright's virtual X11 display and noVNC makes it controllable in a normal web browser. Set PLAYWRIGHT_CHANNEL=chrome to use an installed Google Chrome build; leave it empty to use Playwright Chromium. Headless mode is not used because it would hide verification screens and prevent safe human intervention.
- Node.js 22+
- MongoDB 7/8, local or Atlas
- A verified Brevo sender and API key
- A desktop environment capable of showing Chrome
npm install
cd server
npm install
npx playwright install chromiumCopy server/.env.example to server/.env and provide:
PORT=5000
CLIENT_URL=http://localhost:3000
MONGODB_URI=mongodb://localhost:27017/rolereach
JWT_SECRET=use-a-long-random-secret
BREVO_API_KEY=xkeysib-your-key
BREVO_SENDER_EMAIL=your-verified-sender@example.com
PLAYWRIGHT_PROFILE_DIR=./.linkedin-profiles
PLAYWRIGHT_SLOW_MO=100
NOVNC_PASSWORD=use-a-strong-remote-browser-passwordIn Brevo, open Settings → Senders & IP → Senders, verify the sender address, create an API v3 key, and place both values in the environment file. RoleReach uses Brevo directly—Nodemailer is not used.
Terminal 1:
cd server
npm run devTerminal 2:
npm run devOpen the displayed frontend URL, verify your email, enter roles, and start a live search. During direct desktop development, a visible browser opens locally. Complete LinkedIn login, OTP or CAPTCHA manually if requested.
cp server/.env.example server/.env
docker compose up --buildBefore starting Docker, set a unique NOVNC_PASSWORD in server/.env. Then:
- Open
http://localhost:3000for RoleReach. - Click Open LinkedIn browser.
- RoleReach requests authenticated browser access and embeds the noVNC workspace inside the application; the reviewer is not shown a separate tab or password prompt.
- Sign in to LinkedIn inside the embedded Chrome workspace.
- Minimize the browser workspace, start the search, and reopen it whenever manual verification is needed.
MongoDB data and per-user LinkedIn browser profiles persist in named Docker volumes. docker compose down stops the stack without deleting those volumes.
The current assignment deployment uses Vercel for the frontend, MongoDB Atlas for persistent application data and Railway for the Dockerized Express, Playwright and noVNC service.
Create a Railway service from this repository and configure:
- Root Directory:
/server - Builder: the existing
server/Dockerfile - API public domain target port: the port printed by the application (
8080in the current deployment) - noVNC public domain target port:
6080 - Persistent volume mount:
/app/.linkedin-profiles
Add these runtime variables in Railway. Never commit their real values:
NODE_ENV=production
CLIENT_URL=https://your-project.vercel.app
MONGODB_URI=mongodb+srv://user:password@cluster/rolereach
JWT_SECRET=use-a-long-random-secret
BREVO_API_KEY=xkeysib-your-key
BREVO_SENDER_EMAIL=your-verified-sender@example.com
PLAYWRIGHT_PROFILE_DIR=/app/.linkedin-profiles
PLAYWRIGHT_SLOW_MO=100
NOVNC_PASSWORD=use-a-strong-remote-browser-password
SCREEN_WIDTH=1100
SCREEN_HEIGHT=700
NODE_OPTIONS=--max-old-space-size=256
MALLOC_ARENA_MAX=2Do not manually set PORT; Railway injects it. MongoDB Atlas must allow the
Railway service to connect. For a short-lived demo with dynamic outbound IPs,
an Atlas IP access-list entry of 0.0.0.0/0 can be used only with a strong,
unique database password. Restrict network access further for production.
Import the repository into Vercel with:
- Framework Preset: Next.js
- Root Directory: repository root
- Install Command:
npm ci - Build Command:
npx next build
Set these build-time variables:
NEXT_PUBLIC_API_URL=https://your-api.up.railway.app/api
NEXT_PUBLIC_NOVNC_URL=https://your-browser.up.railway.app/vnc.html?autoconnect=true&resize=scaleAfter Vercel assigns the production domain, set Railway's CLIENT_URL to that
exact HTTPS origin without a trailing slash and redeploy the backend.
Railway Trial currently provides 1 GB RAM. LinkedIn's modern web client and
visible Chromium can approach or exceed that limit even with low-memory browser
flags, causing Playwright to report Page crashed. This is a hosting-resource
constraint rather than an API, database or frontend failure. The Vercel UI,
Brevo OTP login, Express API, MongoDB persistence and noVNC workspace remain
independently testable. For the complete search workflow, use local Docker or a
server with at least 2 GB RAM. The noVNC workspace is intentionally a
single-operator demo workspace; do not use it concurrently from multiple
browsers.
# Frontend production build and rendered-page tests
npm test
# Backend extraction tests
cd server && npm testGitHub Actions runs frontend lint/tests, backend API/unit tests, and both Docker builds on pushes and pull requests. On main-branch pushes, the CD workflow publishes versioned frontend and backend images to GitHub Container Registry and optionally calls the configured deployment hooks.
Configure these repository secrets for automatic deployment:
FRONTEND_DEPLOY_HOOK: the frontend provider's deploy hookBACKEND_DEPLOY_HOOK: the backend provider's deploy hook
Container publishing still completes when either hook is intentionally omitted.
| Method | Route | Purpose |
|---|---|---|
| POST | /api/auth/request-otp |
Send a Brevo email OTP |
| POST | /api/auth/verify-otp |
Verify OTP and return JWT |
| POST | /api/search |
Start an authenticated LinkedIn search |
| GET | /api/search/:jobId/status |
Read live search status |
| POST | /api/search/:jobId/resume |
Resume after manual verification |
| POST | /api/search/:jobId/cancel |
Cancel while preserving collected posts |
| GET | /api/posts |
List persisted user posts |
| PATCH | /api/posts/:id/saved |
Add/remove a post from the shortlist |
| POST | /api/posts/:id/connect |
Send one connection request without a note |
| DELETE | /api/posts/:id |
Delete one saved post |
| GET | /api/activity |
Read persistent search history |
| GET | /api/auth/me |
Validate a persisted login session |
| POST | /api/browser/access |
Authorize one-click access to the noVNC workspace |
| GET | /api/health |
API health check |
- LinkedIn credentials are never requested or stored.
- Only the Playwright profile/cookies persist; keep that directory and its Docker volume private.
- noVNC requires a separate strong password and should be served through HTTPS in production.
- RoleReach returns that credential only after email authentication and places it in the noVNC URL fragment, so it is not sent in HTTP requests or access logs.
- Public contact details are extracted only when present in the post text.
- OTPs are SHA-256 hashed, expire after ten minutes and are cleared after use.
- Search, post and authentication routes are rate-limited.
- Connection requests require an explicit click, are limited to five per day and store a status to prevent repeats.
- A click alone is never recorded as success; RoleReach stores Sent/Pending only after LinkedIn displays a visible confirmation for the target profile.
- JWT secrets, Brevo keys and MongoDB credentials belong only in environment variables.
- LinkedIn selectors and page structure can change.
- Ambiguous cards containing different nested post permalinks are skipped instead of being saved with a mismatched source link.
- Automation can trigger verification or account restrictions.
- LinkedIn can hide Connect under More, mark a request Pending, or make the action unavailable.
- Existing invitations shown as Pending/Withdraw invitation and existing relationships shown as Remove connection are mapped back to the correct persistent status.
- Hashed LinkedIn variants also use a profile-heading-scoped visible-action fallback before Unavailable is recorded.
- Connection state is profile-scoped, so every collected post from the same author displays the same Pending/Connected status.
- Date labels shown by LinkedIn may be approximate.
- The official LinkedIn API does not provide this project’s broad public post keyword-search capability.
- No browser automation can guarantee uninterrupted operation for every account.
- The free Railway deployment has a 1 GB memory ceiling; visible Chromium can crash under LinkedIn's workload. Local Docker or a 2 GB+ server is recommended for the full demonstration.
- The included noVNC workspace is single-operator; production multi-user isolation would require one browser container/session per user.
RoleReach responds safely: it keeps already-saved results, reports the current status, and expects the user to complete any verification manually. It does not attempt to bypass LinkedIn security controls.
app/ React dashboard
server/src/index.js Express routes, auth and rate limits
server/src/automation.js Visible Chrome, extraction and pause/resume workflow
server/start-novnc.sh X11, VNC and noVNC process lifecycle
server/src/models.js MongoDB models and indexes
server/src/email.js Brevo OTP delivery
server/src/utils.js Date, relevance, contact and URL helpers
server/tests/ Backend tests
tests/ Frontend rendered-output tests
.github/workflows/ CI and CD pipelines
Dockerfile Frontend container
server/Dockerfile Backend/Playwright container
docker-compose.yml Web, API and MongoDB stack