Shlok Patel
← All posts
November 4, 20256 min read

GitHub Actions is the server I never had to pay for

MailSender pulls 500+ leads a day across four platforms and costs $0/month to run. Here's why a cron-triggered workflow beat every VPS I considered.

AutomationGitHub ActionsPythonInfra

When I built MailSender, the first architecture question wasn't which framework or which scraper library — it was: where does this thing actually live?

The obvious answer for a Python job that runs on a schedule is 'a small VPS with a cron entry.' I've set up dozens of those. But for a project that has to survive on zero budget and zero babysitting, the boring VPS answer is worse than it looks.

The hidden cost of a small server

A $6/month droplet sounds free. What's not free is: keeping the OS patched, remembering to rotate logs, hardening SSH, monitoring disk usage, restarting the cron daemon when systemd throws a fit, and being on the hook every time something silently stops running.

For a lead-scraper that runs four times a day, none of that work produces user value. It's pure operational tax.

Why Actions won

  • Cron is a first-class primitive. `schedule: cron: '0 */6 * * *'` and you're done — no daemon, no service file, no logrotate.
  • Secrets management is baked in. Gmail OAuth tokens, Google Sheets service accounts, and the Telegram bot key all live in encrypted repo secrets, not on some server's disk.
  • Every run is a fresh, disposable Ubuntu container. If Playwright leaves crumbs, they die with the runner. No 'why is /tmp full again' at 2am.
  • Logs are the run output. Zero effort. Grep-able from the web UI or `gh run view`.
  • Free tier covers ~2000 minutes/month of private-repo compute. MailSender uses maybe 40.

What it looks like

name: mailsender
on:
  schedule:
    - cron: '0 */6 * * *'
  workflow_dispatch:

jobs:
  scrape:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: '3.11' }
      - run: pip install -r requirements.txt
      - run: playwright install chromium
      - run: python -m mailsender.run
        env:
          SHEETS_SA_JSON: ${{ secrets.SHEETS_SA_JSON }}
          TELEGRAM_TOKEN:  ${{ secrets.TELEGRAM_TOKEN }}
          GMAIL_REFRESH:   ${{ secrets.GMAIL_REFRESH }}

Where it breaks down

Actions is not a good fit if you need low latency (cold start + browser install is ~30-45s) or if a single run needs more than 6 hours. It's also worth watching your minute usage the moment a job goes north of 10 minutes on a frequent schedule.

For a hobby-scale or founder-scale automation, though? The answer to 'do I need a server?' is more often than not: no, you need a workflow file.

Rule of thumb I use now: if a job runs on a schedule, terminates in under 20 minutes, and doesn't need a public inbound URL — start on GitHub Actions. Only reach for infrastructure when Actions actually stops fitting.