How SMS, email, minutes, and credits flow through every file
server.js bridges it to Deepgram → AI talks to customer → AI calls functions like save_electrician_job → the flow file (electrician.js) saves to Firebase, sends SMS & email, deducts SMS/email credits → call ends → server.js finaliseCall() saves duration & transcript → separately, Twilio's phone-number-level Status Callback fires twilioStatusCallback which deducts call minutes.
CALL LIFECYCLE
checkAndConsumeSMSCredit() or checkAndConsumeEmailAllowance()users/{uid}.features.availableSMSCredits — returns {allowed: true/false}twilio.messages.create() or sgMail.send()deductSMSCredit() or deductEmailCredit()features.availableSMSCredits by segment count
phoneNumbers/{id}.stats.totalSMSSent / totalEmailsSent (all-time counters)phoneNumbers/{id}/monthlyStats/{YYYY-MM}.smsSent / emailsSent (monthly breakdown)Send to:
Send from:
Sent for: jobs, quotes, emergencies, callbacks, enquiries, messages
Send to:
Send from:
Sent for: job confirmations, emergency acknowledgements, quote confirmations
calculateSMSSegments() function handles this.
Send to:
Send from:
Professional HTML email with job details, customer info, pricing, timestamps. Different colours per type (red=emergency, blue=quote, amber=job).
Time the AI agent is talking to the caller (before any transfer).
Calculated from call start → transfer bridge time (or call end if no transfer).
Total call time including any transfer to a human.
Uses Twilio's actual CallDuration for accuracy.
server.js finaliseCall(). That only saves the duration split (AI vs transfer) to the call doc. The actual deduction is triggered by the Twilio phone-number-level Status Callback URL (set in the Twilio console under each number's Voice Configuration → Call Status Changes), which POSTs to twilioStatusCallback on every call end — regardless of TwiML or call type (voice_agent, direct_divert, standard AI). This uses Twilio's real CallDuration to avoid double-charging.
statusCallbackEvent attributes on <Number> elements in TwiML, which are an additional layer used only in <Dial> transfer scenarios. The number-level callback fires for ALL calls. The TwiML-level callbacks only fire for direct_divert and standard AI transfer calls.
smsHistory / emailHistory subcollections. This lets you see exactly what was sent, when, to whom, with Twilio SID for delivery tracking.
| Flow File | SMS Sends | smsHistory | Email Sends | emailHistory | monthlyStats |
|---|---|---|---|---|---|
| vet-flow.js | 2 | ✅ | 1 | ✅ | ✅ |
| nimble-tek.js | 2 | ❌ | 1 | ❌ | ❌ |
| sushi-grill.js | 5 | ❌ | 2 | ❌ | ❌ |
| take-out.js | 5 | ❌ | 2 | ❌ | ❌ |
| team-connect.js | 2 | ❌ | 0 | — | ❌ |
| electrician.js | ? | 🔍 | ? | 🔍 | 🔍 |
| plumber.js | ? | 🔍 | ? | 🔍 | 🔍 |
save_electrician_job → flow.handleSaveOrder()save_enquiry → flow.handleSave()save_udriveme_enquiry → flow.handleSaveOrder()transfer_call → handled locally → returns pending_transferend_call → handled locally → returns end_call: truecheck_returning_customer → auto-routed via dynamic handler namegenericSaveEnquiry() if no flow handler existscheck_returning_customer → handleCheckReturningCustomer and checks if the flow exports that method.
getFullPrompt() — builds the entire AI system promptgetFunctions() — defines what tools the AI can callhandleSaveOrder() — saves jobs/quotes/emergencies, marks flowCompleted, triggers hanguphandleSave() — saves enquiries/callbacks/messagessendJobNotifications() — SMS to business + SMS to customer + email to businesssendEnquiryNotifications() — SMS + email for callbacks/enquiriescheckAndConsumeSMSCredit() — checks credits (no deduction)deductSMSCredit() — deducts after successful sendcheckAndConsumeEmailAllowance() — checks email allowancedeductEmailCredit() — deducts after successful sendhandleCheckReturningCustomer() — recognises repeat callerssaveCustomerForRecognition() — stores caller for next timetwilioVoiceWebhook — initial call setup, routes to Deepgram bridgetwilioStatusCallback — fires when call ends via Twilio phone-number-level Status Callback URL (fires for ALL call types: voice_agent, direct_divert, standard AI)updateCallWithDuration() — saves final duration to call docdeductMinutesFromUserAccount() — THIS deducts AI + normal minutescheckLowBalanceAndAlert() — warns user if minutes running lowswitchAllPhonesToDivert() — auto-diverts if minutes hit 0smsStatusCallback — tracks SMS delivery status from TwilioprocessCompletedCall() — ⚠️ DEAD CODE — defined but never calledupdateCallWithDurationEnhanced() — ⚠️ DEAD CODE — defined but never calledtwilioStatusCallback is triggered by the Twilio phone-number-level Status Callback URL (set in Twilio console, not TwiML), so it fires for all call types including voice_agent. Uses Twilio's actual CallDuration field which is always accurate.
.set({ merge: true }).
users/{uid}.features.* — these are the "balance" that gets decrementedusers/{uid}/phoneNumbers/{id}.stats.* — display counters per numberusers/{uid}/phoneNumbers/{id}/monthlyStats/{YYYY-MM} — per-month breakdown for dashboards
deductSMSCredit(), but the phone number stats were under-reporting.
| Flow File | monthKey | monthlyStats | increment fix | emoji fix | Status |
|---|
[ERR] Monthly SMS stat: / [ERR] Monthly email stat: errorsmonthlyStats/{YYYY-MM} for "This Month" displaystats.totalSMSSent) remain accurate and unchanged.set({ merge: true }) — no migration needed for existing data