Skip to content
Abdullah.
All work

Case study · December 2025

Cover Me — screening the job before writing the letter

Most cover letter generators write you a letter for any job. This one scores the posting against a preferences file first, tells you what is wrong with it, and only then drafts a letter in a style specified by explicit counter-examples.

Role
Solo — design and build
Timeframe
December 2025
Access
Public deployment and source
Cover Me Pls job-details form: fields for job title, company, company homepage, optional employer questions, and a large job description textarea, on a dark violet-accented interface.

At a glance

Problem

Tailoring a cover letter per application is slow, and the tools that automate it produce writing that reads like it was automated. They also skip the more valuable question — whether the job is worth applying to at all.

Approach

Make the preferences the input, not an afterthought: salary floor, preferred and avoided technologies, and outright red flags live in a config file. A single pass returns a structured match analysis alongside the letter, so the first thing on screen is an assessment rather than a draft.

Outcome

Deployed and used for real applications. Returns a match score with itemised positives and concerns, a drafted letter, answers to any employer questions pasted in, and a PDF export.

Stack

  • Next.js 15 (App Router)
  • React 19
  • TypeScript
  • OpenAI API
  • Zustand
  • Tailwind CSS
  • Framer Motion
  • jsPDF
  • Vercel

Decisions

3 choices where the alternative was reasonable. The rejected option is the interesting half.

  1. Rejected
    Generating a letter for whatever you paste in
    Chosen
    Score the posting first, then write
    Because
    The expensive mistake in a job search is applying to the wrong roles, not writing the letter slowly. Returning positives and concerns alongside the draft means the tool sometimes tells you not to bother, which is the output with the most value in it.
  2. Rejected
    Describing the desired tone in the prompt
    Chosen
    A written style specification with explicit counter-examples
    Because
    "Professional but friendly" produces the same generic letter every model produces. Naming the failure modes — no hedging, no corporate filler, no em dashes, British English — and pairing verbose sentences with their rewrites does far more work than positive instruction alone.
  3. Rejected
    Hard-coding preferences into the prompt
    Chosen
    A preferences.json file interpolated at request time
    Because
    Preferences change more often than prompts do. Keeping them as data means updating a salary floor or adding a red flag is a config edit, not a code change.

The problem

Two problems, and most tools only notice the second one.

The first is that job hunting rewards applying to fewer, better-matched roles, but nothing in the process helps you decide which ones those are. You read a posting, form a vague impression, and either apply or don't.

The second is that tailoring a cover letter per company is slow. The tools that automate it write letters that read exactly like tools wrote them — "I am writing to express my sincere interest in this position at your esteemed organisation" — which is worse than a short honest letter, because a hiring manager has read that opening a hundred times this month.

How it works

You paste a job description, optionally with a title, company, homepage and any employer questions. One pass returns:

  • a match score out of 100
  • positives — what's actually good about this role
  • concerns — what's problematic
  • a drafted cover letter
  • answers to the employer questions, if you pasted any
  • a PDF export

The match analysis comes back as a typed MatchAnalysis object rendered as structure, not prose. The letter is the last thing on the page, not the first, which is the whole editorial point: the tool's first job is to tell you whether to bother.

Preferences are input, not context

The scoring is only meaningful because there's something to score against. A preferences.json file holds a salary floor and currency, preferred technologies, technologies to avoid, and outright red flags. It's interpolated into the request at call time.

Keeping it as data rather than baking it into the prompt matters more than it sounds. Preferences change constantly during a job search — you raise your floor, you decide you're done with a framework, you add a red flag after a bad interview. All of those should be a config edit, not a code change and a redeploy.

It also means the model is doing comparison rather than judgement. "Does this posting satisfy these stated constraints" is a much better-specified task than "is this a good job", and the concerns it returns are things you can point at.

Style as a specification

The interesting engineering here isn't the API call. It's that the prompt carries a written style specification, and most of that specification is negative.

Telling a model to write "professionally but with personality" produces the median letter, every time, because that instruction describes the thing it already does by default. What actually moves the output is naming the failure modes explicitly:

  • no hedging — no "perhaps", no "it might be argued that"
  • no corporate filler or buzzwords
  • no em dashes; restructure the sentence instead
  • contractions are fine, they read as human
  • British English
  • no exclamation marks, no emoji
  • lead with the point, then the supporting detail

And then pairing verbose sentences with their rewrites, so the model has a worked example of the transformation rather than an adjective:

Verbose: "I am writing to express my sincere interest in the position of Software Developer at your esteemed organisation."

Rewritten: "I'm interested in the Software Developer role at [Company]."

Counter-examples do more work than instructions. A model given "be concise" stays verbose; a model shown a specific verbose sentence next to its shorter form stops producing that sentence.

Where the honesty line is

The match score is a language model's judgement against a list of stated preferences. It is not a calibrated metric and it isn't reproducible run to run. It's useful as a prompt to think — a 45 makes you read the concerns properly — and it would be dishonest to present it as anything more precise than that.

The whole thing is also a single pass over one model call. That's a deliberate v1: a multi-stage pipeline with separate scoring and drafting steps would be more controllable and would let each stage be evaluated on its own, and it's the obvious next version if I keep using it.