An AI-powered resume analyser and HR outreach tool — built as a full-stack portfolio project demonstrating AI integration, file handling, and automated email delivery.
- 📤 PDF Resume Upload — drag-and-drop upload with Multer memory storage
- 🤖 AI-Powered Analysis — deep resume scoring against a target job role using Groq's LLaMA 3.3 70B
- 📊 Structured Feedback — overall score, top strengths, critical gaps, missing keywords, and section-by-section breakdowns with suggested rewrites
- 📧 AI-Generated HR Outreach Email — personalised cold emails generated by AI and delivered via Brevo REST API with the resume as an attachment
- 🔁 Stateless Architecture — no database, no auth; clean REST API with state passed via React Router
| Layer | Technology |
|---|---|
| Backend | Node.js + Express (ES Modules) |
| Frontend | React + Vite + Tailwind CSS + DaisyUI |
| AI | Groq SDK — llama-3.3-70b-versatile |
| Brevo REST API (HTTP-based, no SMTP) | |
| PDF Parse | pdf2json with safeDecode wrapper |
| File Upload | Multer with memoryStorage() |
| Routing | React Router v6 |
| Deployment | Render (free tier) |
resumeforge/
├── resume-backend/
│ └── src/
│ ├── routes/
│ │ ├── resume.routes.js
│ │ └── email.routes.js
│ ├── controllers/
│ │ ├── resume.controller.js
│ │ └── email.controller.js
│ ├── services/
│ │ ├── pdf.service.js
│ │ ├── ai.service.js
│ │ └── email.service.js
│ ├── middleware/
│ │ └── upload.middleware.js
│ ├── app.js
│ ├── .env
│ └── .env.example
│
└── resume-frontend/
├── src/
│ ├── pages/
│ │ ├── Home.jsx
│ │ ├── Analysis.jsx
│ │ ├── EmailPreview.jsx
│ │ └── Success.jsx
│ ├── App.jsx
│ └── main.jsx
├── index.html
└── vite.config.js
git clone https://github.com/your-username/resumeforge.git
cd resumeforgecd resume-backend
npm installCreate a .env file based on .env.example:
PORT=5000
GROQ_API_KEY=your_groq_api_key_here
BREVO_API_KEY=your_brevo_api_key_here
EMAIL_USER=your_verified_sender_email@example.comStart the backend:
npm run devcd resume-frontend
npm install
npm run devThe frontend runs on http://localhost:5173 and the backend on http://localhost:5000.
Analyse a resume PDF against a target job role.
Request — multipart/form-data
| Field | Type | Description |
|---|---|---|
resume |
File | PDF file of the resume |
jobRole |
String | Target job role (e.g. "Frontend Developer") |
Response
{
"success": true,
"analysis": {
"overall_score": 72,
"top_strengths": ["..."],
"critical_gaps": ["..."],
"keywords_to_add": ["..."],
"sections": {
"technical_skills": { "score": 80, "summary": "...", "strengths": [], "suggestions": [], "suggested_rewrites": [] },
"projects": { "score": 65, "summary": "...", "strengths": [], "suggestions": [], "suggested_rewrites": [] },
"experience": { "score": 70, "summary": "...", "strengths": [], "suggestions": [], "suggested_rewrites": [] },
"education": { "score": 90, "summary": "...", "strengths": [], "suggestions": [], "suggested_rewrites": [] }
}
}
}Generate an AI-written outreach email and send it via Brevo with the resume attached.
Request — multipart/form-data
| Field | Type | Description |
|---|---|---|
resume |
File | PDF file of the resume |
to |
String | Recipient email address |
jobRole |
String | Target job role |
applicantName |
String | Applicant's full name |
Response
{
"success": true,
"email": {
"subject": "...",
"body": "..."
},
"meta": {
"sentTo": "hr@company.com",
"applicant": "Rishi Sharma"
}
}Home
└─► Analysis Page (POST /api/resume/analyse)
└─► Email Preview Page (POST /api/email/send)
└─► Success Page
Data flows between pages via React Router v6 useLocation state:
navigate('/analysis', {
state: { analysis, jobRole, applicantName }
})Note:
Fileobjects cannot be serialised through Router state. The resume file is re-selected on the Analysis page before sending the email.
| Variable | Required | Description |
|---|---|---|
PORT |
No | Server port (default: 5000) |
GROQ_API_KEY |
Yes | Groq API key for AI inference |
BREVO_API_KEY |
Yes | Brevo API key for email delivery |
EMAIL_USER |
Yes | Verified sender email in Brevo account |
- Push your backend to a GitHub repository.
- Create a new Web Service on Render.
- Set the Build Command to
npm installand Start Command tonode src/app.js. - Add all environment variables in the Render dashboard under Environment.
- Deploy — Render will auto-deploy on every push to
main.
Important: Render's free tier blocks SMTP ports (25, 465, 587). This is why the project uses Brevo's HTTP REST API instead of Nodemailer.
| Problem | Decision |
|---|---|
| Render blocks SMTP | Switched from Nodemailer → Brevo REST API over HTTPS |
pdf-parse ES Module errors |
Switched to pdf2json with a safeDecode wrapper |
| Groq JSON parsing issues | Extract JSON with /\{[\s\S]*\}/ regex, escape raw newlines before JSON.parse |
| File not serialisable via Router state | User re-selects the file on Analysis page instead of passing it through state |
| Context API overhead | Unnecessary for linear flow — useLocation state is sufficient |
- Proper Tailwind utility class rewrite (currently using custom CSS)
- UI/design polish across all pages
- Improved AI prompt with more specific section-level feedback
- Resume score history (local storage)
- Support for
.docxresume uploads
This is a personal portfolio project — but feel free to fork it, learn from it, or suggest improvements via issues.
Built with ❤️ by Rishi
---- 📤 PDF Resume Upload — drag-and-drop upload with Multer memory storage
- 🤖 AI-Powered Analysis — deep resume scoring against a target job role using Groq's LLaMA 3.3 70B
- 📊 Structured Feedback — overall score, top strengths, critical gaps, missing keywords, and section-by-section breakdowns with suggested rewrites
- 📧 AI-Generated HR Outreach Email — personalised cold emails generated by AI and delivered via Brevo REST API with the resume as an attachment
- 🔁 Stateless Architecture — no database, no auth; clean REST API with state passed via React Router
| Layer | Technology |
|---|---|
| Backend | Node.js + Express (ES Modules) |
| Frontend | React + Vite + Tailwind CSS + DaisyUI |
| AI | Groq SDK — llama-3.3-70b-versatile |
| Brevo REST API (HTTP-based, no SMTP) | |
| PDF Parse | pdf2json with safeDecode wrapper |
| File Upload | Multer with memoryStorage() |
| Routing | React Router v6 |
| Deployment | Render (free tier) |
resumeforge/
├── resume-backend/
│ └── src/
│ ├── routes/
│ │ ├── resume.routes.js
│ │ └── email.routes.js
│ ├── controllers/
│ │ ├── resume.controller.js
│ │ └── email.controller.js
│ ├── services/
│ │ ├── pdf.service.js
│ │ ├── ai.service.js
│ │ └── email.service.js
│ ├── middleware/
│ │ └── upload.middleware.js
│ ├── app.js
│ ├── .env
│ └── .env.example
│
└── resume-frontend/
├── src/
│ ├── pages/
│ │ ├── Home.jsx
│ │ ├── Analysis.jsx
│ │ ├── EmailPreview.jsx
│ │ └── Success.jsx
│ ├── App.jsx
│ └── main.jsx
├── index.html
└── vite.config.js
git clone https://github.com/your-username/resumeforge.git
cd resumeforgecd resume-backend
npm installCreate a .env file based on .env.example:
PORT=5000
GROQ_API_KEY=your_groq_api_key_here
BREVO_API_KEY=your_brevo_api_key_here
EMAIL_USER=your_verified_sender_email@example.comStart the backend:
npm run devcd resume-frontend
npm install
npm run devThe frontend runs on http://localhost:5173 and the backend on http://localhost:5000.
Analyse a resume PDF against a target job role.
Request — multipart/form-data
| Field | Type | Description |
|---|---|---|
resume |
File | PDF file of the resume |
jobRole |
String | Target job role (e.g. "Frontend Developer") |
Response
{
"success": true,
"analysis": {
"overall_score": 72,
"top_strengths": ["..."],
"critical_gaps": ["..."],
"keywords_to_add": ["..."],
"sections": {
"technical_skills": { "score": 80, "summary": "...", "strengths": [], "suggestions": [], "suggested_rewrites": [] },
"projects": { "score": 65, "summary": "...", "strengths": [], "suggestions": [], "suggested_rewrites": [] },
"experience": { "score": 70, "summary": "...", "strengths": [], "suggestions": [], "suggested_rewrites": [] },
"education": { "score": 90, "summary": "...", "strengths": [], "suggestions": [], "suggested_rewrites": [] }
}
}
}Generate an AI-written outreach email and send it via Brevo with the resume attached.
Request — multipart/form-data
| Field | Type | Description |
|---|---|---|
resume |
File | PDF file of the resume |
to |
String | Recipient email address |
jobRole |
String | Target job role |
applicantName |
String | Applicant's full name |
Response
{
"success": true,
"email": {
"subject": "...",
"body": "..."
},
"meta": {
"sentTo": "hr@company.com",
"applicant": "Rishi Sharma"
}
}Home
└─► Analysis Page (POST /api/resume/analyse)
└─► Email Preview Page (POST /api/email/send)
└─► Success Page
Data flows between pages via React Router v6 useLocation state:
navigate('/analysis', {
state: { analysis, jobRole, applicantName }
})Note:
Fileobjects cannot be serialised through Router state. The resume file is re-selected on the Analysis page before sending the email.
| Variable | Required | Description |
|---|---|---|
PORT |
No | Server port (default: 5000) |
GROQ_API_KEY |
Yes | Groq API key for AI inference |
BREVO_API_KEY |
Yes | Brevo API key for email delivery |
EMAIL_USER |
Yes | Verified sender email in Brevo account |
- Push your backend to a GitHub repository.
- Create a new Web Service on Render.
- Set the Build Command to
npm installand Start Command tonode src/app.js. - Add all environment variables in the Render dashboard under Environment.
- Deploy — Render will auto-deploy on every push to
main.
Important: Render's free tier blocks SMTP ports (25, 465, 587). This is why the project uses Brevo's HTTP REST API instead of Nodemailer.
| Problem | Decision |
|---|---|
| Render blocks SMTP | Switched from Nodemailer → Brevo REST API over HTTPS |
pdf-parse ES Module errors |
Switched to pdf2json with a safeDecode wrapper |
| Groq JSON parsing issues | Extract JSON with /\{[\s\S]*\}/ regex, escape raw newlines before JSON.parse |
| File not serialisable via Router state | User re-selects the file on Analysis page instead of passing it through state |
| Context API overhead | Unnecessary for linear flow — useLocation state is sufficient |
- Proper Tailwind utility class rewrite (currently using custom CSS)
- UI/design polish across all pages
- Improved AI prompt with more specific section-level feedback
- Resume score history (local storage)
- Support for
.docxresume uploads
This is a personal portfolio project — but feel free to fork it, learn from it, or suggest improvements via issues.
Built with ❤️ by Rishi