- Updated features list with navigation bar, HR notifications, and security features - Added navigation and HR features sections with usage examples - Updated file structure to show modular app/ package - Changed running instructions from app.py to run.py - Added mailcatcher as default SMTP configuration - Included testing instructions and architecture documentation - Updated data.yaml example with status and submitted_at fields - Added comprehensive Security Features section (CSRF, rate limiting) - Added Architecture section explaining factory pattern and modular structure - Enhanced Troubleshooting section with mailcatcher, testing, and navigation issues - Updated technology stack with all dependencies and versions
Job Application System
A professional, accessible Flask web application for job applications with email-based resume functionality, file uploads, and HR notifications.
Features
- Multi-step application process: 5-page workflow with progress navigation
- Progress navigation bar: Visual indicator with backward navigation to edit previous pages
- Email-based resume: Applicants receive a unique link to resume their application
- HR notifications: Automated email alerts to HR when applications are submitted
- Application review page: Dedicated HR interface to view applications and download attachments
- Stateful sessions: Applications can be paused and resumed at any time
- File uploads: Support for CV, cover letter, and other documents (up to 3 files, 4 MB each)
- German interface: All user-facing content in German
- File-based storage: No database required - uses YAML for data serialization
- Security features: CSRF protection and rate limiting (5-second cooldown)
- Modular architecture: Clean separation of concerns with application factory pattern
- Accessible design: Minimal CSS, no JavaScript required, keyboard-friendly
- Validation: Comprehensive server-side validation for all inputs
- Full test coverage: 102 automated tests with 100% pass rate
Application Workflow
- Email Capture: User enters email and receives a resume link
- Personal Information: Name, address, phone, birth year, etc.
- Motivation Questions: Current job situation, motivation, qualifications, salary expectations
- Document Upload: Upload up to 3 documents (CV, cover letter, etc.)
- Confirmation: Success message with next steps
Navigation Features
- Progress bar: Visual indicator shows current step and completion status
- Backward navigation: Users can click on completed steps to return and edit data
- Sequential progression: Users must complete pages in order (cannot skip ahead)
- Data preservation: All entered information is automatically saved when navigating
HR Features
- Email notifications: HR receives an email when an application is submitted
- Review interface: Access applications at
/application/{session-id}/ - Download attachments: HR can download all uploaded documents
- Complete data view: See all applicant information in one place
Technology Stack
- Python 3.8+
- Flask 3.0.0 (with application factory pattern)
- Flask-WTF 1.2.1 (CSRF protection)
- Flask-Mail 0.9.1 (email sending)
- PyYAML 6.0.1 (data serialization)
- Werkzeug 3.0.1 (file handling)
- pytest 7.4.3 (testing framework)
- Minimal CSS for styling
- No JavaScript required
Installation
Prerequisites
- Python 3.8 or higher
- pip (Python package manager)
Setup Steps
-
Clone or download this repository
-
Create a virtual environment (recommended):
python -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate -
Install dependencies:
pip install -r requirements.txt -
Configure environment variables:
- Copy
.env.exampleto.env:cp .env.example .env - Edit
.envand configure your settings:SECRET_KEY=your-secret-key-here COMPANY_NAME=Your Company Name HR_EMAIL=hr@example.com APPLICATION_URL_BASE=http://localhost:5000
- Copy
Email Configuration
The application defaults to mailcatcher for development (localhost:1025, no authentication required).
For Development (Mailcatcher)
-
Install mailcatcher:
gem install mailcatcher -
Run mailcatcher:
mailcatcher -
View emails: Open http://localhost:1080 in your browser
-
No .env changes needed - the application uses mailcatcher by default
For Production (Real SMTP)
Add to your .env file:
MAIL_SERVER=smtp.gmail.com
MAIL_PORT=587
MAIL_USE_TLS=True
MAIL_USERNAME=your-email@example.com
MAIL_PASSWORD=your-app-password
For Gmail:
- Enable 2-factor authentication on your Google account
- Generate an "App Password" at https://myaccount.google.com/apppasswords
- Use the app password (not your regular password) in
MAIL_PASSWORD
For other email providers, adjust MAIL_SERVER and MAIL_PORT accordingly.
Running the Application
Development Mode
python run.py
Or using Flask CLI:
flask run
The application will run at http://127.0.0.1:5000
Running Tests
The application includes a comprehensive test suite with 102 tests:
# Run all tests
pytest tests/ -v
# Run with coverage report
pytest tests/ --cov=app --cov-report=html
# Run specific test file
pytest tests/test_routes.py -v
Production Deployment
For production, use a WSGI server like Gunicorn:
-
Install Gunicorn:
pip install gunicorn -
Run with Gunicorn:
gunicorn -w 4 -b 0.0.0.0:8000 "app:create_app()" -
Use a reverse proxy (e.g., Nginx) in front of Gunicorn
-
Update production settings in
.env:WTF_CSRF_SSL_STRICT=True SESSION_COOKIE_SECURE=True MAIL_SERVER=your-production-smtp-server
Usage
Linking from Job Postings
Link to the application with a job parameter:
https://your-domain.com/apply?job=JuniorMarketingAssistant
The job parameter will be displayed throughout the application and stored with the application data.
Resume Functionality
When applicants enter their email, they receive a link like:
https://your-domain.com/resume/{session-id}
This link allows them to resume their application at any time.
File Structure
.
├── run.py # Application entry point
├── config.py # Configuration settings
├── requirements.txt # Python dependencies
├── .env.example # Example environment variables
├── README.md # This file
├── app/ # Main application package
│ ├── __init__.py # Application factory
│ ├── routes.py # Route handlers (all endpoints)
│ ├── models.py # Data persistence layer
│ ├── validators.py # Input validation functions
│ ├── email_service.py # Email sending functions
│ └── utils.py # Utility functions
├── templates/ # HTML templates
│ ├── base.html # Base template with navigation
│ ├── _navigation.html # Progress bar component
│ ├── page1_email.html # Email capture page
│ ├── page2_personal.html # Personal information page
│ ├── page3_motivation.html # Motivation questions page
│ ├── page4_upload.html # Document upload page
│ ├── page5_confirmation.html # Confirmation page
│ └── application_view.html # HR application review page
├── static/ # Static files
│ └── style.css # Styles (including navigation)
├── tests/ # Test suite (102 tests)
│ ├── conftest.py # Test configuration and fixtures
│ ├── test_routes.py # Route handler tests
│ ├── test_validation.py # Validation function tests
│ ├── test_email.py # Email functionality tests
│ ├── test_storage.py # Data persistence tests
│ ├── test_uploads.py # File upload tests
│ ├── test_integration.py # End-to-end workflow tests
│ ├── test_hr_notifications.py # HR email notification tests
│ ├── test_application_view.py # Application review page tests
│ └── test_config.py # Configuration tests
└── applications/ # Application data (created automatically)
└── {session-id}/
├── data.yaml # Application data
└── attachments/ # Uploaded files
Data Storage
Each application is stored in its own folder under ./applications/{session-id}/:
data.yaml: Application data (personal info, answers, metadata)attachments/: Uploaded documents
Example data.yaml structure:
session_id: "abc123-def456-..."
email: "applicant@example.com"
job_name: "Junior Marketing Assistant"
current_page: 5
status: "submitted"
created_at: "2025-01-15T10:30:00"
updated_at: "2025-01-15T11:45:00"
submitted_at: "2025-01-15T11:45:00"
personal_info:
name: "Müller"
firstname: "Anna"
address: "Hauptstrasse 123"
zip_code: "8001"
city: "Zürich"
phone: "+41 79 123 45 67"
birth_year: "1990"
civil_status: "ledig"
motivation_answers:
current_job: "..."
motivation: "..."
qualifications: "..."
salary: "..."
uploaded_files:
- original_name: "CV.pdf"
stored_name: "20250115_103000_CV.pdf"
uploaded_at: "2025-01-15T10:30:00"
size: 1048576
Validation Rules
Email (Page 1)
- Valid email format required
Personal Information (Page 2)
- Name: Required, max 255 characters
- Firstname: Required, max 255 characters
- Address: Required, max 255 characters
- ZIP Code: Required, numeric, max 10 digits
- City: Required, max 255 characters
- Phone: Required, international format (e.g., +41 79 123 45 67)
- Birth Year: Required, 4 digits, between 1940 and 2010
- Civil Status: Optional, max 255 characters
Motivation (Page 3)
- All fields optional
- Each textarea limited to 3000 characters (~1 A4 page)
File Upload (Page 4)
- Maximum 3 files
- Maximum 4 MB per file
- Allowed formats: PDF, DOC, DOCX, TXT, JPG, JPEG, PNG
Security Features
CSRF Protection
All forms are protected against Cross-Site Request Forgery (CSRF) attacks using Flask-WTF:
- CSRF tokens required on all POST requests
- Tokens included automatically in all forms
- No token expiration (users can take time filling forms)
- Configurable for production HTTPS environments
Rate Limiting
Built-in rate limiting prevents form spam and abuse:
- 5-second cooldown between form submissions (configurable)
- Session-based tracking
- Customizable in
config.py:RATE_LIMIT_SECONDS = 5
Data Security
- Session IDs: Cryptographically secure UUIDs (not guessable)
- File uploads: Validated for size, type, and sanitized filenames
- Path traversal protection: Secure filename handling prevents directory attacks
- Server-side validation: All input validated on the server
- Environment variables: Sensitive configuration kept in
.envfile - No SQL injection: File-based storage eliminates SQL vulnerabilities
Production Security Checklist
When deploying to production, update .env:
SECRET_KEY=<strong-random-secret-key>
WTF_CSRF_SSL_STRICT=True
SESSION_COOKIE_SECURE=True
SESSION_COOKIE_HTTPONLY=True
SESSION_COOKIE_SAMESITE=Lax
HR Features
Accessing Applications
HR staff can review applications using the direct URL:
https://your-domain.com/application/{session-id}/
The session ID is included in the HR notification email sent when an application is submitted.
HR Notification Emails
Configure HR email notifications in .env:
HR_EMAIL=hr@example.com
APPLICATION_URL_BASE=https://your-domain.com
When an applicant submits their application (page 4), an email is automatically sent to the HR email address containing:
- Applicant name and email
- Position applied for
- Submission timestamp
- Number of uploaded documents
- Direct link to the application review page
Application Review Page
The review page (/application/{session-id}/) displays:
- Complete applicant information (all form fields)
- All uploaded documents with download links
- Submission timestamp and metadata
- Professional formatting for easy review
Downloading Attachments
HR can download individual files directly from the review page:
https://your-domain.com/application/{session-id}/download/{filename}
Security: Only files listed in the application's uploaded_files list can be downloaded (prevents unauthorized access).
Customization
Changing Text Limits
Edit config.py:
MAX_STRING_LENGTH = 255
MAX_TEXT_AREA_LENGTH = 3000
MAX_FILE_SIZE = 4 * 1024 * 1024 # 4 MB
MAX_FILES = 3
Changing Allowed File Types
Edit config.py:
ALLOWED_EXTENSIONS = {'pdf', 'doc', 'docx', 'txt', 'jpg', 'jpeg', 'png'}
Translating to Another Language
Edit all templates in ./templates/ - they contain German text that can be translated.
Architecture
Application Factory Pattern
The application uses Flask's application factory pattern for better testability and flexibility:
app/__init__.pycontains thecreate_app()function- Extensions (Flask-Mail, CSRF) are initialized within the factory
- Multiple app instances can be created (useful for testing)
- Configuration can be passed as a parameter
Modular Structure
Code is organized by responsibility:
- routes.py (473 lines): All route handlers and request processing
- models.py (41 lines): Data persistence layer (YAML operations)
- validators.py (48 lines): Input validation functions
- email_service.py (85 lines): Email sending logic
- utils.py (30 lines): Utility functions (rate limiting, etc.)
Benefits:
- Easy to test individual modules in isolation
- Clear separation of concerns
- Easier to navigate and maintain
- Supports team collaboration
Testing Strategy
The application includes 102 automated tests organized by functionality:
- test_routes.py: Route handler behavior
- test_validation.py: Input validation logic
- test_email.py: Email sending functionality
- test_storage.py: YAML data persistence
- test_uploads.py: File upload handling
- test_integration.py: End-to-end workflows
- test_hr_notifications.py: HR email notifications
- test_application_view.py: Application review page
- test_config.py: Configuration settings
Run tests frequently during development to catch regressions early.
Troubleshooting
Email not sending in development
Using Mailcatcher (recommended for development):
-
Verify mailcatcher is running:
mailcatcher -
Check mailcatcher web interface at http://localhost:1080
-
Verify application is configured for mailcatcher:
- Default settings work out of the box (localhost:1025)
- No
.envemail configuration needed for development
Using real SMTP:
- Check your email credentials in
.env - For Gmail, ensure you're using an app password, not your regular password
- Check that
MAIL_USE_TLSis set correctly for your email provider - Check application logs for error messages
Tests failing
If tests fail after making changes:
# Run specific test file to identify the issue
pytest tests/test_routes.py -v
# Run with detailed output
pytest tests/ -vv
# Check test coverage
pytest tests/ --cov=app --cov-report=term-missing
Common issues:
- Import errors: Check module structure in
app/package - Template errors: Verify templates exist in
templates/directory - CSRF errors: Tests disable CSRF in
conftest.py
File upload errors
- Ensure the
./applications/folder exists and is writable - Check file size limits in your web server configuration
- Verify allowed file extensions in
config.py - Check that storage path is correct in config
Session not resuming
- Check that the
./applications/{session-id}/folder exists - Verify that
data.yamlis valid YAML format - Check application logs for errors
- Ensure session ID in URL is correct (UUID format)
Navigation not working
- Verify
current_pageis being passed to templates - Check that navigation component is included in
base.html - Ensure session data contains valid
current_pagevalue - Check browser console for CSS/styling issues
Rate limiting preventing form submission
- Check
RATE_LIMIT_SECONDSinconfig.py(default: 5 seconds) - Wait the specified time between submissions
- For testing, set to 0 in
.env:RATE_LIMIT_SECONDS=0
License
This project is provided as-is for use in job application processes.
Support
For issues or questions, please contact your system administrator.