A self-hosted, ToS-safe job-search assistant built with Spring Boot. It aggregates jobs from free APIs, scores them against your resume with AI, drafts application material, and can generate and send tailored recruiter outreach emails from a Notion database — without auto-submitting anything.
This is a Java/Spring Boot port of the original Next.js "Free Job Finder," and it reuses the same Supabase Postgres database, so both apps can share data.
- Daily job aggregation from Adzuna, JSearch, Remotive, and Arbeitnow.
- AI fit scoring & enrichment (match reasons, per-job suggestions) with automatic multi-provider fallback: Groq → Gemini → OpenRouter.
- Resume upload & parsing (PDF / DOCX / TXT) with AI skill extraction.
- Application tracker board with statuses.
- InMail: AI-drafted recruiter outreach emails from a pasted job description.
- Notion Outreach (two-phase): generate drafts from unprocessed Notion JD rows, review them, then send per-row with your resume attached and auto-tick the Notion checkbox.
- Suggestions report: consolidated resume/skill/positioning advice across your matches.
- Excel export of jobs.
- Single-user password gate with signed HMAC cookies.
- Scheduled daily runs via
@Scheduled(plus a secret-protected manual trigger).
- Java 21, Spring Boot 3.3.x
- Spring Web + Thymeleaf (server-rendered UI) + vanilla JS/CSS
- Spring Data JPA + PostgreSQL (points at the existing Supabase DB)
- Spring Mail (Gmail SMTP) for outreach sending
- Apache POI (Excel + DOCX), Apache PDFBox (PDF)
flowchart TB
subgraph client [Browser]
UI["Thymeleaf pages + app.js"]
end
subgraph web [Spring Boot web layer]
Filter["AuthFilter (HMAC cookie gate)"]
controllers["Controllers (/api/*): Auth, Profile, Resume, Jobs, Tracker, InMail, Notion, Suggestions, Settings, Export, RunDaily"]
end
subgraph services [Service layer]
Pipeline[PipelineService]
Sources[SourceService]
Ai[AiService]
NotionMail[NotionMailService]
Mail[MailService]
InMailSvc[InMailService]
Others["JobService, TrackerService, ProfileService, SettingsService, RunService, ExcelService, ResumeParser"]
Llm[LlmClient]
NotionCli[NotionClient]
end
repos["Spring Data JPA repositories"]
DB[("Supabase PostgreSQL")]
subgraph external [External APIs]
LLM["Groq / Gemini / OpenRouter"]
Boards["Adzuna / JSearch / Remotive / Arbeitnow"]
NotionAPI["Notion API"]
SMTP["Gmail SMTP"]
end
Sched["DailyScheduler (cron)"]
UI -->|"HTTP + fetch()"| Filter --> controllers --> services --> repos --> DB
Ai --> Llm --> LLM
Sources --> Boards
NotionMail --> NotionCli --> NotionAPI
NotionMail --> Mail --> SMTP
Sched --> Pipeline
flowchart TB
gen["POST /api/notion/generate"] --> nms[NotionMailService.generateDrafts]
nms --> nc["NotionClient: listPending + fetchPageText"] --> notion[Notion API]
nms --> ai["AiService.generateOutreachEmail"] --> llm[LlmClient]
nms --> save["InMailService.createFromNotion (persist)"] --> db[("inmail table")]
send["POST /api/notion/send {id}"] --> sd[NotionMailService.sendDraft]
sd --> mail["MailService.send (resume attached)"] --> smtp[Gmail SMTP]
sd --> mark["NotionClient.markDone"] --> notion
sd --> sent["InMailService.markSent"] --> db
JavaJobFinder/
pom.xml
db/schema.sql # full Postgres schema (run once in Supabase)
src/main/java/com/jobfinder/
JavaJobFinderApplication.java
auth/ AuthService, AuthFilter, AuthController
config/ JobFinderProperties, WebConfig, HttpConfig
controller/ Home, Auth, Profile, Resume, Jobs, Tracker, InMail,
Notion, Suggestions, Settings, Export, RunDaily, ApiExceptionHandler
domain/ Profile, Settings, SourceToggles, Job, Run, Tracker, InMail, RawJob
repository/ Spring Data JPA repositories
scheduler/ DailyScheduler
service/ PipelineService, AiService, JobService, TrackerService,
ProfileService, SettingsService, RunService, ExcelService,
ResumeParser, MailService, NotionMailService, InMailService,
FetchDescriptionService,
llm/LlmClient, notion/NotionClient, sources/*
src/main/resources/
application.yml
application-local.yml.example
templates/ index.html, login.html
static/ css/app.css, js/app.js, js/login.js
- JDK 21 (Eclipse Temurin recommended):
winget install EclipseAdoptium.Temurin.21.JDK - Maven:
winget install Apache.Maven
- Your Supabase database password (Supabase → Project Settings → Database). This is separate from the API keys — JPA connects directly to Postgres.
After installing, open a new terminal and verify:
java -version
mvn -versionRun db/schema.sql once in the Supabase SQL editor. All statements are idempotent, so re-running is safe. It includes the resume-attachment and Notion-link columns used by the outreach feature:
alter table public.profile add column if not exists resume_file bytea;
alter table public.profile add column if not exists resume_content_type text;
alter table public.inmail add column if not exists notion_page_id text;Copy the example local config and fill in real values:
Copy-Item src\main\resources\application-local.yml.example src\main\resources\application-local.ymlapplication-local.yml is git-ignored. Every value can also be supplied as an environment variable (see application.yml for the variable names):
Notes:
- If the direct
db.<ref>.supabase.cohost can't be reached (some networks are IPv6-only there), switch to the Session pooler host + username shown in the Supabase dashboard. - Gmail requires a 16-character App Password (2-Step Verification must be enabled) — not your normal login password.
- The Notion integration must be shared with your database (open the database →
•••→ Connections → add your integration).NOTION_DATABASE_IDis the 32-character id before?v=in the database URL.
mvn spring-boot:run "-Dspring-boot.run.profiles=local"Open http://localhost:8080. If app-password is set you'll hit the login page; otherwise the dashboard loads directly.
- Ensure your Notion database has a title column (job title), a
Statuscheckbox column, and the JD text (including the recruiter email) in each row's page body. - Leave rows you want to process unchecked.
- Click Generate drafts — drafts are created and saved (nothing is sent). Safe to re-run: already-drafted rows are skipped, and rate-limited rows can be retried later.
- Review/edit each draft, then click Send — the email is sent with your resume attached, the Notion checkbox is ticked, and the draft is marked sent.
- Single-user gate: one shared
APP_PASSWORD, validated into an HMAC (AUTH_SECRET) signed cookie. No user accounts or signup by design. application-local.ymlholds secrets and is git-ignored — keep tokens, the DB password, and the Gmail app password out of commits and screenshots./api/run-dailyis protected byCRON_SECRETfor external schedulers.
