Today I gave the worker that runs my daily reach ritual two things it did not have: an inbox, and a way to reply. It runs unattended on a Mac mini, and until this morning the only channels it could write to were LinkedIn comments, Threads replies and the post queue. A recruiter who emailed me got nothing until I opened Gmail myself.
The design I settled on is small. Five email tools on the same MCP server the worker already uses: send_email, draft_email, list_inbox, read_email, mark_email_handled. The sender is Gmail SMTP through nodemailer, with the resume attached by URL from my own site so there is one copy of it. The reader is imapflow, one locked session per call, because this runs inside a serverless function and a kept-open IMAP connection is a leak nobody sees. Seven blog tools ride along too, proxied from amargupta.tech, which is how this post exists.
The part I argued with myself about
A machine that can send email on my behalf is a machine that can send the wrong email, twice, at bedtime. So the rules are not in the prompt. They are in the service, and they refuse before any transport is even constructed:
- one mail per address per fourteen days
- five sends a day
- nothing between 22:00 and 08:00 IST
Every refusal writes a row with status held and the rule in its error column. The tool hands that back as an error naming the row id. I wrote the unit tests for each boundary first: 07:59 refused, 08:00 allowed, 21:59 allowed, 22:00 refused, the fifth send allowed and the sixth held, thirteen days refused and fifteen allowed. They went red, then green, and I moved on.
Then the e2e ran
The end-to-end test mints a short-lived API key, drives the real server with a real database, drafts an email, sends the draft in dry-run mode, resends it on the same idempotency key to prove the replay, and then sends a second mail to the same address to prove the fourteen-day rule reads the row it just wrote.
The first time it actually ran, and not merely reported green because it had been skipped, it failed on the send:
send_email: held (row fc1dc825-…) — outside the sending window 08:00–22:00 IST (it is 02:24 IST)
I had been working since before midnight. The guard looked at the clock, said no, and left a receipt. It was doing precisely what I had built it to do, against a production database, on the first night it existed. The test was wrong, not the guard.
What I did with it
There were two tempting fixes and I did neither. One was to let the test set a fake clock through an environment variable, which is a bypass in production code with a test-shaped excuse. The other was to skip the test outside working hours, which is how I had already been fooled once that day: a skipped test reports green, and a suite that is green because nothing ran proves nothing.
Instead the e2e now has two halves. Inside the window it runs the full path: draft, send, replay, held repeat. Outside the window it proves the refusal: a draft is written, sending it is held, the error names the window, and the draft row is untouched. Each half skips the other and prints the IST clock in its skip message, so a run in which both stepped aside cannot happen. At 02:24 the refusal half ran for real and passed. The send half will run in daylight.
The two things I wrote down
The first went into the worker's own instructions: a held email is a refusal with a receipt. Log it, move on, never retry it in the same phase. The cap in the prompt and the cap in the service are the same number on purpose, so a refusal is expected rather than debugged.
The second is about where rules live. A prompt is advice. A guard is a fact. A retried phase, a second session, a tool call typed by hand at two in the morning: all of them hit the same wall and get the same sentence back. That is the property I wanted, and the failing test is the only reason I know I have it.
