A self-hosted job alert bot for jobs that are actually remote (not just US-remote).
- Why this exists
- How it works
- Features
- Tech stack
- Quick start
- Configuration guide
- Usage tips
- Notifications setup
- Adding companies
- Company Discovery
- How it runs
- Contributing
- Roadmap
- License
The best remote jobs aren't all in San Francisco. Some of the most interesting tech companies in the world are based in Stockholm, Berlin, Prague, Amsterdam, and they hire globally. The problem isn't the jobs. The problem is finding them.
Job boards are built around the US market. "Remote" means remote in the US. "Europe" means Germany or UK if you're lucky. If you're in Switzerland, Portugal, or anywhere else, you're missing out on great opportunities. And the companies worth working for? Many of them don't bother posting on LinkedIn or Indeed at all. They have a careers page, a few open roles, and no marketing budget for job boards.
Actually Remote watches those companies directly. You define the list, set your job titles and location, drop in your CV, and it tells you when something relevant shows up automatically everyday.
- You define your target job titles and location keywords in
config.yaml - Actually Remote watches your target companies daily (via GitHub Actions)
- Each job passes through a location filter (fast, free, no AI)
- Matching jobs are scored against your CV by AI (fit score, reasons for/against)
- You receive a daily digest with only relevant matches (email, Discord, or Telegram)
- Optionally run the discovery agent to expand your list. Use the AI agent that searches the web in real time to find remote-friendly companies worth tracking
companies.csv → scraper → location filter → AI scoring → your inbox / Discord / Telegram
- Smart batch scheduling: 1/7 of companies per day, priority companies checked daily
- Location pre-filtering before AI: fast, free, reduces API usage
- AI CV matching with fit score, reasons for/against, and recommendation
- Pluggable AI provider: Gemini (default), Claude, OpenAI, Ollama
- You can select the right communication channel for you:
- Daily email digest: one email per day with all matches
- Discord notifications:one message per job
- Telegram notifications: one message per job
- Deduplication: never alerts on the same job twice
--dry-runmode: validate URLs, no AI, no cost--testmode: full pipeline with mock AI, no API cost- Community-maintained company list: CSV, editable in browser
- PR validation: auto dry-run on
companies.csvchanges - Zero cost to run: GitHub Actions free tier + Gemini free tier
- Company discovery agent: finds new remote-friendly companies you haven't heard of yet using AI + Google Search
| Component | Technology |
|---|---|
| Language | Python 3.12 |
| Scraping | requests + BeautifulSoup4 |
| AI | Google Gemini (pluggable) |
| Discovery Agent | Google Gemini + Google Search grounding |
| Resend | |
| Config | YAML |
| Company list | CSV |
| Scheduling | GitHub Actions |
| Storage | Flat files (no database) |
- Click Fork in the top right
- Set your fork to Private (recommended, it keeps your job search private)
⚠️ Important: Go to Settings → Actions → General → set "Workflow permissions" to "Read and write permissions"
git clone https://github.com/YOUR_USERNAME/actually-remote.git
cd actually-remotecp config.example.yaml config.yamlThen edit config.yaml with your job titles and location keywords. Example:
target_titles:
- "Engineer"
- "Developer"
- "Manager"
location_keywords:
- "Remote"
- "EMEA"
- "Europe"
- "Switzerland"cp cv.example.txt cv.txtReplace the content with your real CV. Plain text works best, no formatting needed.
⚠️ Important: Replace the contents ofseen_jobs.jsonwith{}. Otherwise you will inherit the author's job history and miss real matches.
See Notifications setup below.
Go to your repo → Settings → Secrets and variables → Actions. Add these secrets:
| Secret | Value |
|---|---|
| GEMINI_API_KEY | Your Gemini API key (free at aistudio.google.com) |
| CONFIG_YAML | Your entire config.yaml file content |
| CV_TEXT | Your entire cv.txt file content |
| RESEND_API_KEY | Your Resend API key (free at resend.com) |
| EMAIL_FROM | Sender email (use onboarding@resend.dev for testing) |
| EMAIL_TO | Your email address |
| DISCORD_WEBHOOK_URL | Your Discord webhook URL (optional) |
| TELEGRAM_BOT_TOKEN | Your Telegram bot token (optional) |
| TELEGRAM_CHAT_ID | Your Telegram chat ID (optional) |
Go to the Actions tab in your fork and click: "I understand my workflows, go ahead and enable them"
Run locally first:
python main.py --dry-run # validate URLs, no AI needed
python main.py --test # full pipeline, mock AI, real notificationsTrigger your first real run from the Actions tab: Actions → Daily Job Scraper → Run workflow
Every option in config.yaml:
| Option | Description | Example |
|---|---|---|
ai_provider |
AI provider for CV matching | gemini (default), claude, openai, ollama |
ai_model |
Model identifier | gemini-flash-latest |
min_fit_score |
Minimum score for regular companies (1–10) | 7 |
priority_min_fit_score |
Minimum score for priority companies — lower threshold since you care more | 6 |
target_titles |
Job must contain at least one. Partial matching: "Engineer" matches "Solutions Engineer" | ["Engineer", "Developer"] |
location_keywords |
Job title or description must contain at least one. Runs before AI — fast and free | ["Remote", "EMEA", "Switzerland"] |
notification_channels |
Which channels to use | [discord, email, telegram] |
email.provider |
Email provider | resend |
email.send_if_no_matches |
Send "no matches" email when nothing found | false |
discord.send_if_no_matches |
Send "no matches" summary to Discord | false |
telegram.send_if_no_matches |
Send "no matches" summary to Telegram | false |
rotation_days |
Days between scrapes for non-priority companies | 7 |
Getting better matches:
- Use partial keywords: "Engineer" catches all engineer roles
- Add your target companies as
priority: truefor daily checking - Start with a broad location list, narrow down if too noisy
- Lower
priority_min_fit_scorefor companies you really want
Understanding the batch schedule:
- By default, 1/7 of your company list is checked each day, full cycle every week
- Mark companies as priority: true to check them every single day
- Checking too frequently can trigger bot detection on some career pages
- Small list (under 20 companies)? Set rotation_days: 1 to check everything daily
- When in doubt, use priority: true for the companies you really care about
Managing your company list:
- Use the default list as a starting point
- Delete companies in sectors you don't care about
- Add your dream companies as
priority: true - Pull upstream updates periodically to get new community companies:
git remote add upstream https://github.com/cslylla/actually-remote
git pull upstream mainManaging notifications:
- Enable
send_if_no_matches: trueinitially to confirm it's running - Disable it once you trust the setup
- Use Discord for instant alerts, email for daily digest
Running costs:
- Gemini free tier: 1,500 requests/day — sufficient indefinitely
- GitHub Actions free tier: sufficient for daily runs
- Resend free tier: 100 emails/day — more than enough
- Total cost: $0/month
- Create free account at resend.com
- Create an API key
- Add
RESEND_API_KEY,EMAIL_FROM,EMAIL_TOto GitHub Secrets - Add
emailtonotification_channelsin config.yaml
- Open Discord → your server → Edit Channel → Integrations → Webhooks
- Create webhook, copy URL
- Add
DISCORD_WEBHOOK_URLto GitHub Secrets - Add
discordtonotification_channelsin config.yaml
- Search @BotFather in Telegram
- Send
/newbotand follow instructions - Send a message to your new bot
- Visit
https://api.telegram.org/bot<TOKEN>/getUpdates - Copy your chat ID from the response
- Add
TELEGRAM_BOT_TOKENandTELEGRAM_CHAT_IDto GitHub Secrets - Add
telegramtonotification_channelsin config.yaml
companies.csv is a community resource. You can:
- Edit directly in the GitHub browser, no terminal needed
- Submit a PR — the
validate_companiesworkflow runs--dry-runautomatically - See CONTRIBUTING.md for full guidelines
What qualifies: Companies with global/EMEA/rest of world remote roles and a direct careers page (no login required). No US-only companies, no generic job boards.
Actually Remote ships with a curated list of 100+ companies. But the discovery agent can find companies you haven't heard of yet.
Run it manually whenever you want to expand your list:
python discover.py
The agent searches Ashby, Greenhouse, and Lever job boards using your target_titles and location_keywords from config.yaml.
What it does:
- Runs up to 6 search queries combining your job titles and locations
- Uses Gemini with Google Search to find matching companies
- Filters out companies already in your
companies.csv - Validates each URL is accessible before suggesting it
- Sends results via your configured notification channels
- Saves results to
discovery_results.txt
💡 The agent typically takes 2–5 minutes to complete: it runs multiple web searches, validates each discovered URL, and sends notifications. If API rate limits are reached, errors will appear in the terminal and the affected queries will be skipped.
Output example:
🔍 Actually Remote — Discovery Run
Found 7 new companies to consider:
- Paddle — https://paddle.com/careers
- Wasabi Technologies — https://boards.greenhouse.io/wasabi
- Stripe — https://stripe.com/jobs
Important — free tier limits:
The discovery agent uses your Gemini API key. On the free tier:
- Gemini 2.5 Flash: 5 requests per minute
- Gemini 2.5 Flash Lite: 10 requests per minute
- Running 6 queries uses 6 requests (within limits)
- Avoid running it multiple times in quick succession
- Do not run it on the same day as heavy scraper testing
Adding discoveries to your list:
Review the suggestions and add the ones you want to companies.csv. Either edit directly in GitHub browser or locally. The discovery agent doe not modify your companies.csv automatically.
- Schedule: Daily at 5am UTC (6am CET)
- Manual trigger: Available from Actions tab
- Batch rotation: 1/7 of companies per day; each company checked once per week
- Priority companies: Checked every day
- seen_jobs.json: Auto-committed after each run (persists state)
- Failure notification: GitHub automatically emails you when the workflow fails (and you will get a Discord notification, if Discord is configured)
See CONTRIBUTING.md.
Three ways to contribute:
- Add companies to
companies.csv - Report broken URLs (use the Broken URL issue template)
- Code improvements: open an issue first to discuss
PRs that touch companies.csv are automatically validated with --dry-run.
- Core scraper with batch rotation
- AI CV matching (Gemini)
- Email digest (Resend)
- Discord notifications
- Telegram notifications
- GitHub Actions automation
- Community company list with PR validation
- Company discovery agent (search Ashby/Greenhouse/Lever)
- Playwright support for JS-heavy career pages
- Gmail SMTP support
- Slack notifications
companies.csv is a community resource. Contributions welcome, see CONTRIBUTING.md.
