How to Automate New Grad Job Applications with GitHub Actions and Simplify API in 2025
How to Automate New Grad Job Applications with GitHub Actions and Simplify API in 2025
If you're a new grad hunting for software engineering roles, you've probably discovered the SimplifyJobs/New-Grad-Positions repository—a continuously updated list of 300+ entry-level positions. But manually checking this repo daily and filling out applications is tedious. This guide shows you how to build a GitHub Actions automation pipeline that monitors new job postings and streamlines your application process.
Why Automate Your Job Search Pipeline
The SimplifyJobs repository updates multiple times per day with new roles from companies like FAANG, startups, and enterprise organizations. The repo currently tracks:
- 194 Software Engineering positions
- 45 Data Science/ML roles
- 53 Hardware Engineering openings
- 5 Product Management positions
- 5 Quantitative Finance roles
Manually checking this repo wastes 15-30 minutes daily. More critically, you might miss time-sensitive applications that close within hours. By automating the monitoring and application preparation process, you can respond to new postings within minutes instead of days.
Prerequisites and Setup
Before building the automation, ensure you have:
- A GitHub account with Actions enabled (free tier works)
- Basic knowledge of YAML for GitHub Actions workflows
- Node.js 18+ installed locally for testing
- A Simplify account (free tier available at simplify.jobs)
- Your resume and cover letter templates in markdown format
Step 1: Create the Repository Monitor Script
First, create a Node.js script that fetches and parses the SimplifyJobs repository data. Create a new file monitor-jobs.js:
const https = require('https');
const fs = require('fs');
const REPO_URL = 'https://raw.githubusercontent.com/SimplifyJobs/New-Grad-Positions/dev/README.md';
const CACHE_FILE = './job-cache.json';
function fetchRepoContent() {
return new Promise((resolve, reject) => {
https.get(REPO_URL, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => resolve(data));
}).on('error', reject);
});
}
function parseJobListings(markdown) {
const jobs = [];
const tableRegex = /<tr>\s*<td><strong><a[^>]*>([^<]+)<\/a><\/strong><\/td>\s*<td>([^<]+)<\/td>\s*<td>([^<]+)<\/td>\s*<td[^>]*>.*?href="([^"]+)"/gs;
let match;
while ((match = tableRegex.exec(markdown)) !== null) {
const [, company, role, location, applicationUrl] = match;
// Skip closed positions (marked with 🔒)
if (role.includes('🔒')) continue;
jobs.push({
company: company.trim(),
role: role.trim(),
location: location.trim(),
applicationUrl: applicationUrl.trim(),
foundAt: new Date().toISOString()
});
}
return jobs;
}
async function getNewJobs() {
const content = await fetchRepoContent();
const currentJobs = parseJobListings(content);
let cachedJobs = [];
if (fs.existsSync(CACHE_FILE)) {
cachedJobs = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
}
const cachedUrls = new Set(cachedJobs.map(j => j.applicationUrl));
const newJobs = currentJobs.filter(j => !cachedUrls.has(j.applicationUrl));
// Update cache
fs.writeFileSync(CACHE_FILE, JSON.stringify(currentJobs, null, 2));
return newJobs;
}
getNewJobs().then(newJobs => {
console.log(`Found ${newJobs.length} new job postings`);
console.log(JSON.stringify(newJobs, null, 2));
// Write to output file for GitHub Actions
fs.writeFileSync('./new-jobs.json', JSON.stringify(newJobs, null, 2));
}).catch(console.error);
This script:
- Fetches the latest README from the SimplifyJobs repository
- Parses HTML table rows to extract job details
- Filters out closed positions (marked with 🔒 emoji)
- Compares against cached results to identify new listings
- Outputs new jobs to a JSON file for processing
Step 2: Configure GitHub Actions Workflow
Create .github/workflows/monitor-jobs.yml to run the script on a schedule:
name: Monitor New Grad Jobs
on:
schedule:
- cron: '0 */4 * * *' # Run every 4 hours
workflow_dispatch: # Allow manual triggers
jobs:
check-new-jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Restore job cache
uses: actions/cache@v3
with:
path: job-cache.json
key: job-cache-${{ github.run_id }}
restore-keys: job-cache-
- name: Run job monitor
run: node monitor-jobs.js
- name: Check for new jobs
id: check
run: |
if [ -f new-jobs.json ]; then
COUNT=$(jq length new-jobs.json)
echo "count=$COUNT" >> $GITHUB_OUTPUT
if [ $COUNT -gt 0 ]; then
echo "has_new_jobs=true" >> $GITHUB_OUTPUT
fi
fi
- name: Send notification
if: steps.check.outputs.has_new_jobs == 'true'
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.gmail.com
server_port: 465
username: ${{ secrets.EMAIL_USERNAME }}
password: ${{ secrets.EMAIL_PASSWORD }}
subject: '${{ steps.check.outputs.count }} New Grad Jobs Found'
body: file://new-jobs.json
to: your-email@example.com
from: Job Monitor
- name: Save cache
uses: actions/cache/save@v3
if: always()
with:
path: job-cache.json
key: job-cache-${{ github.run_id }}
Step 3: Filter Jobs by Your Criteria
Enhance the monitoring script to filter jobs based on your preferences. Add this filtering function:
function filterJobs(jobs, criteria) {
return jobs.filter(job => {
// Location filtering
if (criteria.locations && criteria.locations.length > 0) {
const matchesLocation = criteria.locations.some(loc =>
job.location.toLowerCase().includes(loc.toLowerCase()) ||
job.location.toLowerCase().includes('remote')
);
if (!matchesLocation) return false;
}
// Exclude roles requiring citizenship if you don't have it
if (criteria.excludeCitizenship && job.role.includes('🇺🇸')) {
return false;
}
// Exclude roles without sponsorship if you need it
if (criteria.requiresSponsorship && job.role.includes('🛂')) {
return false;
}
// Company filtering
if (criteria.excludeCompanies && criteria.excludeCompanies.length > 0) {
if (criteria.excludeCompanies.some(c =>
job.company.toLowerCase().includes(c.toLowerCase())
)) {
return false;
}
}
return true;
});
}
// Usage example
const criteria = {
locations: ['San Francisco', 'New York', 'Remote'],
requiresSponsorship: true,
excludeCitizenship: false,
excludeCompanies: ['Defense', 'Military']
};
const filteredJobs = filterJobs(newJobs, criteria);
Step 4: Integrate with Simplify's Autofill Extension
The SimplifyJobs repository promotes Simplify's browser extension for one-click applications. You can programmatically prepare application data for Simplify:
function generateSimplifyData(job, resumeData) {
return {
company: job.company,
position: job.role,
applicationUrl: job.applicationUrl,
applicantData: {
fullName: resumeData.fullName,
email: resumeData.email,
phone: resumeData.phone,
location: resumeData.location,
resumeUrl: resumeData.resumeUrl,
coverLetterUrl: resumeData.coverLetterUrl,
linkedIn: resumeData.linkedIn,
github: resumeData.github,
portfolio: resumeData.portfolio
},
timestamp: new Date().toISOString()
};
}
Step 5: Set Up Notification System
Beyond email, you can integrate with multiple notification services. Here's a Slack webhook integration:
const https = require('https');
function sendSlackNotification(jobs, webhookUrl) {
const message = {
text: `🚨 ${jobs.length} New Grad Positions Posted`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*${jobs.length} new positions* found in SimplifyJobs repository`
}
},
...jobs.slice(0, 5).map(job => ({
type: 'section',
text: {
type: 'mrkdwn',
text: `*${job.company}* - ${job.role}\n📍 ${job.location}\n<${job.applicationUrl}|Apply Now>`
}
}))
]
};
const data = JSON.stringify(message);
const url = new URL(webhookUrl);
const options = {
hostname: url.hostname,
path: url.pathname,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': data.length
}
};
return new Promise((resolve, reject) => {
const req = https.request(options, resolve);
req.on('error', reject);
req.write(data);
req.end();
});
}
Comparison: Manual vs Automated Job Tracking
| Aspect | Manual Checking | Automated Pipeline | |--------|----------------|--------------------| | Time per day | 15-30 minutes | 2 minutes (reviewing notifications) | | Response speed | Hours to days | Minutes | | Coverage | 50-100 jobs/week | All 300+ jobs | | Filtering | Manual scanning | Automatic by location/criteria | | Missed opportunities | High (time zones, sleep) | Minimal | | Application prep | Manual data entry | Pre-filled with Simplify | | Cost | Free | Free (GitHub Actions free tier) |
Advanced: Add Application Tracking
Extend your automation to track application status:
function trackApplication(job, status) {
const tracking = {
jobId: Buffer.from(job.applicationUrl).toString('base64'),
company: job.company,
role: job.role,
appliedAt: new Date().toISOString(),
status: status, // 'applied', 'interviewing', 'offer', 'rejected'
notes: ''
};
let applications = [];
if (fs.existsSync('./applications.json')) {
applications = JSON.parse(fs.readFileSync('./applications.json'));
}
applications.push(tracking);
fs.writeFileSync('./applications.json', JSON.stringify(applications, null, 2));
}
Troubleshooting Common Issues
Issue: GitHub Actions not triggering
Solution: Ensure your repository has Actions enabled. Go to Settings → Actions → General and verify "Allow all actions" is selected. The workflow_dispatch trigger allows manual testing.
Issue: Parsing fails after repo format changes
Solution: The SimplifyJobs repo updates its format occasionally. Add error handling and fallback parsing:
function parseJobListings(markdown) {
try {
// Primary parsing logic
return parseTableFormat(markdown);
} catch (error) {
console.error('Primary parser failed:', error);
// Fallback to regex-based parsing
return parseFallback(markdown);
}
}
Issue: Rate limiting on repository fetches
Solution: Use GitHub's API with authentication instead of raw content fetching:
const options = {
hostname: 'api.github.com',
path: '/repos/SimplifyJobs/New-Grad-Positions/contents/README.md',
headers: {
'User-Agent': 'Job-Monitor',
'Authorization': `token ${process.env.GITHUB_TOKEN}`
}
};
Deployment to Production
- Fork the SimplifyJobs repository: This allows you to customize the list and add personal notes
- Set up GitHub Secrets: Store email credentials and API keys in repository secrets
- Configure cron schedule: Adjust based on how frequently the repo updates (every 4 hours is optimal)
- Enable branch protection: Prevent accidental workflow deletions
- Monitor GitHub Actions usage: Free tier includes 2,000 minutes/month, sufficient for this use case
Integrating with Application Tracking Systems
For serious job seekers, connect your automation to tools like Notion, Airtable, or Trello:
async function addToNotion(job, notionApiKey, databaseId) {
const response = await fetch('https://api.notion.com/v1/pages', {
method: 'POST',
headers: {
'Authorization': `Bearer ${notionApiKey}`,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
body: JSON.stringify({
parent: { database_id: databaseId },
properties: {
'Company': { title: [{ text: { content: job.company } }] },
'Role': { rich_text: [{ text: { content: job.role } }] },
'Location': { rich_text: [{ text: { content: job.location } }] },
'Status': { select: { name: 'To Apply' } },
'URL': { url: job.applicationUrl }
}
})
});
return response.json();
}
Next Steps and Optimization
Once your basic automation works, consider these enhancements:
- AI-powered cover letter generation: Use OpenAI's API to customize cover letters per job
- Company research automation: Fetch company data from Crunchbase or LinkedIn
- Application deadline tracking: Parse and alert on upcoming deadlines
- Success metrics: Track application-to-interview conversion rates by company type
- Network integration: Cross-reference jobs with your LinkedIn connections at those companies
By automating the tedious parts of job hunting, you free up time for what actually matters: preparing for interviews, building projects, and networking. This GitHub Actions pipeline ensures you never miss an opportunity while maintaining sanity during the stressful new grad job search.
The SimplifyJobs repository updates continuously, and with this automation in place, you'll be among the first to know—and apply—when your dream role appears.
Recommended Tools
- GitHubWhere the world builds software
- DigitalOceanSimplicity in the cloud
- VercelDeploy web apps at the speed of inspiration