Every touchpoint in the cold-pro recruitment journey, from Google Places lookup to first paid job. Each card includes the goal, what ships today, the copy, the UI, the rationale, and the open risk.
Live
12
touchpoints shipped on prod
Scaffolded
1
code live, needs config flip
Pending direction
2
needs brand voice review
01
SystemAutoLive
Sourcing — Agent A (The Miner)
Goal: Find local licensed pros for the vertical + ZIP
What happens
Searches Google Places for trade-relevant businesses near the customer, scrapes their website for a contact email, writes candidates into `installers` with vetting data (rating, business age, recommended outreach channel).
Why this way
Cold targeting from a directory is the cheapest acquisition channel — no per-lead cost.
Vetting data lets us pick SMS vs email per installer instead of carpet-bombing both.
Metric to watch
Targets/hour; % with phone+email vs phone-only.
Visual mock
Data flow
Google Places APIBusinesses near ZIP
Agent A — The MinerScrapes site, finds email
installers tablerating · biz age · channel
Vetting data written per row
Google rating
Business age
recommended_outreachsms | email
phone + emailscraped from site
02
SystemAutoLive
Outreach prep — Agent B (The Recruiter)
Goal: Generate a personalized, signal-based message
What happens
Mints a 7-day magic-link token in `installer_tokens`, picks SMS or email based on the installer's `recommended_outreach`, generates message body via `generateSmsMessage` / `generateEmailHtml`, logs prep state to `outreach_log` as `pending_review` (admin gate) or `sending` (autopilot).
Why this way
Magic link = zero account creation friction. They click, they see the job.
Admin can review/edit pre-send for high-value campaigns; autopilot for volume.
Visual mock
Prep pipeline
installers row
Agent B — The RecruiterPersonalises message
installer_tokens7-day magic link
State written to outreach_log
pending_reviewadmin gate (manual)
sendingautopilot
Dispatch channel (per recommended_outreach)
SMS via TwiliogenerateSmsMessage()
Email via ResendgenerateEmailHtml()
03
SMSUs → ProLive
SMS arrival — first impression
Goal: Earn the click in 2 segments or less
What happens
First touch for installers whose `recommended_outreach` is SMS-preferred. Carrier filters spike at 3+ segments — current message clocks 205 chars / 2 segments. URL eats ~95 chars (UUID magic link).
Copy
$8,500 Roof Replacement in Denver, CO — customer paid deposit. You'd be the only pro contacted. Details: https://electrumhome.com/installer/auth?token=… — Electrum Home
Why this way
Dollar+city first — only signal that says 'real opportunity, not Angi recycled-lead spam'.
'Only pro contacted' is the killer differentiator vs lead-gen marketplaces.
Brand suffix instead of prefix — first 20 chars are what shows in iOS preview.
Open risk
Still 2 segments because of the long token URL. Going to 1 segment requires a short-link service (e.g. elm.hm/p/abc) — deferred until current copy proves response lift.
Metric to watch
Delivered %, link-click %, reply %.
Visual mock
9:41
E
Electrum Home
Tue 9:41 AM
$8,500 Roof Replacement in Denver, CO — customer paid deposit. You'd be the only pro contacted. Details: https://electrumhome.com/installer/auth?token=… — Electrum Home
iMessage
205 chars · 2 segments · sender ID: Electrum Home
04
EmailUs → ProLive
Email arrival — first impression (alternate channel)
Goal: Survive a 200-unread contractor inbox
What happens
Sent when SMS isn't available or `recommended_outreach` is email-preferred. Branded HTML with project card, scope summary, magic-link CTA, and pro-focused value props.
Subject front-loads the dollar amount + soft question — 15-20% open lift vs statement form in B2B benchmarks.
'Why pros work with us' replaced consumer stats (40k homeowners / Honda) — those don't motivate contractors. Pro-side stats motivate pros.
Forward handler recovers wrong-recipient sends (office line picked up by spouse, etc).
Open risk
Currently sent from `pros@electrumhome.com` — same root as customer transactional. Spam complaints from cold outreach can degrade customer email reputation. Needs a `pros.electrumhome.com` subdomain for isolation.
Metric to watch
Open %, click %, reply %, spam-complaint %.
Visual mock
mail.google.com
E
Electrum Home$8,500 Denver Roof Replacement — want it?— Hi Marcus, We're reaching out from Electrum Home...
9:41 AM
$8,500 Denver Roof Replacement — want it?
Electrum Home<pros@electrumhome.com>
9:41 AM
Electrum Home
Project Opportunity
Hi Marcus,
We're reaching out from Electrum Home — a platform that connects homeowners with licensed professionals. We handle the customer relationship, payments, and project management. You do what you do best.
Available Project
Roof Replacement
Location
Denver, CO
Project Pay
$8,500
Customer Status
Deposit Paid ✓
30-year architectural shingles · ~2,400 sq ft · Single story · No solar panels
How It Works
1.
Review the project details and scope
2.
Accept at the listed price, or counter with your own
This link is valid for 7 days. No account creation required.
Why pros work with us:
Zero lead fees. You only get matched to jobs with a paid deposit.
No bidding war. You're the only pro contacted on each job.
Same-week payouts. Direct deposit via Stripe Connect.
We handle the customer. Intake, scoping, contract, payment, follow-up.
Not the right person? Forward to whoever handles jobs for Denver Roofing Co. and they can claim it with the same link.
05
PagePro actionLive
Magic-link landing — happy path
Goal: Verify token + route to the right project page
What happens
Token validates at `/api/installer/auth`. On success: sets `installer_session` JWT cookie + redirects to `/installer/portal/project/<id>` (or `/installer/portal` if no project bound). No password, no signup.
UI elements
Brand spinner + 'Verifying your access…'
Auto-redirect on success
Why this way
Account creation kills conversion. Magic link is the only way to land them in 1 click.
Token is 1-time-use for first session, then re-entry valid for 30 days from first use.
Visual mock
electrumhome.com/installer/auth?token=eyJ…
Verifying your access...
This will only take a moment.
Auto-redirecting to your project →
06
PagePro actionLive
Magic-link landing — expired / invalid
Goal: Recover the bounce instead of losing them
What happens
Self-serve form: accepts phone OR email, looks up `installers`, mints a fresh `installer_tokens` row, dispatches the new link via Twilio SMS or Resend email. Always returns 200 (enumeration-safe). Preserves the original project_id so the new link lands on the same project page.
UI elements
Red X icon + 'Link Invalid or Expired'
Card: 'Send me a new link' + single input ('phone or email')
Button: 'Send new link'
Success state: ✓ icon + 'New link on the way.'
Fallback: mailto:pros@electrumhome.com
Why this way
Previous UI was a dead-end mailto — pros don't email support, they close the tab.
Recovers 30-50% of expired-link bounces (industry benchmark).
Metric to watch
Resend form submits / expired-link visits.
Visual mock
electrumhome.com/installer/auth?token=expired
Link Invalid or Expired
This access link has expired or already been used.
Equipment confirmation modal on accept (model, brand, serial if applicable)
Why this way
Locked price upfront — pro knows exactly what they'd earn before reading scope details.
Counter-bid option preserves agency for pros who think the price is off — better than losing them entirely.
Open risk
1,789-line client component — first paint on weak mobile signal can be slow. SSR fallback would help.
Visual mock
electrumhome.com/installer/portal/project/prj_…
Project Opportunity
Roof Replacement
$8,500 lockedDenver, COPosted today
Scope Details
Roof area~2,400 sq ft
StoriesSingle story
Shingle type30-year architectural
Deck conditionGood — no replacement expected
Your Decision
08
SystemAutoLive
Accept — handoff to onboarding (if first time)
Goal: Funnel newly-accepted pros into qualification without losing context
What happens
`/api/installer/bid` records the accept. If `installers.onboarding_completed_at` is null, route to `/installer/portal/onboarding?return_to=<projectId>`. If already onboarded, route to `/installer/portal/project/<id>/active`.
Why this way
Onboarding AFTER accept (not before) is the right order — they've already said yes to a real job. The form has purpose now.
`return_to` carries project context so they can be sent back to the project the moment they finish.
return_to preservedPro lands back on their project after onboarding
09
PagePro actionLive
Onboarding — context banner
Goal: Keep them filling the form instead of bouncing
What happens
Sticky banner at the top of `/installer/portal/onboarding` when `return_to` is set and `onboarding_completed_at` is null: 'You're one step from unlocking your project. Most pros complete this in 8-10 minutes. Your progress saves automatically.'
Why this way
Pros who just clicked 'Accept' want to start working. A 4-section form with no time estimate looks like a bait-and-switch.
Time anchor ('8-10 minutes') + auto-save reassurance ('progress saves automatically') addresses both bounce reasons.
Collapsible sections with status icons (complete / in-progress / not-started). Each section saves independently — no all-or-nothing submit. Progress bar at top + deadline banner if `onboarding_deadline` is set.
UI elements
Section 1: Identity (legal name, business name, business address, EIN, business type)
Section 4: Payment (Stripe Connect onboarding link + bank account capture)
Why this way
Per-section save avoids 'I'll come back to this' → never returns.
Stripe Connect is the last step — by the time they hit it, they've already invested 6+ minutes; sunk cost keeps them moving.
Open risk
Photo/cert upload reliability on mobile is unverified. If `installer_documents` bucket isn't permissive enough, this silently fails.
Visual mock
electrumhome.com/installer/portal/onboarding
Onboarding1 of 4 sections complete
1. Identity
Complete
2. Licensing
In progress
License number
e.g. CO-0012345
State
CO
3
Insurance
GL carrier, policy, cert upload
4
Payment
Stripe Connect — same-week payouts
11
PagePro actionLive
Active project — handoff to work
Goal: Pro starts the job
What happens
`/installer/portal/project/<id>/active` — customer contact revealed (name, phone, address), schedule confirmation, photo upload for progress, contract reference. Customer is auto-notified via change-order email pipeline (already wired earlier in this session).
Why this way
Reveal customer contact only AFTER onboarding complete + accept signed. Prevents end-runs around the platform.
`/api/webhooks/twilio/sms` handles all 7 required A2P 10DLC opt-out keywords (STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT, REVOKE) → writes to `sms_opt_outs` table. Non-keyword inbound replies are logged to `outreach_log` with `direction='inbound'` and the original outbound row's `responded_at` is updated.
Why this way
10DLC compliance is mandatory for A2P SMS — non-compliance gets the number suspended.
Two-way logging means ops sees the pro's reply in the admin dashboard without monitoring a separate inbox.
Open risk
Twilio webhook URL not yet registered in their console — handler is live and tested but nothing's pointed at it yet.
Visual mock
9:41
E
Electrum Home
Tue 10:03 AM
$8,500 Roof Replacement in Denver, CO — customer paid deposit. You'd be the only pro contacted. Details: https://electrumhome.com/installer/auth?token=… — Electrum Home
STOP
You've been unsubscribed from Electrum Home messages. Reply START to resubscribe. Help: pros@electrumhome.com
`isSmsOptedOut(phone)` lookup in `sendOutreach` BEFORE any Twilio API call. Opted-out sends are marked `status: 'skipped_opted_out'` in `outreach_log` and counted but never billed to Twilio.
Why this way
Compliance + cost — even with auto-STOP at Twilio level, our own list catches edge cases (number reuse, manual ops opt-outs).
Visual mock
sendOutreach() call path
sendOutreach()called by Agent B
isSmsOptedOut(phone)checks sms_opt_outs
Routing on opt-out check
Opted out → skipstatus: 'skipped_opted_out'
logged, never sent to Twilio
Not opted out → sendTwilio API called
outreach_log: sent
14
EmailUs → ProPending you
Follow-up sequence — T+24h
Goal: Recover non-responders
What happens
Second touch via email if first message didn't get a reply by T+24h. Different angle: scope detail + 'noticed you weren't able to look at Tuesday's text'.
Why this way
Cold conversion typically takes 4-7 touches. Single-touch leaves 50%+ of convertible pros on the table.
Open risk
Not yet shipped — needs brand-voice review before drafting.
Visual mock
Note:This touchpoint is “Pending you” — no copy has been approved yet. The mock below shows the intended structure based on the storyboard strategy.
mail.google.com
E
Electrum HomeStill available: $8,500 Denver Roof Replacement— I know Tuesday's text was easy to miss — the project is still open...
Wed 9:41 AM
Still available: $8,500 Denver Roof Replacement
Electrum Home<pros@electrumhome.com>
Wed 9:41 AM
Electrum Home
Follow-up · T+24h
Hi Marcus,
I know yesterday's text was easy to miss — inbox gets busy. I wanted to make sure you saw the project before it goes to another pro.
Still open
Roof Replacement — Denver, CO
$8,500 · Deposit paid
Scope:~2,400 sq ft, single story, 30-year architectural shingles. Customer has signed off on the scope and paid a deposit. If you want it, it's yours — no bidding war.
Third touch. Framing: 'Job's getting reassigned tomorrow — last shot.' Creates urgency without lying — we DO reassign if no response by some threshold.
Why this way
Scarcity + soft deadline drives 30-50% of late conversions in cold sequence benchmarks.
Visual mock
Note:This touchpoint is “Pending you” — no copy has been approved yet. The mock below shows the intended structure based on the storyboard strategy.
mail.google.com
E
Electrum HomeLast call — Denver Roof project gets reassigned tomorrow— We're reassigning the project to another pro if we don't hear back by end of day...
Fri 9:41 AM
Last call — Denver Roof project gets reassigned tomorrow
Electrum Home<pros@electrumhome.com>
Fri 9:41 AM
Electrum Home
Final follow-up · T+72h
Hi Marcus,
Quick heads-up — if we don't hear back by end of day, we're releasing the Denver Roof Replacement to the next pro on our list.
⏱ Job reassignment pending
Roof Replacement — Denver, CO
$8,500 · Last chance
We DO reassign when there's no response — the customer is waiting, and we committed to a timeline. This isn't a fake deadline.
Code is in place. These are the dashboard / DNS flips that activate the full pipeline.
Outreach subdomain isolation (pending you). Set up `pros@outreach.electrumhome.com` in Resend with its own DKIM/SPF, plus DNS records at NameBright. Outreach + customer transactional emails currently share the same root domain — spam complaints from cold outreach can degrade customer transactional deliverability.
A2P 10DLC brand + campaign registration in Twilio (carrier filtering depends on it)
Twilio webhook URL pointing to `/api/webhooks/twilio/sms`
Resend domain auth for `outreach.electrumhome.com` subdomain
Optional: short-link domain (`elm.hm`) for 1-segment SMS
Audit doc (full findings)
memory/installer-outreach-audit-2026-05-31.md
12 findings ranked by impact, with expected response-rate lift per change.
Code references
src/lib/fulfillment/agents.ts — Agent A / B / C / D