AI-powered job application tracker that learns from your rejections.
Live in production. Free to use.
Applytic tracks every job application you submit, detects patterns across rejections (which resume version converts best, which source channel works, which company sizes respond), and uses Amazon Bedrock to turn that data into actionable coaching - delivered as a chat interface, tailored interview prep, rejection pattern alerts, and a weekly email digest.
Built end-to-end on AWS as a production-grade application. Every service is serverless, infrastructure is code, and every push auto-deploys via GitHub Actions.
I was job hunting and had no data on why I was getting rejected. I had a spreadsheet with company names and "rejected" written next to most of them - but no signal on why. Was it my resume? The channel? The company size?
So I instrumented my own job search. Every application became a data point. After a few weeks I had enough data to see that my v1-generic resume had a 0% response rate from enterprise companies, while v3-ml-focused was getting interviews from startups via referrals. That's the kind of insight you can act on.
Application tracking
- Log applications with company, role, source channel, resume version, company size, job description URL, follow-up date
- Kanban board with drag-and-drop status updates (Applied - Screened - Interview - Offer / Rejected)
- Click any card to view full detail, edit all fields, change status, see timeline
- Search by company/role, filter by source channel
- Color-coded left border per status on kanban cards for instant visual scanning
- Amber "Follow up" badge on overdue cards
Weekly goal tracking
- Set a weekly application target on the Dashboard
- Progress bar showing current week vs goal, turns green when met
- Streak counter - consecutive weeks hitting your goal (fire icon)
- Inline goal editing - click the pencil to update your target anytime
Follow-up reminders
- Attach a follow-up date to any application
- Daily email when applications are overdue for follow-up, grouped by user
- Never miss a follow-up again
Notes timeline
- Add timestamped notes to any application
- Full per-application audit trail
- Notes sorted oldest first for easy reading
CSV export and import
- Export all applications to a CSV with one click - no Lambda, pure client-side
- Import from CSV with full validation, preview, and error reporting
- Download import template to get started quickly
AI insight engine
- Pattern analysis across 6 dimensions: source channel, company size, resume version, role seniority, weekly velocity, status funnel
- Response rate computed per bucket - shows exactly which resume version or source is working
- AI coaching chat powered by Bedrock - answers questions like "why am I getting ghosted?" using your actual data as context
- Markdown rendering in chat responses
- Weekly email digest every Monday with stats + one AI-generated personalised tip
Interview prep
- Generate 10 tailored interview questions per application using the job description URL and Amazon Nova Lite
- Check off questions as you practice them and save your answer notes
- Regenerate questions any time - only visible for applications in Interview or Offer status
- Questions stored per application and persist across sessions
Rejection pattern alerts
- Automatic pattern detection runs every Monday alongside the weekly digest
- Detects: resume version with 0% response rate after 5+ applications, source channel with 0% response after 5+ applications, response rate dropping more than 20 points week-over-week
- Amber alert banners on the Dashboard - dismiss with one click
- Alerts also appear in the weekly digest email
Resume version tracker
- Upload multiple PDF versions to S3 via presigned URLs
- Tag each application with which version was used
- Analytics shows conversion rate per version side-by-side
UI / UX
- Full dark mode with system preference detection, persisted to localStorage
- Mobile responsive - hamburger sidebar on small screens
- Loading skeletons on every page instead of blank states
- Meaningful empty states with calls to action
- Toast notifications (top-center)
Single-table DynamoDB - two entity types (APPLICATION, STATUS_EVENT) in one table with composite keys. Every query is O(1). No joins. Scales to millions of requests/sec with no configuration changes.
Pattern analysis before LLM - the insights Lambda computes structured metrics (response rates per bucket) before calling Bedrock. The LLM receives hard numbers as context, not raw records. This makes advice data-driven and specific rather than generic.
Serverless-first - no EC2, no containers, no idle servers. Lambda + API Gateway + DynamoDB means ~$0 at low volume and automatic scaling. EventBridge replaces cron servers entirely.
ARM64 Lambda - all functions run on Graviton2 (ARM64) for ~20% cost reduction and faster cold starts vs x86.
Presigned S3 URLs for resume upload - the Lambda generates a presigned URL and returns it to the client. The file then uploads directly from the browser to S3 - never passes through Lambda.
Lambda Layer for shared code - single source of truth for CORS, auth extraction, error formatting, Pydantic validation, and X-Ray tracing. All seven Lambda handlers share it.
Client-side CSV - export and import are handled entirely in the browser. No Lambda invocation needed for either operation, keeping them fast and free.
Streak computation - looks back 8 weeks maximum, current week excluded (may be incomplete). Walks back week by week, breaks on first miss.
| Layer | Service |
|---|---|
| Frontend | React 18, TypeScript, Vite, Tailwind CSS, react-markdown |
| Auth | Amazon Cognito (email + JWT) |
| API | API Gateway REST + Lambda (Python 3.12, ARM64) |
| AI / ML | Amazon Bedrock - Amazon Nova Lite |
| Database | DynamoDB - single-table design, PAY_PER_REQUEST |
| Storage | S3 - resume versioning + frontend hosting |
| CDN | CloudFront (450+ edge locations) |
| Scheduling | EventBridge cron (Monday 8am UTC digest, daily 9am UTC follow-ups) |
| Amazon SES | |
| IaC | AWS CDK v2 TypeScript |
| CI/CD | GitHub Actions |
applytic/
├── cdk/ # AWS CDK v2 stack - all infrastructure as code
│ ├── bin/app.ts
│ └── lib/applytic-stack.ts
├── lambdas/ # Python 3.12 Lambda handlers
│ ├── applications/ # CRUD + presigned S3 URL generation
│ ├── insights/ # Pattern analysis + Bedrock AI coaching
│ ├── digest/ # Weekly SES email digest
│ ├── followup/ # Daily follow-up reminder emails
│ ├── settings/ # Weekly goal + streak tracking
│ ├── notes/ # Per-application notes timeline
│ ├── cognito_verify/ # Post Confirmation trigger - SES email verification
│ └── shared_layer/ # Lambda Layer - shared middleware, Pydantic, X-Ray
├── frontend/ # React + Vite + Tailwind CSS
│ └── src/
│ ├── components/ # Kanban, Analytics, Chat, Sidebar, Resume, CSV
│ ├── hooks/ # useApplications, useSettings, useNotes
│ ├── lib/ # API client, Amplify config, theme, csv utils
│ ├── pages/ # Dashboard
│ └── types/ # Shared TypeScript types
├── tests/ # 207 pytest tests (90.75% coverage)
│ ├── test_applications.py
│ ├── test_applications_integration.py
│ ├── test_insights.py
│ ├── test_insights_integration.py
│ ├── test_digest.py
│ ├── test_cognito_verify.py
│ ├── test_followup.py
│ ├── test_settings.py
│ └── test_notes.py
├── scripts/
│ ├── seed_data.py
│ ├── build_layer.sh
│ └── setup_oidc_v11.sh
└── .github/workflows/
├── deploy.yml
└── codeql.yml
Table name: applytic
| Entity | PK | SK | GSI1PK | GSI1SK |
|---|---|---|---|---|
| Application | USER#userId |
APP#appId |
USER#userId |
DATE#timestamp |
| Status event | APP#appId |
EVENT#timestamp |
- | - |
| Rate limit | RATELIMIT#userId |
DATE#YYYY-MM-DD |
- | - |
| User settings | USER#userId |
SETTINGS |
- | - |
| Note | APP#appId |
NOTE#timestamp#noteId |
- | - |
Access patterns:
- List all applications for user - GSI1 query on
USER#userIdsorted by date - Get single application - Main table get on
USER#userId+APP#appId - Get status history for an app - Query
APP#appIdwithEVENT#prefix - Get notes for an app - Query
APP#appIdwithNOTE#prefix - Get user settings - Get on
USER#userId+SETTINGS
GET /v1/applications
POST /v1/applications
GET /v1/applications/{appId}
PUT /v1/applications/{appId}
DELETE /v1/applications/{appId}
POST /v1/applications/{appId}/status
GET /v1/applications/{appId}/notes
POST /v1/applications/{appId}/notes
DELETE /v1/applications/{appId}/notes/{noteId}
POST /v1/resumes/upload-url
GET /v1/resumes/list
GET /v1/insights
POST /v1/insights/chat
GET /v1/users/settings
PUT /v1/users/settings
POST /v1/applications/{appId}/interview-prep/generate
GET /v1/applications/{appId}/interview-prep
PUT /v1/applications/{appId}/interview-prep/{questionId}
GET /v1/users/alerts
PUT /v1/users/alerts/{alertId}/dismiss
All routes protected by Cognito JWT authorizer.
| Metric | Value |
|---|---|
| API throughput | 10,000 req/sec (API Gateway default) |
| DynamoDB SLA | 99.999% availability |
| Lambda cold start | ~300-400ms (Python 3.12 ARM64) |
| CloudFront edge locations | 450+ worldwide |
| Cost at 0 users | ~$0/month |
| Cost at 100 users | ~$2-5/month |
| Cost at 1,000 users | ~$15-30/month |
| Backend test suite | 410 tests, 70% coverage threshold |
| Frontend test suite | 31 Vitest tests |
| Full CDK deploy from scratch | under 3 minutes |
push to main
├── test (pytest 410 tests, 70% coverage threshold)
├── test-frontend (vitest 31 tests)
└── (both must pass before)
├── deploy-backend (cdk deploy)
├── deploy-frontend-aws (s3 sync + cloudfront invalidation)
└── deploy-frontend-pages (GitHub Pages)
- Node.js 18+
- Python 3.12
- AWS CLI configured (
aws configure) - AWS CDK CLI:
npm install -g aws-cdk
bash scripts/build_layer.sh
cd cdk && npm install && cdk deploycd frontend && npm install && npm run devpip install pytest boto3 pydantic aws-lambda-powertools pytest-cov
pip install "moto[dynamodb,s3,ses,cognitoidp]"
python -m pytest tests/ -v --tb=short --cov=lambdas --cov-fail-under=70cd frontend && npm run testcd scripts && python seed_data.py --user-id YOUR_COGNITO_SUB- Issue:
runtimebecame optional when usingPartial<FunctionProps>, causing TypeScript to reject undefined values. - Fix: Added
runtimeexplicitly to each Lambda and removed the shared defaults spread.
- Issue: esbuild failed due to corrupted dependencies or a Node.js version mismatch.
- Fix: Deleted
node_modulesandpackage-lock.json, reinstalled dependencies, and used Node 18/20 LTS.
- Issue: Multiple Lambda handlers were named
handler.py, causing import collisions during tests. - Fix: Used
importlib.util.spec_from_file_location()with unique module names.
- Issue:
dateAppliedwas stored as aYYYY-MM-DDstring and could not be subtracted from a timezone-aware datetime. - Fix: Detected 10-character date strings and added UTC timezone information before subtraction.
- Issue: Component-level
useStatewas reset whenever the component unmounted. - Fix: Lifted message state to
App.tsxand passed it down as props.
📋 Check out all the issues faced and solved: Issues_Encountered_and_Fixed.md
See CHANGELOG.md for full version history.
See CONTRIBUTING.md for setup instructions, branching conventions, and the PR process.
Hardik - Github

