Field notes · AI security
← WritingThe lead nobody books gets a drip, not a dead end
Grey Ridge Signals Group · August 2026
The lead nobody books gets a drip, not a dead end
Grey Ridge Signals Group · August 2026
The booking guardrail means no calendar slot is ever created by accident — a booking requires both an armed environment and a lead that hasn't booked recently. But a guardrail only answers half the question. The other half is what happens to the lead on the other side of the skip: the qualified lead whose booking was refused, and the lead that never qualified in the first place. That second group used to fall into a dead end — classified, logged to nothing, and never spoken to again. The nurture drip replaced the dead end with a measured, timed follow-up loop.
1. The dead end that used to be there
Before the nurture path, the workflow's Is QUALIFIED? false branch routed every unqualified lead into a single empty no-op node named Log NURTURE/SPAM. It wrote no row anywhere. NURTURE leads and SPAM leads — two completely different outcomes for the business — were treated identically: dropped silently at the end of the run.
The gap was invisible in the happy path. The qualified branch had just gained the booking guard, so it was well-tested. The unqualified branch had no test coverage and no observable side effect, which is exactly why the dead end survived as long as it did. The first fix was a classification split: an Is NURTURE? IF node that separates NURTURE from SPAM before either branch does anything else.
2. The drip: log, wait, send, mark
The nurture branch is four nodes: Log Nurture Row → Wait for Follow-up → Nurture Follow-up Email → Respond. SPAM still terminates at Respond with no row, no wait, and no outbound email — SPAM is the one thing the pipeline is allowed to ignore.
Log Nurture Row inserts into a dedicated nurture_actions table carrying the full lead profile — name, email, company, budget, score, classification, reason, received_at — plus a computed follow_up_at set to exactly 48 hours after receipt. The wait node sleeps until that timestamp; its duration is configurable via a NURTURE_WAIT_HOURS environment variable, defaulting to 48 hours when unset. That override is what makes the loop testable: probes can compress the wait to seconds instead of waiting two days.
After the wait fires, Nurture Follow-up Email sends one Resend message to the prospect: subject "Re: your inquiry about AI security", body "We saw your inquiry about AI security testing for {company} and wanted to follow up in case you had questions about how we work." One touch, timed, and — the key addition — counted.
3. The feedback loop that makes the drip measurable
A drip that doesn't report back to the metrics is just another dead end with a delay. The post-send feedback loop closes that: a Mark Follow-up Sent postgres update runs after the email node, flipping follow_up_sent to true and stamping follow_up_sent_at on the row. The monthly metrics gained a Nurture Sent column backed by a query that counts rows where follow_up_sent is true, bucketed by follow_up_sent_at — so drip activity appears in the numbers without any manual backfill.
Two implementation gotchas surfaced while building the update node, both worth recording:
- The postgres v2 update node matches rows with
columnToMatchOn+valueToMatchOn, not theupdateKeyfield older docs suggest. - The HTTP Request v4 node replaces the incoming item with the response body — so
$json.emailis gone after the send completes. The update node therefore matches on the email from theWait for Follow-upnode's output ($('Wait for Follow-up').item.json.email) instead of trying to read it from the email node's result.
4. Test coverage and live probes
The nurture path is covered by eval/test-nurture-path.py, which grew from 33 to 42 checks across the two feature commits; the generators suite holds at 51 checks and the monthly-metrics suite at 24. The checks cover the classification split, the post-send update, and the env-overridable wait.
Live probes on the n8n 2.25.7 stack confirmed behavior end-to-end:
- 2026-08-11: a NURTURE probe produced a
nurture_actionsrow withfollow_up_atset to exactly 48 hours after receipt (2026-08-13 10:41:34 for a probe received 2026-08-11 10:41:34), with the execution paused at the wait node. A SPAM probe completed successfully with no nurture row, no wait, and no email. - 2026-08-12: with
NURTURE_WAIT_HOURS=0.002(about 7.2 seconds), the wait fired, Resend returned 200, and the row flipped tofollow_up_sent=twithfollow_up_sent_atstamped at 2026-08-12 05:46:27. The acceptance query returned 1, and the synthetic probe rows were removed afterward.
One probe constraint worth noting: Resend rejects example.com recipients with a 422, so probes use Resend's official test mailbox delivered@resend.dev instead of fake addresses.
5. What the metrics say — and don't say yet
The August 2026 metrics table now reads: Month | Leads (total) | Leads (QUALIFIED) | Bookings | Nurture Sent — currently 19 total, 8 qualified, 0 bookings, 0 nurture sent. The leads counts exclude test rows by rule (classification='TEST', e2e- prefixed emails, and %test@example.com addresses), so the numbers reflect real prospects only.
Honest limitations: the drip is a single touch, not a multi-step campaign — one follow-up email at 48 hours, then the lead is back in the bucket. It converts a silent dead end into a measured interaction, but it does not yet prove conversion: bookings are still 0 for the month, and the follow-up has not produced a measurable booking yet. The loop's current value is epistemic — the pipeline now knows when a nurture email went out and can start asking whether follow-ups move qualified interest into booked calls. That question is now answerable with data instead of guesswork.
Sources
[1] https://github.com/rezearcher/greyridge-consulting/commit/855167b44e5d41ff635d98a59a090ff4050089f4 — feat: nurture follow-up path for NURTURE leads (t_239d97e7)
"Replace the dead-end 'Log NURTURE/SPAM' noOp with a nurture branch: Is NURTURE? -> Log Nurture Row (postgres insert, nurture_actions) -> Wait for Follow-up (48h) -> Nurture Follow-up Email (Resend drip to prospect) -> Respond. SPAM terminates at Respond without outbound email." "Verified live on the n8n 2.25.7 stack: NURTURE probe -> nurture_actions row with follow_up_at = +48h, execution paused at wait node; SPAM probe -> success execution, no nurture row, no email. TDD: eval/test-nurture-path.py 33/33 (RED -> GREEN), eval/test-generators.py 41/41 no regression."
[2] https://github.com/rezearcher/greyridge-consulting/commit/4c22096f32bba40307ea8737704c01cea037115b — feat(nurture): post-send feedback loop + drip metrics (t_689124c7)
"'Mark Follow-up Sent' postgres update after the nurture follow-up email flips nurture_actions.follow_up_sent=true + follow_up_sent_at (columnToMatchOn email, matches on Wait node output since httpRequest v4 replaces the item with the Resend response)" "Wait node amount env-overridable via NURTURE_WAIT_HOURS, 48h default unchanged; compose passthrough + env.example doc" "tests: eval/test-nurture-path.py 42/42, test_monthly_metrics.py 24/24, test-generators.py 51/51; live probe flipped follow_up_sent (exec 34 success), SPAM untouched, acceptance check returned 1, synthetic rows removed"
[3] https://github.com/rezearcher/greyridge-consulting/commit/61802f460110a0df22fcc3e7c4a91503b870341c — sre: rescue t_239d97e7 deliverable stranded in unmerged worktree (nurture follow-up path in n8n-lead-workflow.json + test-nurture-path.py)
"sre: rescue t_239d97e7 deliverable stranded in unmerged worktree (nurture follow-up path in n8n-lead-workflow.json + test-nurture-path.py)"