Skip to main content

I had six years of job search data and never looked at it

I wanted one number: how many jobs have I actually applied to? Not a feeling, the number.

It turned out I'd been collecting it since March 2020 without noticing. Three Gmail labels — Applied, Denied, Interview — sitting on 1,726 messages. Every application I have ever sent left a receipt, and every receipt was already filed. The dataset was complete, structured enough to be useful, and I had never once opened it.

The result is live at Job Search, Measured: 1,125 applications across 852 companies, filterable by year. The headline is that the single most common outcome, by a very wide margin, is nothing at all — 60.7% of applications were never answered by anyone or anything.

Three labels were the entire dataset

There was no scraping and no API integration to build. The labels did the schema design years ago, and the ATS confirmation emails are unintentionally excellent structured data — they nearly all name the company and the role in the subject or first line, because they're generated from a template with those fields interpolated in.

That's the part worth generalising. I didn't have a data collection problem. I had a never bothered to look problem, and those are very different in cost.

What it actually cost, since that's the part people leave out

I want to be precise here because I've seen this kind of project written up as an afternoon's work.

The analysis was fast. Deciding what a record looks like, writing the transform, building the dashboard — a couple of hours, most of it on the chart rather than the data.

The extraction was not fast. It took 31 paginated passes over those 1,726 messages. Claude's Gmail connector is a remote tool, which means there is no scripting it: every page of results comes back through the model, and the model reads each email and decides what company it's from, what the role was, and whether it's a confirmation, a rejection, an interview invitation, or noise. You cannot regex your way out of this. "Thank you for your interest in [company]", "Important information about your application", and "Follow up from [company]" are all rejections, and nothing but reading them tells you that. (Real subject lines, company redacted — same reason the dataset is.)

So: the data was free, and the extraction was the whole job. That's the honest shape of it.

The good news is that this cost is paid once. A refresh only pulls mail newer than the last record, which is one pass instead of thirty-one.

Prerequisites

One thing, and it's a settings toggle rather than a build step: connect Gmail as a connector in your Claude settings. That's it — no API keys, no OAuth app, no local credentials.

Two gotchas that cost me time:

  • Only one Google account can be connected at a time, and it is not necessarily the account you log in to Claude with. Mine wasn't. Verify which mailbox you're actually reading before you trust anything.
  • The connector is read-oriented for this purpose but it can modify mail. I never gave it a reason to.

Roughly the prompt

This wasn't one prompt, it was a conversation, and pretending otherwise would be the same dishonesty as "took ten minutes." But if I were starting again knowing what I know, this is the prompt I'd send:

I have three Gmail labels — Applied, Denied, and Interview — covering my job search. Page through every message under them. For each message, extract the company, the role, the date, the sending domain, and the event type: application confirmation, rejection, interview, assessment, or other.

Write the records out to disk as you go, one file per page of results, so nothing is lost if this gets interrupted. Dedupe on the Gmail message id.

Then build a transform that groups those messages into distinct applications, works out how far each one got, and computes response times. Produce two outputs: one with the real company names for me, and one anonymised that I can publish.

The instruction that mattered most is the boring one: write to disk as you go. A long extraction that only exists in the conversation is one interruption away from being repeated.

Decide the record shape before you start, because you only get one cheap pass

I got this right by accident. Before extracting anything I sampled a page of results, looked at what was actually in the emails, and fixed the record schema — message id, date, company, role, event type, sender domain.

Had I decided halfway through that I also wanted, say, the job requisition number, the choice would have been re-reading 1,700 emails or having the field on only the recent half. Every field you don't think of up front is either a second full pass or a hole in the data.

The trap I didn't see coming

Anonymising the company names is the obvious step, and it is not sufficient.

Job titles leak employers constantly. "Senior Software Engineer, [Company] Bank." "Senior SWE, AI Backend: Observe by [Company]." A named-vendor role listed through a staffing agency. I had a clean pseudonymised dataset where roughly a dozen employers were still trivially identifiable from a field I hadn't thought of as identifying.

So the scrubber strips brand tokens out of the role titles too, against the full list of companies in the dataset, while protecting ordinary role vocabulary so the titles don't dissolve into nothing. Then I checked: zero company names survive in published role text.

The general version — the free-text fields leak, not just the name field — is the thing I'd want someone else to take from this.

A measurement problem I couldn't fully solve

Response time can only be computed where I have both a confirmation email and a later reply. Plenty of companies never send a confirmation at all — the first and only thing you hear is the rejection. Those applications have a real response time that is invisible to me.

The first version of the transform quietly scored all of them as zero days, because the rejection was the first record, so it looked like the response arrived the instant I applied. The median came out at 1.9 days and I nearly believed it. The real figure, restricted to applications where a confirmation exists, is 5.0 days.

That's a sampling bias I can name but not remove, so the page states it rather than hiding it. Same with silence: some rejections surely never got labelled, so 60.7% "no response" is an upper bound rather than a measurement.

What I'd look at next

The year filter turned up the thing I didn't expect. 2021 was 403 applications and 13 interviews. 2026 so far is 380 applications and 18 interviews — more interviews from fewer applications, at a lower reply rate overall.

I don't know what that is yet. It could be a better résumé, or better targeting, or a different market, or that the 2021 run was a shotgun and this one isn't. The data I have can't separate those, because it records what I sent and what came back, not how much thought went into either.

Which is the limit of the whole exercise, really: it measures the correspondence, not the effort. A one-click application and a four-hour take-home look exactly the same here. If I want the next layer, I have to start recording something the inbox was never going to capture for me.

Comments

No comments yet. Be the first!