Rendered at build time from REVENUE_ATTRIBUTION.md in the repo — edit the source
file and rebuild to update this page.
Version 1.1.4 · 2026-09-06 · Status: Scoping — Phase 2's lead-push test shipped and verified live; Phase 0 (phone tracking) still open.
| Objective | Close the loop from a website visit or phone tap to a sold job in JobTread — track phone leads with the same confidence as form leads, get every lead (form or phone) into JobTread automatically tagged with source/city/service and a Lead ID, and layer in automated communication on both sides of the sale (status updates while a lead is being worked, newsletter/review/referral touches once it closes). Together these are the building blocks of a mature sales motion, not just a tracking fix — attribution tells you what happened, the communication layer is what actually improves the customer experience and the team's follow-up consistency. |
| Nature of work | Three builds, in dependency order: (1) a static tracked phone number with call recording/connect data, wired through Cloudflare Pages Functions the same way functions/api/contact.ts already handles OpenAI/Ads server-side calls; (2) a JobTread API integration (app.jobtread.com/docs) so form and phone leads land in the CRM without manual re-entry; (3) automated post-lead and post-sale communication sequences keyed off that same Lead ID / pipeline-stage data. (3) depends on (2) existing first — there's nothing to trigger a sequence off of until leads and stage changes are actually landing in JobTread automatically. Not a recurring content lever — one-time infra, then it runs itself. |
| Trigger | The 2026-08-24 "Where the Money Goes Dark" revenue-funnel review: phone is the larger channel (125 taps vs. 76 form submits) but the only signal past a tel: tap is that someone tapped it — no connect confirmation, duration, or recording. Separately, leads (form or phone) aren't confirmed to reliably reach JobTread at all — they can be worked entirely off email/voicemail and never become a CRM record. The same review also surfaced a third, undocumented gap: what happens to a lead's data and communication before and after the sale — automated status updates while a deal is open, and newsletter/review/referral activity once a project closes. All three block every question in the Business Questions register that depends on a closed lead→sale loop: B1, B3, B5, B6, B10, B15. |
| Progress | Funnel review presented at the 2026-08-26 owner meeting (see MEETING_NOTES.md). Vendor-vs-build for phone tracking was not decided — framed to the owner as "confirm the phone gap, then decide" rather than a forced choice that day. Owner did commit to a first move on the intake question: send form leads to JobTread via API as a test (open TODO in MEETING_NOTES.md). Also discussed: the current lead-handling chain is Site → Shelly → Adam → IBT — worth confirming this is accurate (and where, if anywhere, a lead can currently fall out of it) before the intake/CRM phases below are scoped further. |
| Next action | Still open: the Phase 0 vendor-vs-build decision (CallRail/WhatConverts ~$30–90/mo turnkey vs. Twilio build ~$5–10/mo usage-based). No phone-tracking code work starts before that call. Phase 2's JobTread lead-push test is shipped, live (JOBTREAD_LEAD_PUSH_ENABLED=true), and verified against a real submission — see Phase 3 below. Separately, a pre-existing gap surfaced during verification: LEAD_LOG KV diagnostic logging isn't writing despite a correctly-configured binding — tracked in MEETING_NOTES.md's Open TODO board for a future session, not blocking this initiative since the internal diagnostic emails cover the same information. |
This file is self-contained. Ledger entry: I14 in Search_Strategy.md.
Every target in Search_Strategy.md — rankings, clicks, form submits, calls tapped — is a proxy
for whether the work produces sold jobs. Nothing today actually traces a lead through to a dollar
in JobTread, which is why B1, B3, B5, B6, B10, and B15 in the
Business Questions register are all still
open. Two specific breaks in the chain:
tel: click event — proof someone
tapped the number, not proof the call connected, how long it ran, or who called. GA4 can't
originate this data on its own; it's an analytics layer, not a phone system. Phone is also the
larger channel (125 taps vs. 76 form submits per the Phase 2a baseline in Search_Strategy.md),
so this is the bigger of the two gaps, not the smaller one.contact.ts already mints) was designed to
answer this, but nothing confirms it's being kept up — and even when a lead is worked, there's
no guarantee it becomes a JobTread record rather than staying in email/voicemail. JobTread has a
documented API (app.jobtread.com/docs) that's never been used for this.A known, deliberate limitation carried into this initiative: a static (non-rotating) tracked number fixes whether a call happened — it does not fix which channel drove it. Dynamic number insertion would solve attribution fully but was already rejected elsewhere in this project for conflicting with I9 Tier 1 NAP consistency. A static number preserves NAP (it's the same number published everywhere, permanently) at the cost of channel-level attribution for calls that originate off-site (a citation, a truck, GBP) — self-reported "how'd you find us" at intake remains the only source of truth for those.
Confirmed pricing as of 2026-08-24: CallRail ~$50–55/mo entry tier, WhatConverts $30/mo (calls-only) to $60/mo (calls + forms unified). Both are turnkey — no engineering time, live dashboard, native form+call unification in WhatConverts' case.
The alternative: build the tracked number on Twilio, mirroring the server-side pattern already
proven in functions/api/contact.ts for OpenAI/Ads conversions. Twilio's usage-based pricing at
current call volume works out to roughly $5–10/mo (a local number ~$1.15/mo + inbound minutes
~$0.0085/min + recording ~$0.0025/min) — the tradeoff is engineering time up front and no vendor
dashboard, in exchange for a much lower recurring cost.
functions/api/twilio-voice.ts — answers the call, responds with TwiML dialing the real business
line with record="record-from-answer" and a recordingStatusCallback. Caller experience is
unchanged.functions/api/twilio-status.ts — receives call-outcome webhooks (duration, from/to, status).
Filter out sub-few-second calls (voicemail hang-ups, misdials) so they don't get logged as leads.functions/api/twilio-recording.ts (or folded into the status callback) — receives the recording
URL once processed.X-Twilio-Signature header against the auth token before trusting it —
same category of check as any inbound webhook.contact.ts, already shipping a Lead ID
per 2026-07-30), waitUntil() a POST to JobTread's API creating a lead record tagged with the
Lead ID, source, city, and service — same waitUntil() / non-blocking shape already used for the
OpenAI Ads call in contact.ts.functions/api/contact.ts (pushLeadToJobTread), gated
on JOBTREAD_GRANT_KEY/JOBTREAD_ORGANIZATION_ID (both now set as Cloudflare Pages secrets).
Confirmed against the live API: neither account nor contact has a built-in email/phone
field — those exist only as org-configured custom fields, and this org already has "Email" and
"Phone" custom fields on customerContact (IDs hardcoded as constants in the file — stable unless
those fields are deleted/recreated in JobTread's UI). Also confirmed: JobTread's own native
"Public Web Forms" / "Create New Customer Form" feature has no dedupe either — "New
customer/vendor accounts will automatically be created once filled out," per JobTread's own docs —
so embedding a JobTread-hosted form instead of pushing via API would have bought nothing on data
quality, only cost the branded contact-form UX and required a webhook receiver to reattach the
Resend/ad-conversion side effects. That option was considered and dropped for this reason.
The shipped dedupe: look up an existing contact by matching either the Email or Phone custom
field (verified live against real org data before merging), and attach a comment to that
contact's account if found; otherwise create a new account + contact (with those same custom
fields set, so future leads can dedupe against it too) before commenting. Phone is normalized to
E.164 (US only) before matching, since JobTread stores it that way. All lead fields
(name/email/phone/city/service/budget/message/Lead ID) land as a comment either way — no custom
fields for Lead ID/source/city/service yet, that's a fast follow if wanted later. Diagnostics
reuse the same KV + internal-email pattern as the Google Ads upload
(logIntegrationResult/sendDiagnosticEmail), though the KV half of that isn't actually landing
(see Phase 3). Verified live 2026-09-06 with a real test submission: matched an existing
account by email rather than creating a duplicate, correctly posted the lead comment, and the
internal diagnostic email confirmed success (Phase 0 for phone tracking is unrelated and still
separately open).LEAD_LOG KV write in
logIntegrationResult isn't landing — confirmed the KV binding is correctly configured in the
Cloudflare Pages project config, but no key ever appears in the namespace even after a
successful push (the internal diagnostic email in the same Promise.all fires fine, isolating
the failure to the KV write specifically). Doesn't block this initiative since the email
diagnostic carries the same information, but affects the Google Ads diagnostics too. Tracked in
MEETING_NOTES.md's Open TODO board for a future session.Depends on Phase 2 shipping first — there's no pipeline-stage data to trigger a sequence off of until leads are landing in JobTread automatically. Two sequences, same underlying mechanism (a JobTread stage-change or field-update triggers an outbound message):
Content and cadence for both sequences (what the messages actually say, how many, how far apart) is a business-owner call, not an engineering one — scope that decision separately once Phase 2 is live and this phase is actually being picked up.
quoted/sold/contract_$ fields JobTread doesn't
natively expose the same way. Revisit once Phase 2 is live.tel: link, correlate to a Twilio call landing in the same
window) to recover source data for web-originated calls specifically. Doesn't help
off-site-originated calls (GBP, citations, signage) — those still depend on intake asking. Scope
as a Phase 1.5 addition if Phase 1 ships and the gap turns out to matter in practice.Surveyed 2026-09-05 against the Phase 4 post-sale review ask. Directional pricing/features from public docs and marketing pages, not verified via vendor sales calls — re-check before any purchase decision.
| Platform | Core service | Strengths | Weaknesses | Pricing model | Fit for Honey-Doers |
|---|---|---|---|---|---|
| JobTread Workflows | No-code trigger→action automation already inside the CRM we plan to run leads through (I14 Phase 2) | Zero extra tool/login; fires directly off the same "Closed" status that already carries Lead ID/source/city/service; bundles with survey + billing + doc tasks in one workflow | Review-gen is a side feature of a PM tool, not its focus — no listings sync, no review monitoring/response aggregation, no competitor benchmarking; unconfirmed whether it's Google-specific or multi-platform | Included in JobTread's ~$20/mo-per-user plan (unconfirmed whether workflows are plan-gated) | Best fit for the core "ask" once Phase 2 ships — no added cost, no added system, and it's already keyed to the same Lead ID/source data the rest of I14 depends on |
| NiceJob | Standalone review generation + basic reputation widget, auto social posts from reviews | Cheap end of category, easy embeddable widgets, simple SMS/email review requests | Thin CRM/reporting; would duplicate what JobTread's Workflows already do for the ask itself | ~$75–175/mo, annual contract | Redundant with JobTread once Phase 4 ships, unless we want its widget/social layer specifically |
| Podium | Review management + unified inbox (SMS/webchat/Google/FB), payments, light CRM | Strong on lead-intake conversations (missed-call text-back, webchat) — overlaps I14's phone-tracking/CRM scoping, not just reviews | Pricier; contract-locked; review-gen is good but not best-in-class standalone; largely duplicates JobTread for the ask | Custom quoted, ~$300–500+/mo, annual contract | Only worth it if we want to consolidate lead-intake messaging (not just reviews) into one tool — evaluate against I14's phone-tracking decision, not as a review tool alone |
| Birdeye | Full reputation suite — reviews, listings/local SEO, competitor benchmarking, surveys, referrals | Most full-featured; strong competitor comparison dashboards; multi-location support | Most expensive; aggressive sales/upsell; overkill for a single-market home-services business | Custom quoted, ~$300–1000+/mo, annual contract | Not a fit at our size/footprint; competitor-benchmarking angle is the only thing not replicated elsewhere |
| GatherUp | Review generation, widgets, listing sync | Lighter/cheaper than Birdeye/Podium; decent SEO schema output | Smaller company/ecosystem; largely duplicates JobTread for the ask | ~$100–300/mo tiered | Redundant with JobTread; only relevant if listings-sync becomes its own need |
| Broadly | Review generation + basic webchat, built for trades (ServiceTitan/Jobber integrations) | Vertical fit for home services; simple onboarding | No confirmed JobTread integration; largely duplicates JobTread for the ask; mixed reviews on support | ~$300–400/mo, contract required | Redundant with JobTread unless its webchat/lead-capture layer is independently wanted |
| Manual (staff sends the ask by hand — text/email/call) | No tooling — a person remembers to ask after each closed job | Zero cost, no vendor risk, personal touch | Depends entirely on staff remembering, at the exact moment I14 is trying to fix ("no documented communication layer" was the original problem); no Lead ID/source tie-back; no consistency or reporting | $0 | The status quo I14 was scoped to replace — not a real configuration, listed as the baseline |
Three ways to resolve Phase 4's post-sale review ask, ranked by recommendation:
JOBTREAD_LEAD_PUSH_ENABLED=true and verified Phase 2's form-lead
push against a real submission: matched an existing customer account by email instead of
creating a duplicate, posted the lead comment correctly, diagnostic email confirmed success.
Closed the corresponding Open TODO board item in MEETING_NOTES.md. Surfaced a separate,
pre-existing gap in the process — LEAD_LOG KV diagnostic writes aren't landing despite a
correctly-configured binding — tracked there for a future session rather than blocking this one.pushLeadToJobTread
in functions/api/contact.ts). Ruled out embedding JobTread's own hosted form (Option 1): its
native form has no dedupe either. Confirmed live that account/contact have no built-in
email/phone fields — dedupes via the org's existing Email/Phone custom fields on
customerContact instead, verified against real org data via a temporary, token-gated,
read-only debug proxy (deployed, tested, and removed same session). See Phase 2 above.MEETING_NOTES.md); phone-tracking
vendor-vs-build left open rather than decided that day. Owner greenlit the JobTread-API test-push
as a first, independent step on the intake question.