← Back to the log

Week 20: I Sent 4 Emails, Got 1 Bounce, and Kept 366 Follow-Ups Frozen

I sent four approved WIMPER emails. One address immediately rejected the message, and the system still showed a one-send mismatch between two of its records.

So I suppressed the bad address, repaired the inbox watcher, and kept 366 follow-up touches frozen.

What Got Built

  • I sent one exact four-recipient WIMPER broker batch. This was batch 004, not a broad restart. Each recipient had been approved. The system sent each email once through Zoho, recorded the provider result, and left the remaining three recipients under read-only reply monitoring.
  • I repaired the Zoho reply watcher. The watcher checks the inbox for responses. It now opens a fresh connection after a temporary socket failure, retries no more than three times, waits between attempts, and refuses to move its checkpoint unless the inbox read succeeds.
  • I changed broker outreach from automated writing to research-first calibration. The agent now prepares small, source-backed broker research cards. I write the first emails. Only after it can compare several approved examples will it extract a reusable writing pattern.
  • I published the July 29 build article with two authenticated dashboard screenshots. The system recorded three deployment receipts for mattragudo.com rather than treating a local file as proof that the article was live.
  • I drafted and deployed a 1,338-word Medicare article. Understand My Medicare content item 227 explains CMS ending the Part D Premium Stabilization Demonstration after 2026. It passed the Hugo site build before deployment.

Matt’s Build Timeline: 2026-07-30

What Broke (And How I Fixed It)

The first failure was visible immediately.

One of the four approved broker emails received a permanent rejection. This was not a temporary delay or a full mailbox. The receiving server rejected the address, so the system suppressed it instead of trying again.

That left three non-bounced recipients under observation. It did not create permission to replace the rejected address, enlarge the batch, or start automatic follow-up.

The second failure was more dangerous because it affected the feedback loop.

The Zoho reply watcher had failed three times with a socket EOF error. In plain English, the inbox connection ended unexpectedly while the watcher was reading it. A send system without a reliable reply watcher can keep pushing messages while missing the very response it is supposed to learn from.

The old watcher did not have a bounded recovery path for that failure. I repaired it so every retry uses a fresh inbox connection. It will make no more than three attempts, with a delay between them.

More important, it now fails closed.

A checkpoint tells the watcher how far it has already read. If a failed run advances that checkpoint, the next run can skip over a real reply. The repaired version only moves the checkpoint after a successful read.

I also added an alert threshold and cooldown. One temporary failure does not produce repeated noise. Three consecutive failed runs create an alert, and the same incident cannot alert again for four hours.

Then I tested the repair instead of calling the code change a fix. Three regression tests passed. A live run connected to Zoho, found nothing new, and exited silently. That quiet result was the expected proof.

The third break remains open.

My email_log table and event bus disagree by one send. The event bus is the system’s receipt stream: it records what agents and scripts say happened. The email log is the direct sending ledger. When those two records differ, I do not know which one should drive the next automated state change until the discrepancy is reconciled.

The pipeline also contains 39 contacted prospects whose last recorded outbound is more than 48 hours old with no inbound reply. There are 366 touches due and held.

Those are not reasons to let the scheduler catch up. They are reasons to keep the gate closed. No stages advanced automatically, and no held follow-ups were released.

The evidence collection had one smaller failure. GitHub’s commits API returned HTTP 401, which means the credential was rejected. I used verified local git history for the 21-commit count and named the fallback rather than estimating.

The Lesson

A watcher is part of the sending system.

Here is what I would tell someone automating outreach: do not judge reliability only by whether the email leaves the outbox. The loop is not complete until replies can be observed without skipping records. If the watcher is broken, the safe response is to stop or hold downstream action.

Bound retries, and prove recovery.

“Retry on error” is not a complete design. Set a maximum, create a fresh connection, wait between attempts, and preserve the last known-good checkpoint. Then run both failure tests and one live check so stale error state is replaced by evidence that the repaired path works.

Treat accounting drift as a gate condition.

A one-send mismatch sounds small next to hundreds of prospects. But automation compounds small errors. If one ledger says a person was contacted and another does not, an automatic follow-up can become a duplicate message or an incorrect stage change.

Let failed pilots change who does the work.

The earlier WIMPER pilot reached 0 verified replies from 20 sends. I did not ask the writing agent for another variation and continue at the same speed. The new process separates evidence gathering from voice learning: the agent researches, I write the first examples, and the system earns the right to generalize only after it can compare real approved work.

Work Log: 2026-07-30

The Numbers

  • Commits: 21 total (0 agent, 21 Matt)
  • Agent jobs run: 26
  • Prospects added: 0
  • Emails sent: 4
  • Immediate permanent rejections: 1
  • Recipients under reply monitoring: 3
  • Social posts: 0
  • Content publication and deployment receipts: 4
  • Held follow-up touches: 366
  • Stale contacted records needing reconciliation: 39
  • Reply-watcher regression tests passed: 3

The most important number is 366.

A busy automation would treat 366 due touches as a backlog to clear. I treat them as 366 chances to compound an unresolved recordkeeping error. Until the sending ledger, event receipts, stages, and reply observations agree, held is the correct state.

The one bounce matters for the same reason. The system did not convert a rejected address into more activity. It suppressed the address and kept the test bounded.

What’s Next

Reconcile the one-send ledger mismatch and the 39 stale contacted records before releasing any of the 366 held touches. Keep the three non-bounced batch 004 recipients under bounded reply observation.