Follow-ups
The reminder that would not stay deleted
Aaron at Job Umbrella · October 9, 2026 · 6 min read
Job Umbrella writes follow-up reminders for you. You applied twelve days ago and nobody has answered, so a task appears on your Board with a short note you can send as it stands. Day 7, day 14, day 21. After an interview, a thank-you note on day 1 and a nudge on day 5 and day 10.
Last week those reminders were written once a week. This week they are written once a day, for a reason that sounds small and is not: a thank-you note dated day 1 is worth sending on day 1, and a weekly pass could only write it on the following Monday. If you interviewed on a Tuesday, the note arrived six days late, which is to say it arrived useless.
Changing the schedule took one commit. It also exposed a bug that the weekly schedule had been hiding for as long as the feature has existed.
Delete it and it comes back
To avoid writing the same reminder twice, the code asked a reasonable-sounding question: does a task for this step already exist on this job? If yes, skip it.
The trouble is that a task is yours. You can tick it off, you can push its date out a week, and you can delete it because you have decided this application is not worth another email. All three of those change the answer to that question. Delete the day-7 reminder and the code could no longer see that it had ever been written — so it wrote it again.
On a weekly schedule you would rarely notice. By the time the next pass ran, seven days later, the job usually owed a later step instead, so the deleted one quietly stayed gone. On a daily schedule it came back the next morning. And the morning after that.
The code was asking what was on your Board. The question it needed to ask was what had already been sent. Those are not the same thing, and only one of them is a fact about the past.
So there is now a separate record of which reminders have been written, which nothing in the product can edit and nothing deletes except the application going away. Reminders are written against that. Tick one off, move it, delete it — none of it changes what was already sent, because none of it should.
The fix needed fixing
That record needed a database change, which is the kind of work that is hard to undo, so it went through the review this project uses for anything schema-shaped: three independent reviewers, each told to break it rather than approve it.
They found five serious faults in my implementation. I want to write down the one I am least proud of, because it is a good lesson about what a record of the past has to record.
Each written reminder was filed under its day number — day 7, day 14, day 21. That seems obviously right until you remember that you can correct the date you applied. Suppose you typed August when you meant October and then fixed it. The old entries still say days 7, 14 and 21 are done, so under your corrected date you would never be reminded again. No error, no warning, a feature that silently stops working for that one job.
The entry now records the date the schedule hangs off, not just the day number, so correcting an applied date restarts the schedule the way you would expect. Two reviewers found that same hole from different directions, and one of them found that I had fixed it in the code and left it sitting in the database migration — the same mistake, twice, in two languages.
A fourth reviewer went at it as a deployment rather than as code and found that the change, as written, would have briefly blocked people from saving a job while it ran. That one was my framing error: I had asked whether it could delay a release, and the honest answer was that it could stall writes on the live site. It ships as two separate steps now, and it took 95 milliseconds.
One other thing worth saying plainly
If you have ever pushed a follow-up reminder’s date out, you may see that one reminder appear once more. It will not come back after that.
The reason is honest and a bit unsatisfying: for a reminder whose date you moved, there is no longer any way to prove which step it was. The code can either guess, and risk silently cancelling a reminder you should get, or admit it does not know and write it once. I chose the duplicate. One reminder you did not need is a small annoyance. A reminder you needed and never got is the thing this feature exists to prevent.
And the thing that was on last week’s open list
Last week’s post ended with “PDF exports still render on the request thread.” That is closed. Laying out a PDF is slow and, worse, uneven: most documents take a fraction of a second, and a few shapes take seconds. While that happened on the thread that serves pages, one person’s awkward resume was everybody else’s page load.
PDFs are now laid out on a separate thread with a deadline. If a document takes too long, that thread is stopped and you get told the document is too complex rather than everyone waiting. The slowest document the app will accept takes about a second; a real resume takes about a third of that.
Release notes
October 8, 202651 commits · 1 migration · main 304e42d (four deploys, the migration alone first)Shipped — a new look
- The Slicker brand across the site and the browser extension: Storm, Slicker and Mist in place of the old blue, and the J-Hook wordmark.
- Two typefaces built for reading, served from this site rather than fetched from anybody else.
- A landing page rewritten in plainer English, and sentence case across the marketing pages.
- The marketing header no longer scrolls sideways on a phone.
Shipped — two tools that need no account
- A ghost-job calculator at /ghost-job-calculator: what a job search costs you in hours when roughly one job in seven was never really open.
- A guide at /guides/ghost-jobs on spotting a listing that is already gone.
Shipped — the app
- Follow-up reminders are written daily, so a day-1 thank-you note arrives on day 1 instead of the following Monday.
- A reminder you delete, complete or reschedule is not written again.
- PDF exports are laid out off the request thread, with a deadline, so one slow document no longer slows down the site.
- The job form and the resume editor’s buttons wrap properly on a phone.
Shipped — being found
- One address per site, so search engines no longer see the same pages twice under a second hostname.
- The test site is kept out of search results in a way crawlers can actually read, which the previous arrangement defeated.
Found by review before release, and fixed
- A resume shaped to be expensive could hold the server for seconds at a time. It is bounded now, and refused before it starts.
- A PDF export could ask the server for far more memory than the document needed.
- The free resume review’s daily allowance is now more generous on a shared connection: one office or VPN no longer uses up everyone’s.
- Signing in from a busy office or VPN no longer told later arrivals their password was wrong.
- A build that was not told which site it was serving assumed it was production. It now assumes it is not.
- Patched the web framework for two advisories affecting sites that host it themselves, as this one does.
Still open
- A reminder whose date you moved may appear once more, for the reason given above.
- Advertising is still not running, and the browser extension update is still with the two stores for review.
- The email library has an outstanding advisory. It is not reachable from anything this site does, and the fix is a major version that changes how a failed send is classified, so it waits for a release of its own.
- Submitting changed pages to search engines is still run by hand after a release, deliberately.
Everything here is free to use
AI resumes and cover letters, a fit check against any posting, search across 12 job boards plus government sites, and a Board that tracks every application. No employer can pay to reach the top of your list.