autointern

x 0.95 · purpose 0.30 · t 1 d · scope 0.55

from 2026-09

Python · FastAPI · SQLAlchemy · SQLite · htmx · Gmail · Docker

Internship postings are spread across company job boards and aggregators, open and close without notice, and the same role shows up in several places. Autointern is a self-hosted tracker that reads one public aggregator feed and a watchlist of company boards through their public ATS APIs (Greenhouse, Lever, Ashby), keeps every posting it has seen in one deduplicated inventory, and ranks each against my profile with deterministic rules. It finds, tracks and records; applying stays with me, through the posting’s own link.

aggregator listings, 2026-10-12
17,005
of them active
4,654

one posting, however many sources

Postings from the aggregator and the ats board move along one stream. A posting the inventory has not seen turns off the stream and settles into it as a new job, a card with title, company, the source it came from, a first-seen mark; a posting it already holds continues past, marked linked (the same posting from another source, joined to its job) or seen again (only its last-seen time moves). A model of the ingest rules with stand-in labels: it shows no counts.

a posting becomes a job once, however many sources list it.

Each scan turns a source’s listings into observations keyed by the source’s own posting id, or a normalized URL when there is none. A posting the inventory hasn’t seen becomes a job. When a company’s board and the aggregator point at the same ATS posting, the second source joins the existing job instead of creating another; a posting seen again only moves its last-seen time. Weaker matches, the same company and title in overlapping locations, are proposed for review and never merged on their own. Nothing is ever deleted.

a posting’s life

One job's history, as a timeline: new job from its board, same posting on the feed, linked, scored against my profile, saved, absent from a complete scan, listed again, absent again, a second miss in a row: closed. Events from scans drop onto the job as they happen, my own decision is marked apart from them, and its availability moves between open, uncertain, closed. A model of the reconciliation rules, with no dates or counts.

data
eventfromwhat is recordedavailability after
foundats board scannew job from its boardopen
linkedaggregator scansame posting on the feed, linkedopen
rankedranking rulesscored against my profileopen
savedmesavedopen
missingats board scanabsent from a complete scanuncertain
recoveredats board scanlisted againopen
missingats board scanabsent againuncertain
closedats board scana second miss in a row: closedclosed
availability follows complete scans, never a single miss.

A job is open while any company board lists it. A complete scan that no longer sees it marks it missing and its availability uncertain; it closes only after two complete scans in a row without it. Failed, partial or rate-limited scans never count as a miss, and the aggregator alone never closes a job. A posting that comes back reuses its row and records the recovery. Every transition lands in an append-only event log, so a job’s history can be read back.

Every posting goes through the same checks in order: not closed, no disqualifier, internship, role family, eligible + fit, listed open. The first one it fails decides its bucket and the reason recorded with it; a posting that passes them all is recommended. Stand-in postings travel the line one after another. A model of the ranking rules: no counts, scores or thresholds.

data
check, in orderif it failsreason recorded
not closedlow priorityposting is closed
no disqualifierlow priorityineligible, with the line that says so
internshiplow priorityno internship or co-op evidence
role familyreview or low priorityno target role family
eligible + fitreview or low priorityeligibility uncertain, or fit below its bar
listed openreviewavailability uncertain
passes every checkrecommendedthe role family it matched
the first check a posting fails decides where it lands, and why.

Ranking is rules, not a model. Eligibility is evidence-based: missing information leaves a posting uncertain, and only a cited line, such as a required active clearance or a senior title, makes it ineligible. A role family from my search config sets its priority tier, and technical fit is a 0–100 score that saturates as my skill groups match the description, each match kept as evidence. The first check a posting fails decides its bucket and the reason shown with it. Ranking never hides a job; saving or dismissing one is my decision, and survives every rescore. A change to the rules or my profile changes a hash, and the worker rescores what it affects.

the inbox

Autointern also reads my Gmail inbox. Each message is matched against regular-expression rules and organized by what it matches, so the mail a search brings in stays in order instead of piling up.

how it runs

Two containers share one SQLite volume: the web app, server-rendered with htmx, and a single worker that holds a file lock. The worker runs one scan per tick, the aggregator every six hours and each board once a day with a little jitter, backing off after a failure, and writes a verified daily backup. Discovery only ever makes HTTPS GETs to the four providers’ public endpoints. The tests run offline, against recorded provider responses.