1. Lead Intake (How leads enter the system)
+---------------+ +---------------+ +----------------------+
| Google Sheets | | Wix Forms | | Manual / AI Agent |
| (Sheet1+4) | | Webhook | | (in-app creation) |
+-------+-------+ +-------+-------+ +----------+-----------+
| | |
v v v
syncGoogleSheetLeads handleWixFormSubmission AgentLead.create()
(scheduled workflow) (Wix webhook workflow) (manual via UI)
| | |
v v v
+-----------------------------------------------------------+
| DEDUP CHECK |
| * Phone in deleted-phones blocklist? -> SKIP |
| * Phone already in AgentLeads? -> SKIP (or backfill) |
| * Date newer than sync marker? -> PROCESS |
+----------------------------+------------------------------+
|
v
+---------------------+
| AgentLead.create() |
| status = "New" |
+----------+----------+
|
v
+---------------------+
| startAIChatForLead |
| (shared/startAIChat)|
+----------+----------+
|
v
+--------------------------------+
| 1. Pick Twilio number (pool) |
| 2. Pin to lead forever |
| 3. Build opening message |
| 4. Send via sendSmsValidated |
| 5. Create SMSConversation |
| (is_ai_managed=true) |
| 6. Update lead -> "Contacted"|
+--------------------------------+2. Inbound SMS Processing (When a customer replies)
Customer sends SMS
|
v
+------------------------------------------+
| Twilio receives SMS |
| -> fires webhook (handleSMSWebhook) |
| AND/OR polled by pollIncomingSMS |
| (5 workflows: Minute 0-4 + Redundant)|
+---------------------+---------------------+
|
v
+------------------------------------------+
| pollIncomingSMS |
| 1. Fetch recent messages from Twilio |
| 2. Is this an ADMIN reply? (7788614055)|
| YES -> route to handleEPPReply |
| or handleWirelessReply |
| NO -> continue to customer flow |
+---------------------+---------------------+
|
v
+------------------------------------------+
| MATCH to SMSConversation by phone number|
| Prefer AI-managed conversation |
+---------------------+---------------------+
|
v
+------------------------------------------+
| Save inbound message to messages[] |
+---------------------+---------------------+
|
v
+------------------------------------------+
| Is lead "Stale"? |
| YES -> Reactivate to "In Conversation" |
| Clear re-engagement state |
| Resume bot (ai_bot_active=true) |
| NO -> continue |
+---------------------+---------------------+
|
v
+------------------------------------------+
| Trigger processAgentLeadReply |
| (the AI brain - see next section) |
+------------------------------------------+3. The AI Brain: processAgentLeadReply (Core decision engine)
START
|
v
+-----------------------------+
| Load conversation + lead |
+--------------+--------------+
|
v
+-----------------------------+ NO +------------------+
| is_ai_managed AND |---------->| SKIP (rep handles|
| ai_bot_active = true? | | manually) |
+--------------+--------------+ +------------------+
| YES
v
+-----------------------------+
| Mark lead "In Conversation" |
| (immediately, before LLM) |
+--------------+--------------+
|
v
+-----------------------------+ YES +------------------+
| Already replied to this SID?|---------->| SKIP (dedup) |
| (last_replied_inbound_sid) | +------------------+
+--------------+--------------+
| NO
v
+-----------------------------+
| CLAIM this inbound SID |
| (prevent concurrent triggers)|
+--------------+--------------+
|
v
+-----------------------------------------------------+
| SHORT-CIRCUIT CHECKS |
| (skip LLM, send canned response instead) |
| |
| 1. Callback pending + short confirmation? |
| -> Send warm ack ("Sounds great, talk then!") |
| |
| 2. Pending callback proposal + "yes/ok/sure"? |
| -> Confirm callback_at, clear proposal, ack |
| |
| 3. STOP / UNSUBSCRIBE / OPT-OUT? |
| -> Send stop ack, mark "Not Interested", |
| permanently stop bot |
+---------------------+------------------------------+
| (none matched)
v
+-----------------------------------------------------+
| FIRST-REPLY NOTIFICATION |
| If this is the customer's FIRST inbound reply: |
| -> SMS reps (+17788614055, +16047103216) |
| "New lead reply! {name} just responded" |
+---------------------+------------------------------+
|
v
+-----------------------------------------------------+
| BUILD LLM CONTEXT |
| * Conversation history (all messages) |
| * Lead data (TELUS status, product interest, notes)|
| * City population check (for Wireless Internet) |
| * Pending callback context (don't re-follow-up) |
| * Learned patterns (from past rep interventions) |
| * Current Vancouver time (for callback scheduling) |
+---------------------+------------------------------+
|
v
+-----------------------------------------------------+
| INVOKE LLM (Claude Sonnet 4.6) |
| Sends: |
| * Massive sales persona prompt (~15K tokens) |
| * Conversation history + context |
| * Customer's latest message |
| |
| Receives JSON: |
| { message, ready_for_quote, ready_for_veriff, |
| extracted_params: { segment, products, speed, |
| telusHomeCustomer, callback_at, ... } } |
+---------------------+------------------------------+
|
v
+-----------------------------------------------------+
| DECISION BRANCHES |
| |
| ready_for_veriff? --------> CREATE VERIFF SESSION |
| (customer agreed to price) | |
| v |
| Append Veriff URL |
| Update lead veriff data |
| |
| ready_for_quote? ------------> CALL PLAN ADVISOR |
| (qualification complete) | |
| AND not already quoted? v |
| AND not Veriff done? getPlanRecommendationInline|
| AND (re-quote OR no quote)? (LLM reads plan PDFs)|
| | |
| Append quote text |
| Start 1-min quote timer |
| |
| customer_declined? -----------> SEND THANK-YOU |
| (no / stop / not interested) | |
| v |
| Mark "Not Interested" |
| Stop bot permanently |
| |
| customer_requested_space? ----> PAUSE RE-ENGAGE |
| (leave me alone / will contact) | |
| Schedule single 5-day |
| soft follow-up |
| |
| customer_in_contract? --------> STOP CONVERSATION |
| (existing TELUS contract) | |
| Mark "Not Interested" |
| Stop bot |
| |
| end_conversation? ------------> STOP BOT |
| (already contacted/serviced) | |
| Stop bot (no status chg) |
| |
| is_renewal? ------------------> TAG AS RENEWAL |
| (existing TELUS internet) | |
| Add [INTERNET RENEWAL] |
| to rep_notes |
| No quote (team handles) |
| |
| manager_credit_pending? ------> START 2-MIN TIMER |
| (customer agreed to check) | |
| manager_credit_at set |
| (poller sends approval |
| follow-up after 2 min) |
| |
| callback_at set? --------------> SCHEDULE CALLBACK |
| (customer asked for call) | |
| Save callback_at |
| Clear pending proposal |
| |
| pending_callback_proposed_at? -> STORE PROPOSAL |
| (bot proposed a time) | |
| Save proposed time |
| Start 5-min auto-confirm |
| |
| wireless_serviceability_request? > SMS TEAM |
| (rural address check) | |
| Send address to team |
| Store pending_wireless |
| |
| customer_asked_epp + workplace? -> SMS TEAM |
| (customer asked about EPP) | |
| Send employer to team |
| Quote WITHOUT EPP disc. |
+---------------------+------------------------------+
|
v
+-----------------------------------------------------+
| SEND REPLY VIA TWILIO |
| sendSmsValidated (shared/twilioSms.ts) |
| * If pinned number fails -> retry on another pool |
| * If all numbers fail -> mark lead "Failed" |
| * Update pinned number if switched |
+---------------------+------------------------------+
|
v
+-----------------------------------------------------+
| UPDATE CONVERSATION + LEAD |
| * Append outbound message to messages[] |
| * Mark inbound message as replied=true |
| * Update callback/proposal/credit/quote timers |
| * Update space_requested state |
| * Update lead status -> "In Conversation" |
| * Save recommendation_summary if quote sent |
+-----------------------------------------------------+4. Re-Engagement Flow (When customers go quiet)
Scheduled hourly (Re-engagement Pings workflow)
|
v
+------------------------------------------------+
| Load all AI-managed, bot-active conversations |
| + all AgentLeads (for status check) |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| Skip leads with status: |
| Not Interested / Closed / Awaiting Install / |
| Failed / Stale |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| SPACE REQUESTED? (customer asked to be left |
| alone) |
| YES -> Did they reply since? |
| YES -> Clear space state, resume normal |
| NO -> 5 days passed? |
| YES -> Send ONE soft follow-up |
| NO -> Skip |
+---------------------+--------------------------+
| (no space request)
v
+------------------------------------------------+
| LAST MESSAGE INBOUND? (customer replied) |
| YES -> Reset re-engagement state (step=0) |
| Skip (bot handles live) |
+---------------------+--------------------------+
| (last is outbound)
v
+------------------------------------------------+
| DETERMINE SEQUENCE TYPE: |
| |
| inboundCount === 0 -> NORMAL (never replied) |
| inboundCount > 0 -> RE-ENGAGEMENT (replied |
| then went quiet) |
+---------------------+--------------------------+
|
+------------+------------+
v v
+--------------+ +----------------------+
| NORMAL LEAD | | RE-ENGAGEMENT LEAD |
| (never | | (has replied before) |
| replied) | +----------+-----------+
+-------+------+ |
| v
v +---------------------------+
+--------------+ | 7-DAY, 1/DAY SEQUENCE |
| 20-MSG OFFSET| | |
| SEQUENCE | | * Sent at customer's last|
| | | reply hour |
| * Day 1: 4 | | * Step 0: wait 1 full day|
| msgs | | * Steps 1-6: 1 msg/day |
| * Day 2: 3 | | at reply hour |
| * Day 4: 3 | | * Step 7: FINAL store- |
| * Day 6: 3 | | contact message |
| * Day 9: 3 | | -> mark "Stale" |
| * Day 12+: 4 | | -> stop bot |
| (less freq)| +-----------+--------------+
+-------+------+ |
| |
v v
+------------------------------------------------+
| EXHAUSTION CHECK: |
| Normal: 20+ outbound, 0 inbound |
| -> mark "Stale", stop bot, keep record |
| Re-engagement: 7 days + final msg sent |
| -> mark "Stale", stop bot |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| 3-DAY RULE (In Conversation -> Contacted): |
| If status is "In Conversation" but customer |
| hasn't replied to last outbound for 3+ days: |
| -> downgrade to "Contacted" |
| (re-engagement continues from current step) |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| MESSAGE GENERATION: |
| LLM (Gemini Flash) generates context-aware |
| follow-up based on actual conversation history |
| * Never references non-existent discussions |
| * Varies wording each time |
| * Max 1-2 sentences, no "haha"/"lol" |
+------------------------------------------------+5. Scheduled Callbacks & Timers (processScheduledCallbacks)
Runs frequently (Process Scheduled Callbacks workflow)
|
v
+-----------------------------------------------------+
| Load all AI-managed conversations |
+---------------------+------------------------------+
|
v
+-----------------------------------------------------+
| CHECK EACH TIMER (in priority order): |
| |
| 1. MANAGER CREDIT (manager_credit_at) |
| * 2 minutes passed since customer agreed? |
| * Not already sent? (manager_credit_sent=false)|
| -> Send "manager approved" follow-up SMS |
| -> Set manager_credit_sent=true |
| |
| 2. QUOTE FOLLOW-UP (quote_followup_at) |
| * 1 minute passed since quote sent? |
| * Customer hasn't replied? (last msg outbound) |
| * Not already sent? (quote_followup_sent=false)|
| -> Send "Any comments?" follow-up |
| -> Set quote_followup_sent=true |
| |
| 3. CALLBACK QUEUE (callback_at) |
| * callback_at time arrived? |
| * callback_sent != true? |
| -> Send "following up as planned" SMS |
| -> callback_attempts++ |
| -> If 3 attempts with no reply: |
| clear callback_at (badge removed) |
| -> If customer replies: clear callback_at |
| |
| 4. CALLBACK PROPOSAL AUTO-CONFIRM |
| (pending_callback_proposed_at) |
| * 5 minutes passed since proposal sent? |
| -> Promote to callback_at (auto-confirm) |
| -> Clear pending proposal |
| |
| 5. BOT REACTIVATION SAFETY NET |
| * ai_bot_active = false? (rep paused it) |
| * bot_permanently_stopped = false? |
| * 15 minutes passed since last activity? |
| * Lead not Stale/Failed? |
| -> Re-enable bot (ai_bot_active = true) |
| (rep can pause for 15 min, then auto-resumes)|
+-----------------------------------------------------+6. Plan Advisor (Quote generation)
Called by processAgentLeadReply when ready_for_quote=true
|
v
+------------------------------------------------+
| INPUT: extracted_params from LLM |
| * customerSegment (consumer/business) |
| * products (internet/mobility/optik_tv/etc) |
| * internet_speed (500MB/1.5GB/3GB) |
| * telusHomeCustomer (M&H eligibility) |
| * byodLines, contractLines (mobility) |
| * eppDiscount (always false - bot never uses)|
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| FETCH PLAN DOCUMENTS |
| * Filter by segment (consumer/business) |
| * Filter by product type |
| * Check expiration_date (skip expired) |
| * Get device price docs if mobility_w_device |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| BUILD LLM PROMPT |
| * Plan PDF URLs (uploaded docs) |
| * Customer parameters |
| * Strict pricing rules: |
| - 1.5GB only ($75 2yr / $120 m2m) |
| - No 1GB / 300MB |
| - Auto-pay already included |
| - M&H $10 off only if consumer mobility |
| - No stacking discounts |
| - 2-year term default |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| INVOKE LLM (Claude Sonnet) |
| Reads plan PDFs + applies rules |
| Returns formatted price quote |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| RETURN quote text to processAgentLeadReply |
| -> appended to bot's reply message |
+------------------------------------------------+7. Twilio SMS Infrastructure (Number pool & delivery)
Any outbound SMS (bot reply, re-engagement, manual)
|
v
+------------------------------------------------+
| sendSmsValidated(base44, {from, to, body}) |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| GET AVAILABLE POOL NUMBERS |
| * From env vars (TWILIO_PHONE_NUMBER 1-6) |
| * Check each number's health record: |
| - Permanently banned? -> exclude |
| - Temporarily banned (24h)? -> exclude |
| * Round-robin selection (rotate start index) |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| TRY SEND on first number |
| POST to Twilio API |
+---------------------+--------------------------+
|
+----------+----------+
v v
SUCCESS FAILURE
| |
v v
+--------------+ +--------------------------+
| Track success | | Track failure |
| Update health | | Update health: |
| (success++) | | failure++ |
| | | * 3 fails in 24h -> 24h ban|
| | | * 10+ fails -> permanent |
| | | ban |
+-------+------+ +----------+---------------+
| |
| v
| +--------------------------+
| | RETRY on next pool number|
| | (repeat until success or |
| | all numbers exhausted) |
| +----------+---------------+
| |
v v
+----------------------------------------------+
| RETURN result |
| * ok: true/false |
| * sid: Twilio message SID |
| * from_number: the number that worked |
| * error: failure reason |
+----------------------------------------------+
|
v
+------------------------------------------------+
| IF from_number changed (retry switched): |
| * Update conversation.from_number |
| * Update AgentLead.pinned_phone_number |
| (so all future messages use the working num) |
+------------------------------------------------+
|
v
+------------------------------------------------+
| IF ALL NUMBERS FAILED: |
| * Mark lead "Failed" (undeliverable) |
| * Stop bot permanently |
| * (This is NOT "Stale" - Stale = no reply) |
+------------------------------------------------+8. Admin Reply Routing (EPP & Wireless checks)
Team member at 7788614055 replies to a check SMS
|
v
+------------------------------------------------+
| pollIncomingSMS detects admin number |
| -> routes to specialized handler |
+---------------------+--------------------------+
|
+------------+------------+
v v
+--------------+ +------------------+
| EPP CHECK | | WIRELESS CHECK |
| handleEPPReply| | handleWirelessReply|
+-------+------+ +--------+---------+
| |
v v
+--------------+ +--------------------------+
| Match to | | Match to |
| conversation | | conversation |
| by pending_ | | by pending_wireless_ |
| epp_workplace| | address |
+-------+------+ +--------+-----------------+
| |
v v
+--------------+ +--------------------------+
| Reply "YES"? | | Reply contains |
| -> Customer | | availability info? |
| qualifies | | -> Send $65 Wireless |
| -> Rep follows| | offer to customer |
| up with | | (or "not available") |
| EPP price | | |
| | | Store result, |
| Reply "NO"? | | clear pending state |
| -> Customer | | |
| doesn't | | Customer replies -> |
| qualify | | processAgentLeadReply |
| | | handles next step |
| Clear pending| | (Veriff if YES, |
| epp state | | reassurance if NO) |
+--------------+ +--------------------------+9. Lead Status Lifecycle
+---------+
| New | <- Created (sync/manual/ai_agent)
+----+----+
| (opening message sent)
v
+-----------+
| Contacted | <- Bot sent opening, no customer reply yet
+-----+-----+
| (customer replies)
v
+------------------+
| In Conversation | <- Customer actively replying
+--------+---------+
| |
| (3+ days | (customer agrees to price)
| silent) v
v +----------+
+----------+ | Awaiting | <- Sale done, install date TBD
| Contacted| | Install |
+-----+----+ +-----+----+
| | (install date set)
| v
| +--------+
| | Closed | <- Sale complete + install date
| +--------+
|
| (20+ outbound, 0 inbound)
v
+--------+ +------------------+
| Stale | | Not Interested |
+--------+ | <- Customer declined/
| STOP / in contract /
| already serviced
+------------------+
+--------+
| Failed | <- SMS undeliverable / invalid number
+--------+ (bot permanently stopped)
REACTIVATION RULES:
* Stale + customer replies -> "In Conversation" (bot resumes)
* Contacted + customer replies -> "In Conversation"
* Any status + send fails -> "Failed" (bot stops)
* Any status + customer declines -> "Not Interested" (bot stops)10. Veriff ID Verification Flow
Customer agrees to price (processAgentLeadReply)
|
v
+------------------------------------------------+
| LLM sets ready_for_veriff = true |
| (or mentions Veriff/link in message) |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| createVeriffSession (shared/veriff.ts) |
| * POST to Veriff API |
| * Returns { sessionId, url } |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| Append Veriff URL to bot reply |
| Update AgentLead: |
| * veriff_session_id = sessionId |
| * veriff_verification_status = "pending" |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| Customer clicks link -> Veriff web session |
| Customer completes ID verification |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| Veriff webhook -> handleVeriffWebhook |
| * Updates veriff_verification_status |
| (approved / declined / resubmitted) |
+------------------------------------------------+11. Learning System (Manual rep interventions)
Rep manually replies to a customer (pauses bot)
|
v
+------------------------------------------------+
| learnFromManualReply (shared) |
| * Capture trigger_message (customer's msg) |
| * Capture conversation_context (last msgs) |
| * Capture manual_reply (rep's response) |
| -> Store as ManualReplyExample |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| Infer LearnedPattern via LLM |
| * situation: what was happening |
| * customer_need: what customer wanted |
| * obstacle: what was blocking |
| * human_action: what rep did |
| * reasoning: why it was appropriate |
| * conditions: when to apply |
| * exclusions: when NOT to apply |
| * generalized_rule: reusable decision rule |
| * keywords: tags for matching |
| * pattern_status: "candidate" (single example)|
| * confidence: 0.0-1.0 |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| FUTURE CONVERSATIONS: |
| processAgentLeadReply loads top patterns |
| * Score by keyword overlap + status + conf |
| * Inject top 4 into LLM context |
| * Bot applies pattern ONLY if: |
| - Current conditions match |
| - Action is authorized by core rules |
| - (patterns never grant new authority) |
+---------------------+--------------------------+
|
v
+------------------------------------------------+
| VALIDATION OVER TIME: |
| * Similar situation + similar action -> |
| validation_count++, -> "validated" |
| * Similar situation + different action -> |
| contradiction_count++, -> "invalidated" |
+------------------------------------------------+12. End-to-End Customer Journey Summary
1. Lead fills Facebook/Google form
-> Google Sheet
-> syncGoogleSheetLeads (every 2 min)
-> AgentLead created
-> startAIChatForLead sends opening SMS
-> Status: "Contacted"
2. Customer replies
-> Twilio webhook / pollIncomingSMS
-> Save inbound message
-> Reactivate if Stale
-> processAgentLeadReply
-> LLM generates reply (with context)
-> Plan Advisor quote (if qualified)
-> Send via Twilio pool
-> Status: "In Conversation"
3. Customer goes quiet
-> sendReengagementMessages (hourly)
-> 20-msg sequence (never replied) OR
7-day sequence (replied then quiet)
-> LLM-generated follow-ups
-> If exhausted: Status "Stale", bot stops
4. Customer re-engages
-> pollIncomingSMS detects reply
-> Reactivate Stale -> "In Conversation"
-> Clear re-engagement state
-> processAgentLeadReply resumes
5. Customer agrees to price
-> LLM sets ready_for_veriff
-> createVeriffSession
-> Veriff URL sent to customer
-> Customer verifies ID
-> Veriff webhook updates status
-> Rep follows up to complete sale
6. Sale complete
-> Rep marks "Closed" (with install date)
-> OR "Awaiting Install" (TBD)
-> installationFollowup sent 1 day post-install