<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jason Shouldice</title>
    <description>The latest articles on DEV Community by Jason Shouldice (@gamlin).</description>
    <link>https://dev.to/gamlin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3834423%2Fb36d2124-bd80-463b-a549-5f5d23fc01ca.jpg</url>
      <title>DEV Community: Jason Shouldice</title>
      <link>https://dev.to/gamlin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gamlin"/>
    <language>en</language>
    <item>
      <title>VICIdial for Collections Agencies in 2026: Third-Party Collections, Skip Tracing, and Regulation F Configuration</title>
      <dc:creator>Jason Shouldice</dc:creator>
      <pubDate>Sat, 28 Mar 2026 13:06:03 +0000</pubDate>
      <link>https://dev.to/gamlin/vicidial-for-collections-agencies-in-2026-third-party-collections-skip-tracing-and-regulation-f-3ajn</link>
      <guid>https://dev.to/gamlin/vicidial-for-collections-agencies-in-2026-third-party-collections-skip-tracing-and-regulation-f-3ajn</guid>
      <description>&lt;p&gt;&lt;strong&gt;AI-powered skip tracing now delivers 3-5x higher right-party contact rates than manual lookups. The industry-standard right-party contact rate sits around 25% — top performers using multi-source skip tracing hit 76%. But every one of those contacts must comply with Regulation F's 7-in-7 call cap, the FDCPA's time-of-day restrictions, the TCPA's consent revocation rules (full enforcement April 2026), and a patchwork of state collection laws that change faster than most compliance departments can track. Here's the 2026 VICIdial configuration for collections agencies that want high contact rates without high risk.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Collections is the most heavily regulated outbound calling vertical in the United States. The FDCPA has been on the books since 1977, but Regulation F — which codified specific, measurable rules for call frequency, electronic communication, and disclosure requirements — changed the compliance landscape permanently when it took effect in November 2021. Four years later, the enforcement environment continues to tighten.&lt;/p&gt;

&lt;p&gt;This post is the 2026 update to our &lt;a href="https://dev.to/blog/vicidial-debt-collection-compliance/"&gt;debt collection compliance guide&lt;/a&gt;, focused specifically on third-party collections agencies, skip tracing integration, and the regulatory changes that hit in 2025-2026. If you're a first-party collector (collecting your own debts), some of the FDCPA rules don't apply to you — but most of this configuration still applies because TCPA, state laws, and the CFPB's enforcement posture apply to everyone.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Changed in 2025-2026
&lt;/h2&gt;

&lt;h3&gt;
  
  
  TCPA Consent Revocation — Full Enforcement April 2026
&lt;/h3&gt;

&lt;p&gt;The FCC's consent revocation rule takes effect April 11, 2026. Under this rule:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A consumer can revoke consent to receive automated calls/texts by "any reasonable means" — including saying "stop calling me" during a call&lt;/li&gt;
&lt;li&gt;Revocation must be processed within a "reasonable time" — interpreted as 24-48 hours&lt;/li&gt;
&lt;li&gt;The "revocation-all" component (where revoking one type of automated contact blocks all automated contact) has been delayed until January 31, 2027&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For collections agencies, this is a significant operational challenge.&lt;/strong&gt; A debtor who says "stop calling me" during a collection call triggers a consent revocation that must be processed system-wide within 48 hours. If you have multiple phone numbers for that debtor across multiple accounts, ALL automated dialing to ALL numbers must stop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;VICIdial implementation:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Custom Fields:
  CONSENT_REVOKED: ENUM (NO, YES)
  REVOCATION_DATE: DATETIME
  REVOCATION_METHOD: VARCHAR (verbal, text, email, mail)
  REVOCATION_AGENT: VARCHAR (agent who received revocation)
  CEASE_DESIST: ENUM (NO, VERBAL, WRITTEN)
  CD_DATE: DATETIME

Campaign Filter:
  Exclude all records where CONSENT_REVOKED = YES OR CEASE_DESIST != NO
  Filter updates: Every hour (not just daily — 48-hour processing window)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  State Collection Law Updates
&lt;/h3&gt;

&lt;p&gt;Several states enacted or updated collection-specific regulations in 2025-2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;New York&lt;/td&gt;
&lt;td&gt;Updated frequency caps&lt;/td&gt;
&lt;td&gt;Stricter than Reg F in some circumstances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;California&lt;/td&gt;
&lt;td&gt;CCPA/CPRA data handling&lt;/td&gt;
&lt;td&gt;Debt info may be "personal information" under CCPA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Massachusetts&lt;/td&gt;
&lt;td&gt;Updated collection call restrictions&lt;/td&gt;
&lt;td&gt;Narrower calling windows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Illinois&lt;/td&gt;
&lt;td&gt;Biometric data in skip tracing&lt;/td&gt;
&lt;td&gt;BIPA applies to voice recordings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Colorado&lt;/td&gt;
&lt;td&gt;State-level frequency caps&lt;/td&gt;
&lt;td&gt;More restrictive than federal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Build state-level campaign filters in VICIdial to handle these variations.&lt;/p&gt;

&lt;h3&gt;
  
  
  CFPB Enforcement Posture
&lt;/h3&gt;

&lt;p&gt;The CFPB's enforcement of Regulation F has produced several consent orders against collection agencies for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exceeding the 7-in-7 call frequency cap&lt;/li&gt;
&lt;li&gt;Failing to properly identify the caller and the debt&lt;/li&gt;
&lt;li&gt;Third-party disclosure (revealing the debt to someone other than the debtor)&lt;/li&gt;
&lt;li&gt;Failing to send required validation notices within 5 days of initial contact&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't theoretical risks — they're active enforcement patterns.&lt;/p&gt;




&lt;h2&gt;
  
  
  Regulation F Compliance in VICIdial
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The 7-in-7 Call Frequency Cap
&lt;/h3&gt;

&lt;p&gt;Regulation F creates a presumption of FDCPA harassment if a collector:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Makes more than &lt;strong&gt;7 telephone calls within 7 consecutive days&lt;/strong&gt; regarding a particular debt&lt;/li&gt;
&lt;li&gt;Makes a call within &lt;strong&gt;7 days after a telephone conversation&lt;/strong&gt; with the consumer about the debt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Critical distinctions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The cap is &lt;strong&gt;per debt&lt;/strong&gt;, not per consumer. If a debtor has 3 debts with your agency, you can make 7 calls per week on EACH debt (21 total). But this is the legal maximum — not a recommended practice.&lt;/li&gt;
&lt;li&gt;The 7-day window is &lt;strong&gt;rolling&lt;/strong&gt;, not calendar-week based.&lt;/li&gt;
&lt;li&gt;The cap counts &lt;strong&gt;call attempts&lt;/strong&gt;, not connections. Unanswered calls count.&lt;/li&gt;
&lt;li&gt;The conversation lockout is &lt;strong&gt;7 days from the last conversation&lt;/strong&gt;, not the last attempt.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  VICIdial Configuration for 7-in-7 Tracking
&lt;/h3&gt;

&lt;p&gt;VICIdial doesn't have built-in per-debt frequency capping, so you need to build it with custom fields and external logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Custom Fields (per lead record):
  DEBT_ID: VARCHAR (unique identifier for the specific debt)
  ATTEMPTS_7DAY: INT (rolling count of call attempts in last 7 days)
  LAST_CONVERSATION: DATETIME (timestamp of last live conversation)
  NEXT_ELIGIBLE_DATE: DATE (earliest date the next call can be made)

External Logic (cron job running every hour):
  1. Count call attempts per DEBT_ID in the vicidial_log table
     for the last 7 days
  2. If count &amp;gt;= 7, update NEXT_ELIGIBLE_DATE to [today + days remaining]
  3. If LAST_CONVERSATION is within 7 days, update NEXT_ELIGIBLE_DATE
  4. Campaign hopper filter: exclude leads where NEXT_ELIGIBLE_DATE &amp;gt; today
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Implementation script (pseudo-logic):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run every hour via cron&lt;/span&gt;
&lt;span class="c"&gt;# For each active debt in the campaign:&lt;/span&gt;
&lt;span class="c"&gt;#   Count calls in last 7 days from vicidial_log&lt;/span&gt;
&lt;span class="c"&gt;#   If count &amp;gt;= 7: set lead status to FREQ_HOLD&lt;/span&gt;
&lt;span class="c"&gt;#   If last conversation &amp;lt; 7 days ago: set lead status to CONV_HOLD&lt;/span&gt;
&lt;span class="c"&gt;#   If neither: set lead status back to active&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Call Time Restrictions
&lt;/h3&gt;

&lt;p&gt;The FDCPA prohibits collection calls before &lt;strong&gt;8:00 AM&lt;/strong&gt; and after &lt;strong&gt;9:00 PM&lt;/strong&gt; in the debtor's local time. This matches the TSR, but for collections there's no flexibility.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Campaign Settings:
  Local Call Time: 8am-9pm
  Timezone Method: POSTAL (use debtor's address for timezone)
  Fallback Timezone: PHONE (area code — if no address data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Convenience exception:&lt;/strong&gt; A collector can call outside 8-9 hours if the consumer has specifically consented to being contacted at that time. Track this with a custom field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONVENIENCE_TIME: VARCHAR (e.g., "weekdays 7am OK" or "Saturdays 10am-2pm")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Skip Tracing Integration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why Skip Tracing Matters for Collections
&lt;/h3&gt;

&lt;p&gt;The industry-standard right-party contact (RPC) rate for collections is approximately 25%. That means 75% of your call attempts never reach the person who owes the debt. Skip tracing — the process of finding current contact information for consumers — directly attacks this problem.&lt;/p&gt;

&lt;p&gt;Modern skip tracing vendors report RPC rate improvements of 35% or higher when agencies use multi-source skip tracing. Some platforms, like BatchSkipTracing, report 76% RPC rates — nearly 3x the industry standard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skip Tracing Vendors and Integration
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vendor&lt;/th&gt;
&lt;th&gt;Strength&lt;/th&gt;
&lt;th&gt;Integration&lt;/th&gt;
&lt;th&gt;Cost Model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TLOxp (TransUnion)&lt;/td&gt;
&lt;td&gt;Full consumer data coverage&lt;/td&gt;
&lt;td&gt;Batch API&lt;/td&gt;
&lt;td&gt;Per-lookup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LexisNexis Accurint&lt;/td&gt;
&lt;td&gt;Deep public records&lt;/td&gt;
&lt;td&gt;API + batch&lt;/td&gt;
&lt;td&gt;Per-lookup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tracers&lt;/td&gt;
&lt;td&gt;Cost-effective, API-first&lt;/td&gt;
&lt;td&gt;REST API&lt;/td&gt;
&lt;td&gt;Per-lookup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IDI (Idicore)&lt;/td&gt;
&lt;td&gt;Real-time data refresh&lt;/td&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;Per-lookup + subscription&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Experian Skip Tracing&lt;/td&gt;
&lt;td&gt;Credit data linkage&lt;/td&gt;
&lt;td&gt;Batch&lt;/td&gt;
&lt;td&gt;Per-lookup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BatchSkipTracing&lt;/td&gt;
&lt;td&gt;High RPC rate, bulk processing&lt;/td&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;Per-batch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  VICIdial Skip Trace Workflow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: New account loaded into VICIdial
  → Initial phone number from creditor file

Step 2: First pass dialing (creditor-provided number)
  → If RPC: proceed with collection
  → If wrong number / disconnected / no answer after 3 attempts:
    flag for skip trace

Step 3: Skip trace batch
  → Send flagged records to skip trace vendor via API
  → Receive appended phone numbers, addresses, email
  → Load new phone numbers as alternate contacts in VICIdial

Step 4: Second pass dialing (skip-traced numbers)
  → Dial new numbers with fresh attempt count
  → Higher RPC rate expected on skip-traced numbers

Step 5: Ongoing skip trace refresh
  → Re-skip accounts every 30-60 days if still unresolved
  → Consumer data changes frequently — refreshing improves RPC over time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Loading Skip Trace Results into VICIdial
&lt;/h3&gt;

&lt;p&gt;When skip trace results return multiple phone numbers per debtor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API Call: /vicidial/non_agent_api.php
Function: update_lead
Parameters:
  lead_id: [existing lead]
  phone_number: [best phone from skip trace]
  alt_phone: [second phone]
  address3: [third phone]  (VICIdial workaround for additional phones)
  custom_7: SKIP_TRACE_DATE = [today]
  custom_8: SKIP_TRACE_SOURCE = [vendor name]
  custom_9: SKIP_TRACE_CONFIDENCE = [confidence score]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;FDCPA compliance for skip-traced numbers:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When calling a skip-traced number, the agent must first verify they're speaking with the correct person BEFORE disclosing the debt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Hello, may I speak with [debtor_first_name] [debtor_last_name]?"

IF CORRECT PERSON:
  → Proceed with Mini-Miranda and collection conversation

IF NOT CORRECT PERSON:
  → "I apologize, I may have the wrong number. Thank you."
  → Disposition as WRONG_NUMBER
  → DO NOT mention the debt, the agency, or any account details
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is critical — disclosing the existence of a debt to a third party is an FDCPA violation. On a skip-traced number where you're not certain who will answer, identity verification comes FIRST.&lt;/p&gt;




&lt;h2&gt;
  
  
  Right-Party Contact and Mini-Miranda
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Mini-Miranda Delivery
&lt;/h3&gt;

&lt;p&gt;The FDCPA requires that in the initial communication with the consumer, and in all subsequent communications, the collector must disclose:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;That the communication is from a debt collector&lt;/li&gt;
&lt;li&gt;That the purpose is to collect a debt&lt;/li&gt;
&lt;li&gt;That any information obtained will be used for that purpose&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Agent script with Mini-Miranda:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IDENTITY VERIFICATION:
"Hello, may I speak with [debtor_name]?"

IF CONFIRMED:
"[debtor_name], this is [agent_name] with [Agency Name]. I'm calling
regarding an account with [Creditor Name]. This is an attempt to
collect a debt, and any information obtained will be used for that
purpose. This call may be recorded."

"The balance on this account is [amount]. Are you in a position to
resolve this today?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  VICIdial Script Enforcement
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Script: COLLECTION_CALL
  Required Checkboxes (must be checked before any disposition):
  [ ] Verified identity of debtor before disclosing any information
  [ ] Delivered Mini-Miranda disclosure
  [ ] Stated recording notification (if applicable)
  [ ] Did NOT disclose debt to any third party

  Script blocks disposition until all checkboxes are checked.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Third-Party Disclosure Prevention
&lt;/h3&gt;

&lt;p&gt;Build safeguards into the VICIdial workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Voicemail messages:&lt;/strong&gt; Never leave a voicemail that mentions the debt, the creditor, or that the call is from a debt collector. Compliant VM:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"This is [agent_name] with [Agency Name] calling for [debtor_name].
Please call us back at [number] regarding an important business matter."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Answering machine detection:&lt;/strong&gt; When AMD detects a machine, the voicemail drop must be compliant. Review your voicemail audio file carefully — it must not contain debt-related language.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Third-party contacts:&lt;/strong&gt; If someone other than the debtor answers (roommate, spouse, child), the agent can ONLY:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask to speak with the debtor&lt;/li&gt;
&lt;li&gt;Leave a message asking the debtor to call back&lt;/li&gt;
&lt;li&gt;NOT reveal the debt, the creditor, or the purpose of the call&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Multi-Client Portfolio Management
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Client Isolation for Collection Agencies
&lt;/h3&gt;

&lt;p&gt;Most third-party collection agencies handle accounts from multiple creditor clients. Each client's accounts must be isolated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Campaign Architecture:
  Campaign: CLIENT_BANK_A
    List IDs: 300001-300999
    DNC List: BANK_A_DNC
    Caller ID: [Bank A's preferred caller ID or agency generic]
    Script: BANK_A_COLLECTION
    Custom Dispositions: [Bank A's required disposition set]

  Campaign: CLIENT_BANK_B
    List IDs: 400001-400999
    DNC List: BANK_B_DNC
    Caller ID: [Bank B's preferred caller ID]
    Script: BANK_B_COLLECTION
    Custom Dispositions: [Bank B's required disposition set]

  Campaign: CLIENT_MEDICAL
    List IDs: 500001-500999
    DNC List: MEDICAL_DNC
    Caller ID: [Agency caller ID — not medical provider]
    Script: MEDICAL_COLLECTION (HIPAA-aware)
    Additional Compliance: HIPAA Business Associate Agreement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creditor-Specific Requirements
&lt;/h3&gt;

&lt;p&gt;Each creditor may have requirements beyond FDCPA/Reg F:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Bank A&lt;/th&gt;
&lt;th&gt;Bank B&lt;/th&gt;
&lt;th&gt;Medical&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Max call attempts/day&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max call attempts/week&lt;/td&gt;
&lt;td&gt;7 (Reg F)&lt;/td&gt;
&lt;td&gt;5 (stricter)&lt;/td&gt;
&lt;td&gt;3 (stricter)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Calling hours&lt;/td&gt;
&lt;td&gt;8-9 consumer&lt;/td&gt;
&lt;td&gt;9-8 (stricter)&lt;/td&gt;
&lt;td&gt;9-7 (stricter)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Settlement authority&lt;/td&gt;
&lt;td&gt;Up to 60%&lt;/td&gt;
&lt;td&gt;Up to 50%&lt;/td&gt;
&lt;td&gt;Up to 40%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment plan minimum&lt;/td&gt;
&lt;td&gt;$50/month&lt;/td&gt;
&lt;td&gt;$25/month&lt;/td&gt;
&lt;td&gt;$25/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recording disclosure&lt;/td&gt;
&lt;td&gt;Required in all states&lt;/td&gt;
&lt;td&gt;Two-party states only&lt;/td&gt;
&lt;td&gt;Required in all states&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reporting frequency&lt;/td&gt;
&lt;td&gt;Weekly&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;Weekly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Build each creditor's specific requirements into their campaign configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Collection Workflow in VICIdial
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Disposition Codes for Collections
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPC     — Right-party contact (spoke with debtor)
PTP     — Promise to pay (amount and date recorded)
PTPFULL — Promise to pay in full
PTPPART — Promise to pay partial / payment plan
PAID    — Payment received during call
SETTLED — Settlement agreed upon
DISPUTE — Debtor disputes the debt (trigger validation notice)
CEASE   — Cease and desist requested (add to DNC immediately)
HARDSHIP — Financial hardship (reduced payment plan)
SKIP    — Number is wrong / debtor not at this number
DISC    — Number disconnected
EMPLOYER — Called debtor's workplace (document carefully)
REF     — Refused to discuss
ATTY    — Debtor represented by attorney (cease contact, contact attorney only)
BKRPT   — Debtor filed bankruptcy (cease all collection activity)
DECEASED — Debtor is deceased (handle per state probate rules)
NA      — No Answer
AM      — Answering Machine (compliant VM left)
BZ      — Busy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Payment Processing
&lt;/h3&gt;

&lt;p&gt;When a debtor agrees to pay during the call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payment Workflow:
  1. Agent confirms amount and payment method
  2. Agent presses hotkey to pause recording (PCI compliance)
  3. Agent enters payment in separate secure payment portal
  4. Payment portal returns confirmation number
  5. Agent resumes recording
  6. Agent reads confirmation number and payment summary
  7. Agent dispositions as PAID or PTP with payment date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Promise-to-Pay Follow-Up
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Campaign: PTP_FOLLOWUP
Dial Method: RATIO at 1.0
Trigger: Day before promised payment date
Local Call Time: 9am-7pm
Agent Script: PTP_REMINDER
Max Attempts: 2

Script:
"Hi [debtor_name], this is [agent] from [Agency Name] regarding your
account with [Creditor]. I'm calling as a courtesy reminder about
your payment arrangement of [amount] due [date]. Will you be able
to make that payment as discussed?"

IF YES: "Thank you. We'll confirm receipt once it's processed."
IF NO: "I understand. Can we discuss a modified arrangement?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Performance Metrics for Collections
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Key Metrics
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Industry Average&lt;/th&gt;
&lt;th&gt;Top Performer&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Right-Party Contact Rate&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;td&gt;50-76%&lt;/td&gt;
&lt;td&gt;Skip tracing quality is the primary driver&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Promise-to-Pay Rate&lt;/td&gt;
&lt;td&gt;15-20% of RPC&lt;/td&gt;
&lt;td&gt;25-35% of RPC&lt;/td&gt;
&lt;td&gt;Agent skill and scripting matter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PTP Kept Rate&lt;/td&gt;
&lt;td&gt;60-70%&lt;/td&gt;
&lt;td&gt;75-85%&lt;/td&gt;
&lt;td&gt;Follow-up campaign improves this&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dollars Collected/Agent Hour&lt;/td&gt;
&lt;td&gt;$50-$150&lt;/td&gt;
&lt;td&gt;$200-$400&lt;/td&gt;
&lt;td&gt;Varies by portfolio type and balance size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost Per Dollar Collected&lt;/td&gt;
&lt;td&gt;$0.08-$0.15&lt;/td&gt;
&lt;td&gt;$0.05-$0.08&lt;/td&gt;
&lt;td&gt;Technology + agent efficiency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compliance Violations&lt;/td&gt;
&lt;td&gt;Industry problem&lt;/td&gt;
&lt;td&gt;Zero target&lt;/td&gt;
&lt;td&gt;One violation can cost millions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  VICIdial Reports for Collections
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Right-Party Contact Rate by List/Portfolio&lt;/strong&gt;&lt;br&gt;
Track RPC rate segmented by creditor client, list age, and skip trace status. Identify which portfolios need skip trace refreshes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Promise-to-Pay and Fulfillment Report&lt;/strong&gt;&lt;br&gt;
Track PTP volume, PTP amounts, and fulfillment rates by agent and portfolio. Agents with high PTP but low fulfillment rates may be making unrealistic arrangements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Frequency Cap Compliance Report&lt;/strong&gt;&lt;br&gt;
Daily report showing any accounts approaching the 7-in-7 limit. Flag accounts at 5 or 6 attempts for supervisor review before the 7th call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Cease-and-Desist / Revocation Report&lt;/strong&gt;&lt;br&gt;
Track all consent revocations and cease-and-desist requests. Verify they were processed within 48 hours. This is your audit trail for TCPA compliance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Dollars Collected Dashboard&lt;/strong&gt;&lt;br&gt;
Real-time and daily tracking of collections by agent, team, campaign, and creditor client. This is the metric that drives agency profitability.&lt;/p&gt;


&lt;h2&gt;
  
  
  Call Recording and Retention
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Recording Requirements
&lt;/h3&gt;

&lt;p&gt;Collections calls must be recorded for compliance documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Campaign Settings:
  Campaign Recording: ALLFORCE
  Recording Format: WAV (highest quality for regulatory review)
  Recording Storage: /encrypted/recordings/collections/[client]/
  Encryption: AES-256 at rest
  Retention: Minimum 3 years from last collection activity per account
             (per Reg F record retention requirements)
  Backup: Offsite backup with same retention and encryption
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  State Recording Consent
&lt;/h3&gt;

&lt;p&gt;Remember that recording consent requirements vary by state. In two-party consent states, the agent must disclose recording at the beginning of every call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Recording Disclosure (played automatically or read by agent):
"This call may be monitored or recorded for quality assurance
and compliance purposes."

Two-Party Consent States (2026):
CA, CT, DE, FL, IL, MD, MA, MI, MT, NH, NV, PA, WA
(verify current list — some states have updated requirements)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Infrastructure for Collections Agencies
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Server Sizing
&lt;/h3&gt;

&lt;p&gt;Collections operations have unique infrastructure patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High call volume per agent (aggressive recycling)&lt;/li&gt;
&lt;li&gt;Large recording storage (all calls recorded, 3+ year retention)&lt;/li&gt;
&lt;li&gt;Complex reporting queries (per-debt frequency tracking)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Recommended architecture for 50-100 seat collections agency:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server 1: VICIdial Web + Admin
Server 2: Asterisk Telephony (handles 200+ concurrent channels)
Server 3: MySQL Database (SSD storage for fast frequency queries)
Server 4: Recording Storage (NAS with 10+ TB for 3-year retention)
Optional: Read replica for reporting queries (prevents report load
         from affecting production dialing)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trunk Configuration
&lt;/h3&gt;

&lt;p&gt;Collections agencies should use SIP trunks with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;STIR/SHAKEN attestation (Level A)&lt;/li&gt;
&lt;li&gt;CNAM registration with agency name or neutral business name&lt;/li&gt;
&lt;li&gt;Multiple DID pools (rotate to prevent flagging)&lt;/li&gt;
&lt;li&gt;At least 2 SIP providers (redundancy for production operations)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you're running a collections agency on VICIdial and need help with Regulation F compliance, skip tracing integration, frequency cap tracking, or multi-client architecture — &lt;a href="https://vicistack.com" rel="noopener noreferrer"&gt;ViciStack&lt;/a&gt; builds and manages production VICIdial deployments for ARM operations. We'll increase your right-party contact rate and dollars collected per agent hour within two weeks. $5,000 flat — $1,000 down, $4,000 on results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vicistack.com/contact/" rel="noopener noreferrer"&gt;Talk to us about your collections operation&lt;/a&gt;&lt;/p&gt;

</description>
      <category>voip</category>
      <category>asterisk</category>
      <category>sysadmin</category>
      <category>devops</category>
    </item>
    <item>
      <title>Your Contact Rate Is 15%. It Should Be 25%. Here's the Operations Playbook.</title>
      <dc:creator>Jason Shouldice</dc:creator>
      <pubDate>Sat, 28 Mar 2026 13:05:28 +0000</pubDate>
      <link>https://dev.to/gamlin/your-contact-rate-is-15-it-should-be-25-heres-the-operations-playbook-ghl</link>
      <guid>https://dev.to/gamlin/your-contact-rate-is-15-it-should-be-25-heres-the-operations-playbook-ghl</guid>
      <description>&lt;p&gt;Contact rate sits upstream of every other number that matters in an outbound operation. If you cannot get humans on the phone, close rate is irrelevant, talk time is irrelevant, and your CRM integration is decorative.&lt;/p&gt;

&lt;p&gt;Most outbound operations run 15-25% daily contact rates on fresh, healthy lists. That means 75-85% of dialing effort produces nothing. The difference between 15% and 25% on the same list is the difference between profitability and payroll that does not cover itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure Before You Touch Anything
&lt;/h2&gt;

&lt;p&gt;Pull three numbers from your current operation. Raw answer rate: calls answered divided by total calls placed. Right-party contact rate: live conversations with the intended person divided by total leads attempted. Qualified contact rate: meaningful conversations where the agent got through the qualifying script.&lt;/p&gt;

&lt;p&gt;Then pull per-DID performance. Most operations never look at contact rate by individual phone number. A single spam-labeled DID in your rotation can drag your entire campaign down 20-50% overnight. Sort ascending by answer rate. The DIDs at the bottom -- the ones running 3-5% when your average is 18% -- are almost certainly spam-labeled. Pull them from rotation immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three-Layer DNC Scrub
&lt;/h2&gt;

&lt;p&gt;DNC scrubbing is not just compliance. It is an optimization tool. Every call to a DNC-registered number is a wasted dial, a wasted agent second, and a potential $43,792 fine.&lt;/p&gt;

&lt;p&gt;Layer 1: Federal DNC Registry. Scrub before every campaign. Layer 2: State DNC lists -- 32 states maintain their own, some stricter than federal. Layer 3: Internal DNC from every opt-out channel feeding into one table. Then validate phone numbers through carrier lookup and strip disconnects, landlines in mobile campaigns, and recently ported numbers. A clean scrub typically removes 15-25% of records, but your contact rate per dial goes up because you stopped dialing dead numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time-of-Day Dialing
&lt;/h2&gt;

&lt;p&gt;The time you place a call affects whether it gets answered more than almost any other variable. B2C sweet spots are typically 10-11 AM and 4-7 PM local time. But these shift by vertical -- solar leads answer better in the afternoon, insurance peaks mid-morning, debt collection works best in early evening.&lt;/p&gt;

&lt;p&gt;Build a time-of-day matrix from your historical data broken into hourly intervals. Compare answer rate and conversion rate side by side, because the hour with the highest answer rate is not always the hour with the highest conversion rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  DID Rotation and Reputation
&lt;/h2&gt;

&lt;p&gt;This is where most operations lose the game in 2026. Perfect data, perfect timing, perfectly tuned dialer -- and 8% answer rates because your numbers are flagged as spam.&lt;/p&gt;

&lt;p&gt;The rules: maximum 50 calls per DID per day, 10-15 per hour. Cool-down period of 14-30 days after labeling, or retire permanently. Maintain 3-5 DIDs per active agent. Enable local presence dialing by matching outbound CID to the area code being dialed -- research shows prospects are nearly 4x more likely to answer local numbers.&lt;/p&gt;

&lt;p&gt;Check reputation weekly using Free Caller Registry, Hiya, TNS Call Guardian, and First Orion. Automate it. Any DID flagged gets disabled immediately.&lt;/p&gt;

&lt;p&gt;STIR/SHAKEN A-level attestation on every outbound DID is non-optional. The contact rate difference between A-level and C-level attestation runs 30-60% in many markets. If your trunk provider cannot guarantee A-level on your numbers, switch providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attempt Cadence
&lt;/h2&gt;

&lt;p&gt;Industry data shows diminishing returns after attempt 5-6. First attempt reaches 15-20%. By the 5th attempt, cumulative contact rate is 40-45%. The incremental gain on attempts 7+ drops below 2%. Cap at 6-8 total attempts spread across 7-14 days, with status-specific retry intervals. No answers retry after 1 hour, then 4 hours, then next day. Busy signals retry after 2 hours. Disconnects never retry.&lt;/p&gt;

&lt;p&gt;For web leads, speed to lead is the biggest factor. Leads contacted within 5 minutes convert at 8x the rate of leads contacted after 30 minutes. Insert them into the hopper with maximum priority for sub-2-minute callback.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Checklist
&lt;/h2&gt;

&lt;p&gt;Three-layer DNC scrub. Carrier lookup validation. Per-DID answer rate monitoring. 50 calls per DID per day max. Local presence dialing. A-level STIR/SHAKEN. Time-of-day windows targeting peak hours. 6-8 attempt cadence over 7-14 days. Real-time API insertion for web leads. Measure baseline, re-measure weekly, kill what does not work.&lt;/p&gt;

&lt;p&gt;If your contact rate is below 15% and you implement all of this, expect 22-28% within 2-3 weeks. A 50-80% improvement in daily conversations on the same list with the same agents.&lt;/p&gt;

&lt;p&gt;Originally published at &lt;a href="https://vicistack.com/blog/contact-rate-optimization-guide/" rel="noopener noreferrer"&gt;https://vicistack.com/blog/contact-rate-optimization-guide/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>voip</category>
      <category>asterisk</category>
      <category>sysadmin</category>
      <category>devops</category>
    </item>
    <item>
      <title>Why ViciStack? 7 Reasons Call Centers Choose Us Over DIY</title>
      <dc:creator>Jason Shouldice</dc:creator>
      <pubDate>Sat, 28 Mar 2026 04:55:15 +0000</pubDate>
      <link>https://dev.to/gamlin/why-vicistack-7-reasons-call-centers-choose-us-over-diy-37cb</link>
      <guid>https://dev.to/gamlin/why-vicistack-7-reasons-call-centers-choose-us-over-diy-37cb</guid>
      <description>&lt;p&gt;You are running VICIdial. You know the software works. But your connect rates are stuck, your IT person is stretched thin, and you have a suspicion that the dialer is not performing anywhere near its potential.&lt;/p&gt;

&lt;p&gt;You have been thinking about hiring someone. Maybe you have looked at freelancers on Upwork. Maybe you have called a couple of VICIdial hosting companies. Maybe you have been putting it off because you have been burned by IT consultants before who promised the moon and delivered a slide deck.&lt;/p&gt;

&lt;p&gt;This is who we are, what we do, and why operations ranging from 10-seat insurance shops to 300-agent BPO floors choose ViciStack. No fluff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 1: We Only Do One Thing
&lt;/h2&gt;

&lt;p&gt;ViciStack does VICIdial optimization. That is it. We do not sell hosting. We do not resell carrier minutes. We do not build CRMs. We do not offer "omnichannel contact center solutions" or whatever buzzword the vendor landscape has cooked up this quarter.&lt;/p&gt;

&lt;p&gt;Every engineer on our team has spent years inside VICIdial's codebase, Asterisk's telephony stack, and the MySQL tables that hold everything together. When you call us with a problem, you are not getting a generalist who has to Google the difference between vicidial_log and vicidial_closer_log. You are getting someone who has seen your exact problem 50 times before and knows which of the 247 configuration parameters needs to change.&lt;/p&gt;

&lt;p&gt;Why this matters to you: the difference between a generalist and a specialist is measured in hours of downtime and thousands of dollars in lost revenue. When your Asterisk process crashes at peak hour, a generalist spends 2-4 hours reading forum posts and trying things. Our team knows the three most likely causes from the error signature and has the process restarted in 15-45 minutes. For a 50-agent floor generating &lt;a href="https://vicistack.com/blog/vicidial-support/" rel="noopener noreferrer"&gt;$2,190/hour in revenue&lt;/a&gt;, that difference is $3,285-$8,213 in recovered revenue per incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 2: The Numbers Speak
&lt;/h2&gt;

&lt;p&gt;We have completed over 100 VICIdial optimization engagements. Here are the aggregate results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Median Before&lt;/th&gt;
&lt;th&gt;Median After&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Connect rate&lt;/td&gt;
&lt;td&gt;3.8%&lt;/td&gt;
&lt;td&gt;7.1%&lt;/td&gt;
&lt;td&gt;+87%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent connects per day&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;+86%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AMD false positive rate&lt;/td&gt;
&lt;td&gt;19%&lt;/td&gt;
&lt;td&gt;4.1%&lt;/td&gt;
&lt;td&gt;-78%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drop rate&lt;/td&gt;
&lt;td&gt;3.4%&lt;/td&gt;
&lt;td&gt;1.8%&lt;/td&gt;
&lt;td&gt;-47%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent utilization&lt;/td&gt;
&lt;td&gt;58%&lt;/td&gt;
&lt;td&gt;76%&lt;/td&gt;
&lt;td&gt;+18 points&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DID answer rate&lt;/td&gt;
&lt;td&gt;4.2%&lt;/td&gt;
&lt;td&gt;9.8%&lt;/td&gt;
&lt;td&gt;+133%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are medians, not cherrypicked outliers. The 25th percentile client (worst quartile of our results) still sees a 52% improvement in connect rate. The 75th percentile sees 124%.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://dev.to/blog/vicidial-optimization-results/"&gt;full results breakdown&lt;/a&gt; covers data by industry, operation size, and optimization area. The &lt;a href="https://dev.to/blog/vicidial-roi-case-study/"&gt;ROI case study&lt;/a&gt; provides a day-by-day walkthrough of a 45-agent insurance center that went from 3.2% to 7.1% connect rate in 14 days -- with every metric documented.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 3: Pay on Results, Not Promises
&lt;/h2&gt;

&lt;p&gt;Our pricing model is designed around one principle: you should not pay for work that does not produce results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The free audit.&lt;/strong&gt; Our &lt;a href="https://dev.to/blog/vicidial-free-audit-what-to-expect/"&gt;247-point diagnostic&lt;/a&gt; costs you nothing. We analyze your entire VICIdial deployment -- server health, MySQL performance, Asterisk configuration, campaign settings, compliance posture, security vulnerabilities -- and deliver a detailed report within 24 hours. You get specific, actionable findings with estimated revenue impact. If you want to fix everything yourself, the report is yours to keep. No obligation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The $5,000 engagement.&lt;/strong&gt; If the audit reveals optimization opportunities (it always does), the paid engagement is structured as: $1,000 down, $4,000 on completion. The $4,000 is due when we deliver measurable improvement. Not when we say we are done. When the numbers prove it.&lt;/p&gt;

&lt;p&gt;Over 100 engagements, we have never had a client refuse the completion payment. Not because we have great lawyers -- because the results are unambiguous. When your connect rate goes from 3.8% to 7.1%, the improvement is visible in every report your call center produces.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ongoing management: $1,500/month.&lt;/strong&gt; For operations that want continuous optimization rather than a one-time tune-up. This includes 24/7 monitoring, weekly DID management, monthly AMD tuning, compliance updates, security patching, and priority support with a 15-minute response SLA.&lt;/p&gt;

&lt;p&gt;Compare that to the alternatives:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;th&gt;What You Get&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Full-time VICIdial admin&lt;/td&gt;
&lt;td&gt;$10,000-$14,000&lt;/td&gt;
&lt;td&gt;One person, business hours only, no backup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Freelancer (10-15 hrs/month)&lt;/td&gt;
&lt;td&gt;$1,500-$3,000&lt;/td&gt;
&lt;td&gt;Variable quality, no SLA, no monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ViciStack&lt;/td&gt;
&lt;td&gt;$1,500&lt;/td&gt;
&lt;td&gt;Specialized team, 24/7 monitoring, 15-min SLA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.platform28.com/blog/five9-pricing-breakdown" rel="noopener noreferrer"&gt;Five9 equivalent&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;$7,950+ (50 seats at $159/seat)&lt;/td&gt;
&lt;td&gt;Hosted platform, 36-month contract, 50-seat minimum&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Five9 comparison is worth noting. At 50 agents, Five9's Core plan costs $7,950/month just for the software license. That is before per-minute telecom charges, add-on features, and CRM integration fees. ViciStack's ongoing management at $1,500/month is 19% of the Five9 license cost -- and you are running on your own infrastructure with no seat minimums, no 36-month lock-in, and no auto-renewal traps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 4: 15-Minute Response SLA
&lt;/h2&gt;

&lt;p&gt;VICIdial downtime costs money. A lot of money.&lt;/p&gt;

&lt;p&gt;A 50-agent floor generates approximately $2,190 in revenue per hour during peak dialing. When your system goes down, every minute counts. Here is what response time looks like across different support options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Support Source&lt;/th&gt;
&lt;th&gt;Typical Response Time&lt;/th&gt;
&lt;th&gt;Revenue Lost (50 agents, 4-hour outage)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VICIdial community forum&lt;/td&gt;
&lt;td&gt;4-8 hours&lt;/td&gt;
&lt;td&gt;$8,760-$17,520&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Freelance consultant&lt;/td&gt;
&lt;td&gt;2-12 hours&lt;/td&gt;
&lt;td&gt;$4,380-$26,280&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In-house admin (during business hours)&lt;/td&gt;
&lt;td&gt;15-60 min&lt;/td&gt;
&lt;td&gt;$730-$2,190&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In-house admin (after hours)&lt;/td&gt;
&lt;td&gt;1-8 hours&lt;/td&gt;
&lt;td&gt;$2,190-$17,520&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ViciStack&lt;/td&gt;
&lt;td&gt;Under 15 minutes&lt;/td&gt;
&lt;td&gt;$365-$730&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Our 15-minute response SLA means a real human who knows your system picks up or responds within 15 minutes, 24 hours a day, 7 days a week. Not a ticket acknowledgment. Not an auto-reply. An engineer who can SSH into your server and start diagnosing.&lt;/p&gt;

&lt;p&gt;We can do this because we are not a general-purpose MSP juggling 400 clients across every technology stack. We run VICIdial environments. The diagnostic process is systematized. The common failure modes are documented. When your Asterisk process segfaults, we do not start from scratch -- we start from a decision tree built from thousands of prior incidents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 5: We Catch the Silent Killers
&lt;/h2&gt;

&lt;p&gt;The problems that cost you the most money are the ones you do not know about.&lt;/p&gt;

&lt;p&gt;A server crash is loud. You notice immediately. But the issues that quietly drain your revenue over weeks and months? Those only surface when someone is actively monitoring for them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DID reputation degradation.&lt;/strong&gt; Your caller IDs get flagged as spam by carrier databases. Answer rates drop from 9% to 3% over 6-8 weeks. No alarm goes off. No error message appears. Your agents make the same number of dials but get fewer answers. By the time someone notices the trend in a monthly report, you have burned through your entire DID pool and lost hundreds of thousands in potential revenue.&lt;/p&gt;

&lt;p&gt;We check DID health weekly. When a number drops below our threshold, it gets investigated and rotated before the damage compounds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AMD drift.&lt;/strong&gt; Call patterns change seasonally. Carrier routing changes. Voicemail systems get updated. The AMD parameters that were perfect in January may produce 12% false positives by June. Without monthly recalibration, you slowly lose connections without realizing it.&lt;/p&gt;

&lt;p&gt;We recalibrate AMD monthly using actual call data, not guesswork.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compliance exposure.&lt;/strong&gt; TCPA rules change. State laws change. Your drop rate creeps up after a configuration adjustment. Your DNC list expires and nobody runs a fresh scrub. These are not hypothetical risks -- &lt;a href="https://prospeo.io/s/tcpa-violations-penalties" rel="noopener noreferrer"&gt;2,788 TCPA cases were filed in 2024&lt;/a&gt;, up 67% from the prior year. The average class action settlement is $6.6 million. One compliance slip can cost more than your entire annual revenue.&lt;/p&gt;

&lt;p&gt;We monitor compliance continuously and update your configuration same-day when regulations change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security vulnerabilities.&lt;/strong&gt; CVE-2024-8503 and CVE-2024-8504 gave attackers unauthenticated root access to VICIdial servers. Exploit code was published publicly. Servers were compromised within hours. If your system was not patched from SVN revision 3848, it is vulnerable right now.&lt;/p&gt;

&lt;p&gt;We apply security patches within 48 hours of disclosure. Not 48 days. Not "whenever we get to it."&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 6: Built From 100+ Engagements
&lt;/h2&gt;

&lt;p&gt;Every VICIdial installation is different. But the problems are not.&lt;/p&gt;

&lt;p&gt;After optimizing over 100 call centers, we have built a playbook that covers every common (and most uncommon) VICIdial configuration issue. That playbook translates into faster diagnosis, more precise tuning, and fewer surprises.&lt;/p&gt;

&lt;p&gt;Specific things we bring that no single admin or freelancer can match:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-demographic AMD profiles.&lt;/strong&gt; We have calibrated AMD parameters for Medicare (65+), insurance (35-55), debt collection (25-45), solar (homeowners 30-60), and political (general population) demographics. These are not guesses -- they are derived from analyzing millions of call disposition records across our client base. Here is what the difference looks like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Default VICIdial AMD settings (wrong for everyone):&lt;/span&gt;
&lt;span class="na"&gt;initial_silence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2500&lt;/span&gt;
&lt;span class="na"&gt;after_greeting_silence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;800&lt;/span&gt;
&lt;span class="na"&gt;greeting&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1500&lt;/span&gt;
&lt;span class="na"&gt;silence_threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;256&lt;/span&gt;

&lt;span class="c1"&gt;# ViciStack Medicare (65+) profile:&lt;/span&gt;
&lt;span class="na"&gt;initial_silence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3200&lt;/span&gt;
&lt;span class="na"&gt;after_greeting_silence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1100&lt;/span&gt;
&lt;span class="na"&gt;greeting&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2000&lt;/span&gt;
&lt;span class="na"&gt;silence_threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;220&lt;/span&gt;

&lt;span class="c1"&gt;# ViciStack Debt Collection (25-45) profile:&lt;/span&gt;
&lt;span class="na"&gt;initial_silence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2600&lt;/span&gt;
&lt;span class="na"&gt;after_greeting_silence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;850&lt;/span&gt;
&lt;span class="na"&gt;greeting&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1600&lt;/span&gt;
&lt;span class="na"&gt;silence_threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;250&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Medicare profile gives the older demographic more time to answer and produces 60-70% fewer false positives than defaults. The debt collection profile is tighter because the demographic answers faster but screens more aggressively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DID vendor relationships.&lt;/strong&gt; We know which DID providers deliver consistently clean numbers and which sell recycled numbers that get flagged within days. We know which carriers provide A-level STIR/SHAKEN attestation and which provide B or C level. This saves you the trial-and-error process of testing vendors yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benchmark data.&lt;/strong&gt; When we tell you that your 4.2% connect rate is below the median for a 50-agent insurance operation, that is not an opinion. It is a statistic derived from our client base. When we tell you that 7.1% is achievable, it is because we have achieved it repeatedly in operations similar to yours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure pattern recognition.&lt;/strong&gt; We have seen every way VICIdial can break. MySQL table corruption on the vicidial_log table. Asterisk segfaults from SIP stack overflow. SVN upgrades that break the agent interface. Carrier deregistration during maintenance windows. Each failure has a documented diagnosis and resolution path.&lt;/p&gt;

&lt;p&gt;For example, when a client reports "agents cannot log in" at 8 AM on a Monday, our diagnostic path looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1: Check if Asterisk is running&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"core show channels"&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;

&lt;span class="c"&gt;# Step 2: Check if all VICIdial screens are running&lt;/span&gt;
screen &lt;span class="nt"&gt;-ls&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;AST_

&lt;span class="c"&gt;# Step 3: Check MySQL for connection exhaustion&lt;/span&gt;
mysql &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SHOW STATUS LIKE 'Threads_connected';"&lt;/span&gt;
mysql &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SHOW VARIABLES LIKE 'max_connections';"&lt;/span&gt;

&lt;span class="c"&gt;# Step 4: Check disk space (kills cron jobs silently)&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; / /var /srv 2&amp;gt;/dev/null

&lt;span class="c"&gt;# Step 5: Check the VD_auto_dialer and AST_update processes&lt;/span&gt;
ps aux | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"VD_auto_dialer|AST_update|ADMIN_keepalive"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our experience, 80% of "agents cannot log in" issues are one of: Asterisk crashed (step 1), the AST_update or VD_auto_dialer cron died (steps 2/5), MySQL hit max_connections (step 3), or disk filled up (step 4). We get through this checklist in under 5 minutes. That institutional knowledge is worth more than any single engineer's experience, no matter how talented they are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 7: No Lock-In, No BS
&lt;/h2&gt;

&lt;p&gt;We do not require contracts. We do not have auto-renewal clauses. We do not hold your data hostage.&lt;/p&gt;

&lt;p&gt;The free audit is free whether you hire us or not. The $5,000 engagement is pay-on-results. The ongoing management is month-to-month -- you can cancel with 30 days notice, and we hand over every configuration document, monitoring script, and performance baseline we have built for your environment.&lt;/p&gt;

&lt;p&gt;If we stop delivering value, you should stop paying us. The fact that our average ongoing client retention is over 18 months tells you that most clients see the value clearly enough to stay.&lt;/p&gt;

&lt;p&gt;Compare that to the typical vendor experience:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Five9&lt;/strong&gt;: 36-month contracts with same-length auto-renewal. Miss the cancellation window by one day and you are locked for another three years.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Convoso&lt;/strong&gt;: Annual contracts with 60-day cancellation notice requirements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freelancers&lt;/strong&gt;: No contract usually means no commitment on their side either. They can disappear, raise rates, or deprioritize you whenever a bigger client comes along.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We operate on the belief that the best retention strategy is making it easy to leave but delivering results that make you not want to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who ViciStack is Not For
&lt;/h2&gt;

&lt;p&gt;Transparency cuts both ways. Here is who should not hire us:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Operations under 10 agents.&lt;/strong&gt; We can help, but the ROI is thinner at small scale. A 5-agent operation might gain $3,000-$5,000/month from optimization. That still beats our $1,500/month fee, but the margin is not as dramatic as larger operations. For very small teams, our $5,000 one-time engagement without ongoing management is usually the right move.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Operations moving off VICIdial.&lt;/strong&gt; If you have already decided to migrate to Five9, Convoso, or another platform, we are not going to talk you out of it. Optimizing a system you are planning to replace in 6 months is not a good investment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Operations with working expert staff.&lt;/strong&gt; If you have a full-time VICIdial engineer who monitors DID health weekly, tunes AMD monthly, patches within days of disclosure, and keeps your connect rate above 7% -- you do not need us. Send that person our &lt;a href="https://dev.to/blog/"&gt;VICIdial optimization articles&lt;/a&gt; for additional ideas, but your money is better spent elsewhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Operations that need a hosted platform.&lt;/strong&gt; We optimize self-hosted VICIdial. We do not provide hosting. If you want someone else to manage the infrastructure end-to-end (servers, networking, hardware), look at a managed hosting provider first, then consider us for the optimization layer on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Get Started
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Free audit.&lt;/strong&gt; &lt;a href="https://dev.to/free-audit/"&gt;Request it here&lt;/a&gt;. We need SSH read-only access to your VICIdial server. Setup takes 10 minutes. Report arrives within 24 hours. No credit card, no commitment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Review the report.&lt;/strong&gt; We will schedule a 30-minute call to walk through the findings if you want. Or you can just read the PDF. Either way, you will know exactly what is wrong, what it is costing you, and what fixing it looks like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Decide.&lt;/strong&gt; Fix it yourself (25% of audit clients do), hire us for the $5,000 engagement (most common), or sign up for ongoing management. No pressure, no deadline, no expiring offers.&lt;/p&gt;

&lt;p&gt;The only thing we would ask is that you do not wait another month while your DIDs get more flagged, your AMD kills more live connections, and your agents sit idle while the dialer underperforms.&lt;/p&gt;

&lt;p&gt;Every day at 4% connect rate instead of 7% is money you do not get back.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/free-audit/"&gt;Get your free audit&lt;/a&gt; | Call &lt;a href="tel:+13432042353"&gt;343-204-2353&lt;/a&gt;&lt;/p&gt;

</description>
      <category>voip</category>
      <category>asterisk</category>
      <category>sysadmin</category>
      <category>devops</category>
    </item>
    <item>
      <title>Install VICIdial in 15 Minutes: The Fastest Path From Zero to Working Dialer</title>
      <dc:creator>Jason Shouldice</dc:creator>
      <pubDate>Sat, 28 Mar 2026 04:55:13 +0000</pubDate>
      <link>https://dev.to/gamlin/install-vicidial-in-15-minutes-the-fastest-path-from-zero-to-working-dialer-5g8m</link>
      <guid>https://dev.to/gamlin/install-vicidial-in-15-minutes-the-fastest-path-from-zero-to-working-dialer-5g8m</guid>
      <description>&lt;p&gt;&lt;strong&gt;Last updated: March 2026 | Reading time: ~14 minutes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every VICIdial installation guide I've found makes the same mistake: it assumes you have an entire weekend to burn. You don't. You want a working dialer, you want it fast, and you want to know which installation path won't blow up at 2 a.m. on launch day.&lt;/p&gt;

&lt;p&gt;This guide covers three methods — ranked by speed — with exact commands, realistic time estimates, and the post-install checklist that gets you from "blank server" to "first test call placed." Pick the one that matches your situation and skip everything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before You Touch a Server: Hardware Requirements
&lt;/h2&gt;

&lt;p&gt;VICIdial runs Asterisk under the hood, and Asterisk is picky about timing. That means bare metal or dedicated hardware. Running VICIdial on a $5/month VPS will produce choppy audio, MeetMe conference errors, and agents who can't hear anything. Don't do it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Minimum specs (5–15 agents):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Minimum&lt;/th&gt;
&lt;th&gt;Recommended&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;4 cores, 2.4 GHz&lt;/td&gt;
&lt;td&gt;8 cores, 3.0 GHz&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAM&lt;/td&gt;
&lt;td&gt;8 GB&lt;/td&gt;
&lt;td&gt;16 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;200 GB SSD (mandatory)&lt;/td&gt;
&lt;td&gt;500 GB NVMe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network&lt;/td&gt;
&lt;td&gt;100 Mbps&lt;/td&gt;
&lt;td&gt;1 Gbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;ViciBox 12, AlmaLinux 9, Rocky 9&lt;/td&gt;
&lt;td&gt;ViciBox 12.0.2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Scaling rule of thumb:&lt;/strong&gt; budget one server per 25 agents. Beyond that, you need a cluster — one database/web server plus dedicated Asterisk boxes. Our &lt;a href="https://dev.to/blog/vicidial-cluster-guide"&gt;cluster configuration guide&lt;/a&gt; walks through the multi-server topology.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What about cloud?&lt;/strong&gt; AWS, Hetzner, and OVH all work if you use dedicated instances (not shared VPS). AWS &lt;code&gt;c5.xlarge&lt;/code&gt; or Hetzner's dedicated EX-series are solid starting points. Expect $100–$350/month for a single production server. Our &lt;a href="https://dev.to/blog/vicidial-cloud-deployment"&gt;cloud deployment guide&lt;/a&gt; covers provider-specific setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: ViciBox ISO (15 Minutes)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; First-timers, single-server deployments, people who just want it to work.&lt;/p&gt;

&lt;p&gt;ViciBox is the official turnkey distribution. It bundles OpenSUSE, Asterisk, MariaDB, Apache, and all the VICIdial application code into a single ISO. Download it, boot it, answer five questions, and you have a running dialer. It is the fastest path to a working system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Download the ISO
&lt;/h3&gt;

&lt;p&gt;Grab ViciBox 12.0.2 (the current stable release) from the official download server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://download.vicidial.com/iso/vicibox/server/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burn it to a USB drive with &lt;code&gt;dd&lt;/code&gt;, Rufus (Windows), or balenaEtcher. For a VM test lab, just mount the ISO directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Linux/Mac — write ISO to USB (replace /dev/sdX with your drive)&lt;/span&gt;
&lt;span class="nb"&gt;sudo dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vicibox-12.0.2-amd64.iso &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/sdX &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4M &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;progress
&lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Boot and Run Phase 1
&lt;/h3&gt;

&lt;p&gt;Boot from the USB. Select &lt;strong&gt;Install ViciBox&lt;/strong&gt; at the boot menu and press Enter.&lt;/p&gt;

&lt;p&gt;The Phase 1 installer handles the base OS. You'll configure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Installation disk&lt;/strong&gt; — Select the target drive (SSD required). If you have multiple disks, pick the one you want as root.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Root password&lt;/strong&gt; — Set something strong. You'll SSH in with this later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network&lt;/strong&gt; — Set a static IP. DHCP works for testing but you'll regret it in production when your IP changes and every SIP trunk breaks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Phase 1 takes about 5–8 minutes depending on disk speed. When you see the &lt;code&gt;vicibox12:~ #&lt;/code&gt; prompt, remove the USB and reboot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Run Phase 2 (VICIdial Installation)
&lt;/h3&gt;

&lt;p&gt;After reboot, log in as root. The ViciBox post-install wizard starts automatically, or you can trigger it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vicibox-install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script installs all VICIdial components — Asterisk, MariaDB, the web admin panel, AGC scripts, DAHDI timing, sound files — everything. It asks for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MySQL root password&lt;/strong&gt; — pick something and remember it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server IP&lt;/strong&gt; — your static IP from Phase 1&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timezone&lt;/strong&gt; — get this right the first time (see the gotchas section below)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Phase 2 runs for about 5–7 minutes. When it finishes, your web admin panel is live.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Access the Admin Panel
&lt;/h3&gt;

&lt;p&gt;Open a browser and navigate to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;http://YOUR_SERVER_IP/vicidial/welcome.php
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Default admin login: &lt;strong&gt;6666 / 1234&lt;/strong&gt;. Change this immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Total time: ~15 minutes.&lt;/strong&gt; That's not marketing; that's actual wall-clock time on a modern SSD-based server.&lt;/p&gt;

&lt;h3&gt;
  
  
  ViciBox Pros and Cons
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fastest installation path&lt;/td&gt;
&lt;td&gt;Locked to OpenSUSE base&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Official support available&lt;/td&gt;
&lt;td&gt;Less control over OS packages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All components pre-configured&lt;/td&gt;
&lt;td&gt;Harder to customize Asterisk build flags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regular security patches&lt;/td&gt;
&lt;td&gt;Single-server only (cluster needs manual config)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Method 2: Manual Install on AlmaLinux 9 / Rocky Linux 9 (~45–90 Minutes)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Production deployments, teams that want full OS control, anyone who needs custom Asterisk modules.&lt;/p&gt;

&lt;p&gt;CentOS 7 reached end-of-life in June 2024. If you're following a guide that says &lt;code&gt;yum install&lt;/code&gt; on CentOS 7 — stop. AlmaLinux 9 and Rocky Linux 9 are the current RHEL-compatible targets.&lt;/p&gt;

&lt;p&gt;There are two sub-options here: the community auto-installer (faster) or a full manual scratch install (most control).&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A: Community Auto-Installer (~45 Minutes)
&lt;/h3&gt;

&lt;p&gt;The carpenox installer automates the entire process on AlmaLinux 9. It pulls all dependencies, compiles Asterisk, installs DAHDI, checks out VICIdial from SVN, and configures the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set timezone first — critical&lt;/span&gt;
timedatectl set-timezone America/New_York

&lt;span class="c"&gt;# System updates&lt;/span&gt;
dnf check-update
dnf update &lt;span class="nt"&gt;-y&lt;/span&gt;
dnf &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nb"&gt;install &lt;/span&gt;epel-release git
dnf &lt;span class="nb"&gt;install &lt;/span&gt;kernel&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kernel-debug&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="c"&gt;# Disable SELinux (VICIdial doesn't play nice with it)&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/SELINUX=enforcing/SELINUX=disabled/g'&lt;/span&gt; /etc/selinux/config

&lt;span class="c"&gt;# Clone the installer&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /usr/src
git clone https://github.com/carpenox/vicidial-install-scripts.git
&lt;span class="nb"&gt;cd &lt;/span&gt;vicidial-install-scripts

&lt;span class="c"&gt;# Reboot to load new kernel (required for DAHDI)&lt;/span&gt;
reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After reboot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /usr/src/vicidial-install-scripts
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x main-installer.sh
./main-installer.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installer is interactive — it will ask for your server IP, MySQL password, and timezone. Follow the prompts. The whole process takes 30–45 minutes depending on your internet speed and CPU.&lt;/p&gt;

&lt;p&gt;When it finishes, hit the admin panel at &lt;code&gt;http://YOUR_IP/vicidial/welcome.php&lt;/code&gt; with credentials &lt;strong&gt;6666 / 1234&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B: Full Manual Scratch Install (~90 Minutes)
&lt;/h3&gt;

&lt;p&gt;If you want to control every package version, compile flag, and config file, here's the manual path. This is the same process the auto-installer runs, just broken into individual steps so you can see what's happening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Base system prep:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Disable SELinux&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/SELINUX=enforcing/SELINUX=disabled/g'&lt;/span&gt; /etc/selinux/config

&lt;span class="c"&gt;# Install development tools and EPEL&lt;/span&gt;
dnf &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nb"&gt;install &lt;/span&gt;epel-release
dnf update &lt;span class="nt"&gt;-y&lt;/span&gt;
dnf groupinstall &lt;span class="s2"&gt;"Development Tools"&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="c"&gt;# Enable PowerTools/CRB repo (needed for devel packages)&lt;/span&gt;
dnf config-manager &lt;span class="nt"&gt;--set-enabled&lt;/span&gt; crb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Install all dependencies in one shot:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; php php-cli php-gd php-curl php-mysqlnd php-ldap php-zip &lt;span class="se"&gt;\&lt;/span&gt;
  php-fileinfo php-opcache php-mbstring php-xml php-xmlrpc php-imap &lt;span class="se"&gt;\&lt;/span&gt;
  wget unzip make patch gcc gcc-c++ subversion php-devel gd-devel &lt;span class="se"&gt;\&lt;/span&gt;
  readline-devel httpd libpcap libpcap-devel ncurses ncurses-devel &lt;span class="se"&gt;\&lt;/span&gt;
  screen mutt newt-devel sqlite-devel libuuid-devel sox sendmail &lt;span class="se"&gt;\&lt;/span&gt;
  lame lame-devel htop iftop mariadb-server mariadb-devel &lt;span class="se"&gt;\&lt;/span&gt;
  perl-CPAN perl-YAML perl-libwww-perl perl-DBI perl-DBD-MySQL perl-GD &lt;span class="se"&gt;\&lt;/span&gt;
  openssl-devel libxml2-devel libedit-devel libsrtp-devel &lt;span class="se"&gt;\&lt;/span&gt;
  elfutils-libelf-devel kernel-devel-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; mod_ssl certbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Start MariaDB and create the database:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; mariadb

mysql &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
SET GLOBAL connect_timeout=60;
CREATE DATABASE asterisk DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
CREATE USER 'cron'@'localhost' IDENTIFIED BY 'your_secure_password';
GRANT SELECT,INSERT,UPDATE,DELETE,LOCK TABLES ON asterisk.* TO 'cron'@'%' IDENTIFIED BY 'your_secure_password';
CREATE USER 'custom'@'localhost' IDENTIFIED BY 'your_custom_password';
GRANT SELECT,INSERT,UPDATE,DELETE,LOCK TABLES ON asterisk.* TO 'custom'@'%' IDENTIFIED BY 'your_custom_password';
GRANT RELOAD ON *.* TO 'cron'@'%';
FLUSH PRIVILEGES;
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Install Perl modules:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /usr/bin/
curl &lt;span class="nt"&gt;-LOk&lt;/span&gt; http://xrl.us/cpanm
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x cpanm

cpanm &lt;span class="nt"&gt;-n&lt;/span&gt; Digest::MD5 Digest::SHA1 DBI DBD::mysql Net::Telnet Time::HiRes &lt;span class="se"&gt;\&lt;/span&gt;
  Net::Server Mail::Sendmail Unicode::Map Spreadsheet::WriteExcel &lt;span class="se"&gt;\&lt;/span&gt;
  OLE::Storage_Lite Proc::ProcessTable IO::Scalar Spreadsheet::ParseExcel &lt;span class="se"&gt;\&lt;/span&gt;
  Curses Getopt::Long Net::Domain Term::ReadKey Term::ANSIColor &lt;span class="se"&gt;\&lt;/span&gt;
  Spreadsheet::XLSX Spreadsheet::Read LWP::UserAgent HTML::Entities &lt;span class="se"&gt;\&lt;/span&gt;
  HTML::Strip HTML::FormatText HTML::TreeBuilder Time::Local &lt;span class="se"&gt;\&lt;/span&gt;
  MIME::Decoder Mail::POP3Client Mail::IMAPClient IO::Socket::SSL &lt;span class="se"&gt;\&lt;/span&gt;
  MIME::Base64 MIME::QuotedPrint Crypt::Eksblowfish::Bcrypt Text::CSV
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Compile DAHDI (timing source):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /usr/src/
wget https://downloads.asterisk.org/pub/telephony/dahdi-linux-complete/dahdi-linux-complete-current.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf dahdi-linux-complete-current.tar.gz
&lt;span class="nb"&gt;cd &lt;/span&gt;dahdi-linux-complete-&lt;span class="k"&gt;*&lt;/span&gt;/
make &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make install-config
modprobe dahdi
dahdi_genconf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Compile Asterisk:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /usr/src/
wget http://download.vicidial.com/required-apps/asterisk-13.29.2-vici.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf asterisk-13.29.2-vici.tar.gz
&lt;span class="nb"&gt;cd &lt;/span&gt;asterisk-13.29.2/

&lt;span class="c"&gt;# Configure with bundled pjproject (handles NAT traversal)&lt;/span&gt;
./configure &lt;span class="nt"&gt;--libdir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/lib64 &lt;span class="nt"&gt;--with-pjproject-bundled&lt;/span&gt; &lt;span class="nt"&gt;--with-jansson-bundled&lt;/span&gt;

make menuselect   &lt;span class="c"&gt;# Enable app_meetme, res_speech, codec_g729 if licensed&lt;/span&gt;
make &lt;span class="nt"&gt;-j&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;nproc&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
make &lt;span class="nb"&gt;install
&lt;/span&gt;make samples
make config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7. Check out VICIdial from SVN and run the installer:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /usr/src/astguiclient
&lt;span class="nb"&gt;cd&lt;/span&gt; /usr/src/astguiclient
svn checkout svn://svn.eflo.net:3690/agc_2-X/trunk

&lt;span class="nb"&gt;cd &lt;/span&gt;trunk
perl install.pl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;install.pl&lt;/code&gt; script asks for paths and server configuration. Accept defaults for most options; set your webroot to &lt;code&gt;/var/www/html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Populate the database and start services:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql asterisk &amp;lt; /usr/src/astguiclient/trunk/extras/MySQL_AST_CREATE_tables.sql
mysql asterisk &amp;lt; /usr/src/astguiclient/trunk/extras/first_server_install.sql

&lt;span class="c"&gt;# Update server IP in the database&lt;/span&gt;
/usr/share/astguiclient/ADMIN_update_server_ip.pl &lt;span class="nt"&gt;--old-server_ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.10.10.15

&lt;span class="c"&gt;# Populate area codes&lt;/span&gt;
/usr/share/astguiclient/ADMIN_area_code_populate.pl

&lt;span class="c"&gt;# Start everything&lt;/span&gt;
systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; httpd
systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; asterisk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;9. Install sound files:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /var/lib/asterisk/sounds
wget &lt;span class="nt"&gt;-q&lt;/span&gt; http://downloads.digium.com/pub/telephony/sounds/asterisk-core-sounds-en-ulaw-current.tar.gz
wget &lt;span class="nt"&gt;-q&lt;/span&gt; http://downloads.digium.com/pub/telephony/sounds/asterisk-core-sounds-en-wav-current.tar.gz
wget &lt;span class="nt"&gt;-q&lt;/span&gt; http://downloads.digium.com/pub/telephony/sounds/asterisk-extra-sounds-en-ulaw-current.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf asterisk-core-sounds-en-ulaw-current.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf asterisk-core-sounds-en-wav-current.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf asterisk-extra-sounds-en-ulaw-current.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Admin panel: &lt;code&gt;http://YOUR_IP/vicidial/welcome.php&lt;/code&gt; — login &lt;strong&gt;6666 / 1234&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manual Install Pros and Cons
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Full control over every component&lt;/td&gt;
&lt;td&gt;Takes 45–90 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Choose your own Asterisk version&lt;/td&gt;
&lt;td&gt;More moving parts to break&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom kernel and module support&lt;/td&gt;
&lt;td&gt;You own every dependency update&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works on any RHEL 9 derivative&lt;/td&gt;
&lt;td&gt;No official support for manual builds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Method 3: Docker Containers (~20 Minutes)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Dev/test environments, CI pipelines, teams already running Kubernetes, people who want to tear down and rebuild without hosing their host OS.&lt;/p&gt;

&lt;p&gt;Docker and VICIdial have a complicated history. Early attempts failed because of DAHDI timing issues — Asterisk's MeetMe conferencing module needs a kernel-level timing source. Modern Docker on Linux shares the host kernel clock, which means LXC containers can handle DAHDI just fine. The Perl scripts technically don't need to run on the same box as Asterisk, so splitting them into separate containers actually helps scalability.&lt;/p&gt;

&lt;p&gt;There's no official VICIdial Docker image. Community-maintained images exist on Docker Hub, but they vary in quality. The most reliable approach is building your own from the ViciBox base or the AlmaLinux scratch install.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Compose Stack
&lt;/h3&gt;

&lt;p&gt;Here's a minimal &lt;code&gt;docker-compose.yml&lt;/code&gt; that runs VICIdial with separate containers for web/app, Asterisk, and MariaDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vicidial-db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb:10.11&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vicidial-db&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your_root_password&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;asterisk&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cron&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your_cron_password&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;vicidial-db-data:/var/lib/mysql&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./sql/MySQL_AST_CREATE_tables.sql:/docker-entrypoint-initdb.d/01-schema.sql&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./sql/first_server_install.sql:/docker-entrypoint-initdb.d/02-seed.sql&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3306:3306"&lt;/span&gt;

  &lt;span class="na"&gt;vicidial-web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.vicidial&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vicidial-web&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;vicidial-db&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;VICIDIAL_DB_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vicidial-db&lt;/span&gt;
      &lt;span class="na"&gt;VICIDIAL_DB_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;asterisk&lt;/span&gt;
      &lt;span class="na"&gt;VICIDIAL_DB_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cron&lt;/span&gt;
      &lt;span class="na"&gt;VICIDIAL_DB_PASS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your_cron_password&lt;/span&gt;
      &lt;span class="na"&gt;SERVER_IP&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_HOST_IP"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;vicidial-recordings:/var/spool/asterisk/monitorDONE&lt;/span&gt;

  &lt;span class="na"&gt;vicidial-asterisk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.asterisk&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vicidial-asterisk&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;privileged&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;    &lt;span class="c1"&gt;# Required for DAHDI timing&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;vicidial-db&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5060:5060/udp"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5060:5060/tcp"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10000-10500:10000-10500/udp"&lt;/span&gt;  &lt;span class="c1"&gt;# RTP range&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;VICIDIAL_DB_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vicidial-db&lt;/span&gt;
      &lt;span class="na"&gt;SERVER_IP&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_HOST_IP"&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vicidial-db-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vicidial-recordings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Building the Asterisk Container
&lt;/h3&gt;

&lt;p&gt;Your &lt;code&gt;Dockerfile.asterisk&lt;/code&gt; needs DAHDI compiled against the host kernel headers. Mount them from the host or install them in the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; almalinux:9&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;dnf &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nb"&gt;install &lt;/span&gt;epel-release &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; dnf update &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    dnf groupinstall &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="s2"&gt;"Development Tools"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; wget subversion kernel-devel &lt;span class="se"&gt;\
&lt;/span&gt;    libxml2-devel openssl-devel newt-devel &lt;span class="se"&gt;\
&lt;/span&gt;    sqlite-devel libuuid-devel sox lame lame-devel

&lt;span class="c"&gt;# Build DAHDI, Asterisk, checkout VICIdial&lt;/span&gt;
&lt;span class="c"&gt;# (Use the same compile steps from Method 2)&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5060/udp 5060/tcp&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["asterisk", "-f"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important: the &lt;code&gt;vicidial-asterisk&lt;/code&gt; container runs with &lt;code&gt;privileged: true&lt;/code&gt;. That's the only way DAHDI can access kernel timing. If you're not comfortable with privileged containers in production, stick with ViciBox or bare metal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start the Stack
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Watch logs&lt;/span&gt;
docker-compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; vicidial-web

&lt;span class="c"&gt;# Access admin panel&lt;/span&gt;
http://YOUR_HOST_IP/vicidial/welcome.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Docker Pros and Cons
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Reproducible builds&lt;/td&gt;
&lt;td&gt;Privileged container required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Easy teardown and rebuild&lt;/td&gt;
&lt;td&gt;No official images&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scales with orchestration&lt;/td&gt;
&lt;td&gt;DAHDI needs host kernel headers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Isolates components cleanly&lt;/td&gt;
&lt;td&gt;More moving parts than ViciBox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Great for dev/test&lt;/td&gt;
&lt;td&gt;Not battle-tested for 100+ agent production&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For the full Docker deep-dive including Kubernetes deployment and volume management, see our &lt;a href="https://dev.to/blog/vicidial-docker-deployment"&gt;Docker deployment guide&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Post-Install Checklist: First Campaign in 10 Minutes
&lt;/h2&gt;

&lt;p&gt;You've got a running VICIdial instance. Now you need to make it actually do something. Here's the fastest path to your first test call.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Change the Default Admin Password
&lt;/h3&gt;

&lt;p&gt;Log in at &lt;code&gt;/vicidial/welcome.php&lt;/code&gt; with &lt;strong&gt;6666 / 1234&lt;/strong&gt;. Go to &lt;strong&gt;Admin &amp;gt; Users &amp;gt; 6666&lt;/strong&gt; and change the password. Do this now, not later.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Set the Server Timezone
&lt;/h3&gt;

&lt;p&gt;Navigate to &lt;strong&gt;Admin &amp;gt; Servers &amp;gt; Modify Server&lt;/strong&gt;. Verify the timezone matches your system clock. If it doesn't match, your call logs will show wrong timestamps and Local Call Time enforcement will dial people at 3 a.m.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Verify system timezone&lt;/span&gt;
timedatectl

&lt;span class="c"&gt;# If wrong, fix it&lt;/span&gt;
timedatectl set-timezone America/Chicago
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update the PHP timezone in your &lt;code&gt;php.ini&lt;/code&gt; to match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;date.timezone&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;America/Chicago&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Apache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl restart httpd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Add a SIP Trunk (Carrier)
&lt;/h3&gt;

&lt;p&gt;Go to &lt;strong&gt;Admin &amp;gt; Carriers &amp;gt; Add Carrier&lt;/strong&gt;. You need a VoIP provider that gives you SIP credentials. Fill in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Carrier Name&lt;/strong&gt;: Your provider's name&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocol&lt;/strong&gt;: SIP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registration String&lt;/strong&gt;: &lt;code&gt;provider_username:provider_password@sip.provider.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codec&lt;/strong&gt;: Start with &lt;code&gt;ulaw&lt;/code&gt; (G.711). It's uncompressed, uses more bandwidth, but causes fewer audio issues than G.729.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Save. Asterisk will register with your provider within 30 seconds. Check the CLI to confirm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip show registry"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see your provider listed with a status of &lt;code&gt;Registered&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Create Your First Campaign
&lt;/h3&gt;

&lt;p&gt;Go to &lt;strong&gt;Admin &amp;gt; Campaigns &amp;gt; Add Campaign&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Campaign ID&lt;/strong&gt;: &lt;code&gt;TEST01&lt;/code&gt; (2–8 uppercase characters)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Campaign Name&lt;/strong&gt;: &lt;code&gt;Test Campaign&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dial Method&lt;/strong&gt;: &lt;code&gt;RATIO&lt;/code&gt; (start here; switch to &lt;code&gt;ADAPT_AVERAGE_INTENSITY&lt;/code&gt; later)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto Dial Level&lt;/strong&gt;: &lt;code&gt;1.0&lt;/code&gt; (one line per agent — safe for testing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active&lt;/strong&gt;: &lt;code&gt;Y&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local Call Time&lt;/strong&gt;: Set to your region's allowed calling hours&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Create a Lead List and Upload Leads
&lt;/h3&gt;

&lt;p&gt;Go to &lt;strong&gt;Admin &amp;gt; Lists &amp;gt; Add List&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;List ID&lt;/strong&gt;: &lt;code&gt;10001&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;List Name&lt;/strong&gt;: &lt;code&gt;Test Leads&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Campaign&lt;/strong&gt;: &lt;code&gt;TEST01&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active&lt;/strong&gt;: &lt;code&gt;Y&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Upload a CSV with at minimum these columns: &lt;code&gt;phone_code&lt;/code&gt; (1 for US), &lt;code&gt;phone_number&lt;/code&gt;, &lt;code&gt;first_name&lt;/code&gt;, &lt;code&gt;last_name&lt;/code&gt;. Even three rows is enough for testing. Go to &lt;strong&gt;Admin &amp;gt; Lists &amp;gt; Load Leads&lt;/strong&gt; and upload the file.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Create an Agent User
&lt;/h3&gt;

&lt;p&gt;Go to &lt;strong&gt;Admin &amp;gt; Users &amp;gt; Add User&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User ID&lt;/strong&gt;: Pick a number (e.g., &lt;code&gt;100&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password&lt;/strong&gt;: Set something real&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Level&lt;/strong&gt;: &lt;code&gt;1&lt;/code&gt; (agent)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Group&lt;/strong&gt;: &lt;code&gt;AGENTS&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7. Set Up an Agent Phone
&lt;/h3&gt;

&lt;p&gt;Go to &lt;strong&gt;Admin &amp;gt; Phones &amp;gt; Add Phone&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Extension&lt;/strong&gt;: &lt;code&gt;100&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server IP&lt;/strong&gt;: Your VICIdial server IP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocol&lt;/strong&gt;: &lt;code&gt;SIP&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Login&lt;/strong&gt;: &lt;code&gt;100&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pass&lt;/strong&gt;: Set a SIP password&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Register a softphone (Zoiper, MicroSIP, or the built-in WebRTC phone) to your server using these credentials.&lt;/p&gt;

&lt;p&gt;For WebRTC setup (no softphone needed), see our &lt;a href="https://dev.to/blog/vicidial-webrtc-setup"&gt;WebRTC agent guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Make Your First Test Call
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open the agent interface: &lt;code&gt;http://YOUR_IP/agc/vicidial.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Log in with your agent credentials&lt;/li&gt;
&lt;li&gt;Select the &lt;code&gt;TEST01&lt;/code&gt; campaign&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Resume&lt;/strong&gt; to go into active status&lt;/li&gt;
&lt;li&gt;The dialer should pull a lead and dial it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If audio works both ways, congratulations — you have a working dialer.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Gotchas That Eat Your Weekend
&lt;/h2&gt;

&lt;p&gt;Every VICIdial install hits at least one of these. Save yourself the troubleshooting by checking them upfront.&lt;/p&gt;

&lt;h3&gt;
  
  
  Timezone Mismatch (The #1 Issue)
&lt;/h3&gt;

&lt;p&gt;VICIdial checks time consistency between three sources: the system clock, PHP, and MariaDB. If any of them disagree, you'll see "There is a time synchronization problem with your system" on every page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix it in one pass:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Set system timezone&lt;/span&gt;
timedatectl set-timezone America/New_York

&lt;span class="c"&gt;# 2. Set PHP timezone&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/;date.timezone =/date.timezone = America\/New_York/'&lt;/span&gt; /etc/php.ini

&lt;span class="c"&gt;# 3. Restart services&lt;/span&gt;
systemctl restart httpd mariadb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ViciBox 12.0.2 fixed a MariaDB TIMESTAMP field default that caused phantom time sync errors. If you're on an older ViciBox version, upgrade.&lt;/p&gt;

&lt;h3&gt;
  
  
  Firewall Blocking SIP/RTP
&lt;/h3&gt;

&lt;p&gt;SIP runs on port 5060 (TCP and UDP). RTP audio runs on a configurable range, usually 10000–20000 UDP. If your firewall blocks any of these, calls will either fail to connect or connect with no audio.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# firewalld (AlmaLinux/Rocky)&lt;/span&gt;
firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--add-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5060/udp
firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--add-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5060/tcp
firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--add-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10000-20000/udp
firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--add-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80/tcp
firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--add-port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;443/tcp
firewall-cmd &lt;span class="nt"&gt;--reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If calls connect but one or both sides hear silence, that's almost always a NAT/firewall issue with RTP. Check that your &lt;code&gt;externip&lt;/code&gt; and &lt;code&gt;localnet&lt;/code&gt; settings in Asterisk's SIP config are correct.&lt;/p&gt;

&lt;h3&gt;
  
  
  Codec Mismatches
&lt;/h3&gt;

&lt;p&gt;If your SIP provider expects G.729 and you're sending G.711 (ulaw), calls will fail with a &lt;code&gt;488 Not Acceptable Here&lt;/code&gt; error. Match your Asterisk codec settings to whatever your carrier requires.&lt;/p&gt;

&lt;p&gt;Check what codecs are loaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"core show codecs"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the carrier's codec in &lt;strong&gt;Admin &amp;gt; Carriers &amp;gt; Modify Carrier&lt;/strong&gt; under the dial prefix or trunk config. When in doubt, &lt;code&gt;ulaw&lt;/code&gt; (G.711) works with 95% of providers and avoids transcoding overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  DAHDI Timing Not Loaded
&lt;/h3&gt;

&lt;p&gt;If MeetMe conferencing won't start and agents can't hear anything, DAHDI probably isn't loaded. Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lsmod | &lt;span class="nb"&gt;grep &lt;/span&gt;dahdi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If nothing comes back, load it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;modprobe dahdi
dahdi_genconf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add &lt;code&gt;dahdi&lt;/code&gt; to &lt;code&gt;/etc/modules-load.d/dahdi.conf&lt;/code&gt; so it loads on boot.&lt;/p&gt;

&lt;h3&gt;
  
  
  SELinux Blocking Apache or Asterisk
&lt;/h3&gt;

&lt;p&gt;If the web panel returns a blank page or 403 errors and your Apache error log shows permission denials, SELinux is the culprit. VICIdial doesn't ship with SELinux policies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check if SELinux is enforcing&lt;/span&gt;
getenforce

&lt;span class="c"&gt;# Disable it (required for VICIdial)&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/SELINUX=enforcing/SELINUX=disabled/g'&lt;/span&gt; /etc/selinux/config
reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Asterisk Not Starting After Reboot
&lt;/h3&gt;

&lt;p&gt;Usually a missing DAHDI module or a bad &lt;code&gt;extensions.conf&lt;/code&gt;. Check the logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-100&lt;/span&gt; /var/log/asterisk/messages
asterisk &lt;span class="nt"&gt;-rvvv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for &lt;code&gt;Unable to open pseudo channel&lt;/code&gt; (DAHDI timing) or &lt;code&gt;pbx_config: Failed to load&lt;/code&gt; (broken dialplan).&lt;/p&gt;




&lt;h2&gt;
  
  
  Method Comparison: Which One Should You Pick?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;ViciBox ISO&lt;/th&gt;
&lt;th&gt;Manual (AlmaLinux 9)&lt;/th&gt;
&lt;th&gt;Docker&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Install time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15 min&lt;/td&gt;
&lt;td&gt;45–90 min&lt;/td&gt;
&lt;td&gt;20 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Skill required&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Basic Linux&lt;/td&gt;
&lt;td&gt;Intermediate&lt;/td&gt;
&lt;td&gt;Docker + Linux&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OS control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Production ready&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Dev/test only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Official support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (VICIhost)&lt;/td&gt;
&lt;td&gt;Community only&lt;/td&gt;
&lt;td&gt;Community only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Max agents&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;25 per server&lt;/td&gt;
&lt;td&gt;25 per server&lt;/td&gt;
&lt;td&gt;Untested at scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Upgrade path&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;zypper update&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;svn update&lt;/code&gt; + recompile&lt;/td&gt;
&lt;td&gt;Rebuild image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;First install, SMB&lt;/td&gt;
&lt;td&gt;Enterprise, custom builds&lt;/td&gt;
&lt;td&gt;Testing, CI/CD&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Our recommendation:&lt;/strong&gt; Start with ViciBox for your first install. Get familiar with the admin panel, campaigns, and call flow. When you outgrow a single server or need custom Asterisk modules, do a manual install on AlmaLinux 9 for your production cluster. Keep Docker for your staging environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  What to Do After Installation
&lt;/h2&gt;

&lt;p&gt;A running dialer is just the starting point. Here's what to tackle in your first week:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure AMD (Answering Machine Detection)&lt;/strong&gt; — Without it, agents waste 30%+ of their time listening to voicemail greetings. Our &lt;a href="https://dev.to/blog/vicidial-amd-guide"&gt;AMD configuration guide&lt;/a&gt; covers the settings that actually matter and the ones you should never touch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tune your dial level&lt;/strong&gt; — Starting at 1.0 is safe but slow. Our &lt;a href="https://dev.to/blog/vicidial-auto-dial-level-tuning"&gt;auto-dial level tuning guide&lt;/a&gt; shows how to ramp up without spiking your abandonment rate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set up call recording&lt;/strong&gt; — Required for QA and compliance. Enable it in the campaign settings and configure storage rotation before your disk fills up. See our &lt;a href="https://dev.to/blog/vicidial-call-recording"&gt;call recording guide&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Harden security&lt;/strong&gt; — Change all default passwords, set up firewall rules, restrict admin panel access by IP, and enable HTTPS. Our &lt;a href="https://dev.to/blog/vicidial-security-hardening"&gt;security hardening guide&lt;/a&gt; has the full checklist.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set up monitoring&lt;/strong&gt; — The Real-Time Report is your best friend. Learn it. Our &lt;a href="https://dev.to/blog/vicidial-realtime-report-guide"&gt;real-time report guide&lt;/a&gt; explains every metric.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;STIR/SHAKEN compliance&lt;/strong&gt; — If you're dialing US numbers, your carrier needs STIR/SHAKEN attestation or your calls will show "Spam Likely." This isn't a VICIdial setting — it's a carrier requirement. Our &lt;a href="https://dev.to/blog/stir-shaken-vicidial-guide"&gt;STIR/SHAKEN guide&lt;/a&gt; explains what to ask your provider.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Need Help Getting Past the Install?
&lt;/h2&gt;

&lt;p&gt;Installation is the easy part. The hard part is tuning VICIdial so your agents actually connect with live humans instead of burning through leads.&lt;/p&gt;

&lt;p&gt;We've spent years tuning VICIdial deployments for outbound sales teams. AMD settings, dial level optimization, caller ID rotation, carrier selection — the stuff that turns a $0.03/minute dialer into a machine that books meetings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://vicistack.com/contact" rel="noopener noreferrer"&gt;Book a free 30-minute teardown&lt;/a&gt;&lt;/strong&gt; — we'll look at your current setup and tell you exactly where you're leaving conversions on the table. If we can help, we'll quote it. If we can't, you still walk away with a list of fixes. No pitch, no pressure.&lt;/p&gt;

</description>
      <category>voip</category>
      <category>asterisk</category>
      <category>sysadmin</category>
      <category>devops</category>
    </item>
    <item>
      <title>75+ Call Center Statistics for 2026: Industry Data, Benchmarks, and Trends</title>
      <dc:creator>Jason Shouldice</dc:creator>
      <pubDate>Sat, 28 Mar 2026 04:53:30 +0000</pubDate>
      <link>https://dev.to/gamlin/75-call-center-statistics-for-2026-industry-data-benchmarks-and-trends-62e</link>
      <guid>https://dev.to/gamlin/75-call-center-statistics-for-2026-industry-data-benchmarks-and-trends-62e</guid>
      <description>&lt;p&gt;&lt;strong&gt;The call center industry hit $352.4 billion in 2024 and is on track to cross $496 billion by 2027. But behind that growth number, the industry is splitting in two -- operations that adopted AI and cloud infrastructure are pulling away from those still running on-prem legacy stacks.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;We compiled 75+ statistics from industry reports, analyst firms, and government data to give you a single reference for everything happening in call centers right now. Every stat includes its source and year so you can cite it directly.&lt;/p&gt;

&lt;p&gt;Use the table of contents to jump to the section that matters for your operation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Market Size and Industry Growth&lt;/li&gt;
&lt;li&gt;Agent Performance Benchmarks&lt;/li&gt;
&lt;li&gt;Workforce and Turnover&lt;/li&gt;
&lt;li&gt;AI and Automation&lt;/li&gt;
&lt;li&gt;Technology and Infrastructure&lt;/li&gt;
&lt;li&gt;VoIP and Telephony&lt;/li&gt;
&lt;li&gt;Predictive Dialers&lt;/li&gt;
&lt;li&gt;Costs and Economics&lt;/li&gt;
&lt;li&gt;Customer Satisfaction and Experience&lt;/li&gt;
&lt;li&gt;Compliance and Regulation&lt;/li&gt;
&lt;li&gt;Remote Work and Operations&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Market Size and Industry Growth
&lt;/h2&gt;

&lt;p&gt;The call center industry keeps growing, but the shape of that growth has changed. Cloud contact centers and AI-driven operations are eating market share from traditional on-prem setups.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Statistic&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;The global call center market was valued at &lt;strong&gt;$352.4 billion&lt;/strong&gt; in 2024.&lt;/td&gt;
&lt;td&gt;Research and Markets, 2024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Projected market value: &lt;strong&gt;$496 billion by 2027&lt;/strong&gt;, up from $340 billion in 2020.&lt;/td&gt;
&lt;td&gt;PR Newswire, 2024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;The market is expected to reach &lt;strong&gt;$500.1 billion by 2030&lt;/strong&gt; at a 6% CAGR.&lt;/td&gt;
&lt;td&gt;Research and Markets, 2024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;The U.S. telemarketing and call centers industry is worth &lt;strong&gt;$28.5 billion&lt;/strong&gt; in 2026.&lt;/td&gt;
&lt;td&gt;IBISWorld, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;The call center outsourcing market reached &lt;strong&gt;$381.53 billion&lt;/strong&gt; in 2026 and is projected to hit &lt;strong&gt;$655.98 billion by 2032&lt;/strong&gt; (9.3% CAGR).&lt;/td&gt;
&lt;td&gt;Technavio, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Contact center software market CAGR: &lt;strong&gt;21.9%&lt;/strong&gt; (2026-2033).&lt;/td&gt;
&lt;td&gt;Grand View Research, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;The CCaaS (Contact Center as a Service) market is projected to grow from &lt;strong&gt;$8.33 billion in 2026 to $30.15 billion by 2034&lt;/strong&gt; (17.4% CAGR).&lt;/td&gt;
&lt;td&gt;Fortune Business Insights, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;The AI call center market reached &lt;strong&gt;$2.98 billion&lt;/strong&gt; in 2026, projected to hit &lt;strong&gt;$13.52 billion by 2034&lt;/strong&gt; (20.8% CAGR).&lt;/td&gt;
&lt;td&gt;Fortune Business Insights, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;SMEs account for &lt;strong&gt;55.72%&lt;/strong&gt; of the contact center market in 2026.&lt;/td&gt;
&lt;td&gt;Fortune Business Insights, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;BFSI (banking, financial services, insurance) holds &lt;strong&gt;21.34%&lt;/strong&gt; of the contact center market, growing at 22.9% CAGR.&lt;/td&gt;
&lt;td&gt;Fortune Business Insights, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Agent Performance Benchmarks
&lt;/h2&gt;

&lt;p&gt;These are the numbers that floor supervisors and ops managers actually use. If your operation is below these benchmarks, you have a specific problem to fix.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Statistic&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Industry average &lt;strong&gt;First Call Resolution (FCR): 70-74%&lt;/strong&gt;. Top performers hit 80%+.&lt;/td&gt;
&lt;td&gt;SQM Group / CloudTalk, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Average Handle Time (AHT): &lt;strong&gt;6-8 minutes&lt;/strong&gt; (general inquiries). Best-in-class: 4-6 minutes.&lt;/td&gt;
&lt;td&gt;CloudTalk, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Average Speed of Answer (ASA): &lt;strong&gt;28-40 seconds&lt;/strong&gt; industry average. Top tier: under 15 seconds.&lt;/td&gt;
&lt;td&gt;Sprinklr / CloudTalk, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Call abandonment rate benchmark: &lt;strong&gt;5-8%&lt;/strong&gt;. Best-in-class operations stay under 3%.&lt;/td&gt;
&lt;td&gt;Lead Advisors / CloudTalk, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;40%&lt;/strong&gt; of callers abandon after waiting 5+ minutes.&lt;/td&gt;
&lt;td&gt;Sprinklr, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;60%&lt;/strong&gt; of customers hang up after 60 seconds on hold.&lt;/td&gt;
&lt;td&gt;Sprinklr, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;Outbound conversion rate global benchmark: &lt;strong&gt;2.5%&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;CloudTalk, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;High-volume cold calling benchmark: &lt;strong&gt;80-150 calls per day&lt;/strong&gt; per agent, with calls averaging 2-5 minutes.&lt;/td&gt;
&lt;td&gt;Industry benchmarks, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;The 80/20 service level rule (80% of calls answered within 20 seconds) remains the standard, though top operations now target &lt;strong&gt;90/15&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;Nextiva, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;Average customer interaction duration: &lt;strong&gt;6 minutes 10 seconds&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;TrueList, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;Healthcare FCR: ~71%. Finance FCR: 67-71%. Retail FCR: ~78%.&lt;/td&gt;
&lt;td&gt;CloudTalk, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To put these benchmarks in context, here is what a typical VICIdial v2.14b0.5 campaign dashboard query looks like when you pull agent performance stats from the &lt;code&gt;vicidial_agent_log&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;talk_sec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_talk_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dead_sec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_dead_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SALE'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;conversion_pct&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;   &lt;span class="n"&gt;vicidial_agent_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;  &lt;span class="n"&gt;event_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;CURDATE&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;  &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;conversion_pct&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;avg_dead_time&lt;/code&gt; column is the one most managers ignore -- it measures the seconds an agent sits idle between calls. With a properly tuned predictive dialer, you should see dead time under 8 seconds. If it is north of 30 seconds, your &lt;code&gt;auto_dial_level&lt;/code&gt; or &lt;code&gt;hopper_level&lt;/code&gt; settings need adjustment. See our &lt;a href="https://dev.to/blog/contact-rate-optimization/"&gt;contact rate optimization guide&lt;/a&gt; for the exact campaign settings.&lt;/p&gt;




&lt;h2&gt;
  
  
  Workforce and Turnover
&lt;/h2&gt;

&lt;p&gt;Agent churn remains the single most expensive problem in the industry. The numbers have barely budged in a decade, though remote work is finally making a dent.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Statistic&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;Annual call center turnover rate: &lt;strong&gt;40-45%&lt;/strong&gt; in 2025, with some centers hitting 60%.&lt;/td&gt;
&lt;td&gt;Insignia Resources, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;First-year agent attrition: &lt;strong&gt;65-70%&lt;/strong&gt;. Most agents who leave do so within 12 months.&lt;/td&gt;
&lt;td&gt;Convoso, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;Average agent tenure: &lt;strong&gt;13-15 months&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;Convoso, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;Cost to replace a single agent: &lt;strong&gt;$10,000-$20,000&lt;/strong&gt; (recruiting, training, ramp-up).&lt;/td&gt;
&lt;td&gt;AmplifAI, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;For a 100-agent center with 40% turnover, annual replacement costs reach &lt;strong&gt;$400,000-$800,000&lt;/strong&gt;. Full impact including lost productivity: &lt;strong&gt;$1M+&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;AmplifAI, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;2.86 million&lt;/strong&gt; people work in U.S. contact centers. The industry lost 350,000 jobs between 2014-2023.&lt;/td&gt;
&lt;td&gt;Statista, 2024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;87%&lt;/strong&gt; of agents report high workplace stress.&lt;/td&gt;
&lt;td&gt;Convoso, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;77%&lt;/strong&gt; of agents say job stress affects their personal life.&lt;/td&gt;
&lt;td&gt;Convoso, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;61%&lt;/strong&gt; of contact centers report a rise in emotionally charged customer interactions.&lt;/td&gt;
&lt;td&gt;Calabrio, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;Labor accounts for &lt;strong&gt;95%&lt;/strong&gt; of total contact center operating costs.&lt;/td&gt;
&lt;td&gt;Gartner, 2024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;65%&lt;/strong&gt; of CX leaders view AI as essential for reducing agent burnout.&lt;/td&gt;
&lt;td&gt;Zoom, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you run VICIdial, you can track your own turnover impact by comparing ramp-up periods in the &lt;code&gt;vicidial_users&lt;/code&gt; table. Check the gap between &lt;code&gt;user_create_date&lt;/code&gt; and the date an agent first hits target conversion rates in the &lt;code&gt;vicidial_closer_log&lt;/code&gt;. Most operations find that agents who survive 90 days perform within 10% of veterans -- the problem is getting them to day 90.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI and Automation
&lt;/h2&gt;

&lt;p&gt;AI went from conference slide decks to production deployments in 2025. But the adoption numbers tell a split story: almost everyone bought AI tools, far fewer actually integrated them into daily operations.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Statistic&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;88%&lt;/strong&gt; of contact centers report using some form of AI-powered solution.&lt;/td&gt;
&lt;td&gt;IBM, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;Only &lt;strong&gt;25%&lt;/strong&gt; have fully integrated AI automation into daily operations.&lt;/td&gt;
&lt;td&gt;IBM, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;91%&lt;/strong&gt; of businesses with 50+ employees use AI chatbots in some part of the customer journey.&lt;/td&gt;
&lt;td&gt;Tidio, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;Gartner projects conversational AI will cut contact center labor costs by &lt;strong&gt;$80 billion&lt;/strong&gt; in 2026.&lt;/td&gt;
&lt;td&gt;Gartner, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;10%&lt;/strong&gt; of agent interactions will be automated by end of 2026, up from 1.6% in 2023 -- a fivefold increase.&lt;/td&gt;
&lt;td&gt;Gartner, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;30%&lt;/strong&gt; of service cases were resolved by AI in 2025. Expected to hit &lt;strong&gt;50% by 2027&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;Nextiva, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;80%&lt;/strong&gt; of contact centers expected to use AI for routing or agent coaching by end of 2026.&lt;/td&gt;
&lt;td&gt;Nextiva, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;AI-assisted agents see a &lt;strong&gt;14% increase&lt;/strong&gt; in issues resolved per hour and a &lt;strong&gt;9% reduction&lt;/strong&gt; in average handle time.&lt;/td&gt;
&lt;td&gt;Zoom, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;Average AI chatbot response time: &lt;strong&gt;under 3 seconds&lt;/strong&gt;. Average human agent first response: &lt;strong&gt;6.8 hours&lt;/strong&gt; (email/ticket), real-time voice under 40 seconds.&lt;/td&gt;
&lt;td&gt;Dante AI, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;76%&lt;/strong&gt; of contact center leaders are formalizing a split model: AI handles routing and availability, humans handle complex and emotional interactions.&lt;/td&gt;
&lt;td&gt;CMSWire, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;Only &lt;strong&gt;3%&lt;/strong&gt; of contact centers operate on a single, unified platform. Average organization manages &lt;strong&gt;3.9 different&lt;/strong&gt; contact center technologies.&lt;/td&gt;
&lt;td&gt;IBM, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;AI-powered answering machine detection (AMD) reaches &lt;strong&gt;95-98% accuracy&lt;/strong&gt;, versus 80-85% for legacy rule-based systems.&lt;/td&gt;
&lt;td&gt;MightyCall / Convoso, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;80%&lt;/strong&gt; of cold calls go to voicemail. Optimized AMD is the biggest single lever for improving live connection rates.&lt;/td&gt;
&lt;td&gt;NobleBiz, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On VICIdial, AMD is configured per-campaign via the admin GUI. The key setting is &lt;code&gt;amd_type&lt;/code&gt; in the campaign configuration. Here is a typical Asterisk dialplan snippet from &lt;code&gt;/etc/asterisk/extensions.conf&lt;/code&gt; that shows how AMD feeds back into VICIdial's call routing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[amd-check]&lt;/span&gt;
&lt;span class="py"&gt;exten&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; s,1,Answer()&lt;/span&gt;
&lt;span class="py"&gt;exten&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; s,n,AMD()&lt;/span&gt;
&lt;span class="py"&gt;exten&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; s,n,GotoIf($["${AMDSTATUS}"="HUMAN"]?human:machine)&lt;/span&gt;
&lt;span class="py"&gt;exten&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; s,n(human),AGI(agi://127.0.0.1:4577/call_log--HQ--${EXTEN}--${CALLERID(num)}----)&lt;/span&gt;
&lt;span class="py"&gt;exten&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; s,n(machine),Hangup()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference between 85% and 97% AMD accuracy at 150 dials/day per agent translates to roughly 18 additional live conversations per agent per shift. Over a 20-seat floor, that is 360 extra live connects per day. Our &lt;a href="https://dev.to/blog/vicidial-amd-guide/"&gt;AMD tuning guide&lt;/a&gt; covers the specific &lt;code&gt;amd.conf&lt;/code&gt; settings that move accuracy from the 85% range to 95%+.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technology and Infrastructure
&lt;/h2&gt;

&lt;p&gt;The cloud migration is essentially done for new installations. The remaining on-prem holdouts are large enterprises with compliance or latency requirements -- and even they are running hybrid architectures.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Statistic&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;78%&lt;/strong&gt; of call centers have migrated to cloud infrastructure in 2026.&lt;/td&gt;
&lt;td&gt;MedhaCloud, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;92%&lt;/strong&gt; of new contact center installations are cloud-based.&lt;/td&gt;
&lt;td&gt;CX Today, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;29.5%&lt;/strong&gt; of companies use true CCaaS (public-cloud, multi-tenant). Another &lt;strong&gt;21%&lt;/strong&gt; use hosted/managed platforms.&lt;/td&gt;
&lt;td&gt;Metrigy, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;72%&lt;/strong&gt; of enterprises have shifted from on-premise to cloud-based customer engagement platforms.&lt;/td&gt;
&lt;td&gt;Fortune Business Insights, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;64%&lt;/strong&gt; of customer interactions are now handled through digital channels (chat, email, social, self-service).&lt;/td&gt;
&lt;td&gt;Fortune Business Insights, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;Only &lt;strong&gt;7%&lt;/strong&gt; of contact centers deliver truly seamless cross-channel transitions.&lt;/td&gt;
&lt;td&gt;IBM, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;Only &lt;strong&gt;36%&lt;/strong&gt; of leaders report having a true omnichannel contact center setup.&lt;/td&gt;
&lt;td&gt;Nextiva, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;85%&lt;/strong&gt; of leaders cite outdated systems as the primary blocker to service improvements.&lt;/td&gt;
&lt;td&gt;Zoom, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 92% cloud figure for new installations (stat #47) tells an incomplete story. Many open-source operations -- VICIdial on Asterisk 18, FreePBX, GOautodial -- run in cloud VMs (AWS, DigitalOcean, Vultr) while keeping full control of their &lt;code&gt;/etc/asterisk/&lt;/code&gt; configuration and &lt;code&gt;vicidial_hopper&lt;/code&gt; tuning. That hybrid approach gives you cloud scalability without the per-seat SaaS markup.&lt;/p&gt;




&lt;h2&gt;
  
  
  VoIP and Telephony
&lt;/h2&gt;

&lt;p&gt;VoIP is no longer the alternative -- it is the default. Legacy POTS (plain old telephone service) prices increased 400% after FCC deregulation, making the economics impossible to argue against.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Statistic&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;The global VoIP market is valued at &lt;strong&gt;$185.34 billion&lt;/strong&gt; in 2026, growing to &lt;strong&gt;$388.97 billion by 2034&lt;/strong&gt; (10.4% CAGR).&lt;/td&gt;
&lt;td&gt;Fortune Business Insights, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;31%&lt;/strong&gt; of companies worldwide operate on VoIP systems.&lt;/td&gt;
&lt;td&gt;Tragofone, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;96%&lt;/strong&gt; of North American enterprises will have cloud or mobile PBX by end of 2026.&lt;/td&gt;
&lt;td&gt;Tragofone, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;Legacy POTS line prices increased &lt;strong&gt;400%&lt;/strong&gt; in North America following FCC deregulation.&lt;/td&gt;
&lt;td&gt;Tragofone, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;td&gt;Businesses save &lt;strong&gt;$55-65 per user/month&lt;/strong&gt; by consolidating legacy telecom tools into unified VoIP platforms.&lt;/td&gt;
&lt;td&gt;Tragofone, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;84%&lt;/strong&gt; of U.S. telecom traffic is signed and verified with STIR/SHAKEN as of H1 2025.&lt;/td&gt;
&lt;td&gt;Tragofone, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;48.4 billion robocalls&lt;/strong&gt; in the first 11 months of 2025 in the U.S., with an &lt;strong&gt;86% year-over-year increase&lt;/strong&gt; in scam robocalls.&lt;/td&gt;
&lt;td&gt;Tragofone, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;61&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;258.5 million&lt;/strong&gt; active Do Not Call registrations in FY 2025.&lt;/td&gt;
&lt;td&gt;FTC, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;The SIP trunking market: &lt;strong&gt;$73.14 billion&lt;/strong&gt; in 2025, projected to reach &lt;strong&gt;$157.91 billion by 2030&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;Tragofone, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;td&gt;Cloud telephony market projected to reach &lt;strong&gt;$52.3 billion by 2033&lt;/strong&gt; (8.9% CAGR).&lt;/td&gt;
&lt;td&gt;Tragofone, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For VICIdial shops running Asterisk 18+, STIR/SHAKEN attestation is configured in &lt;code&gt;/etc/asterisk/pjsip.conf&lt;/code&gt; with the &lt;code&gt;stir_shaken&lt;/code&gt; module. Getting full A-level attestation means your outbound CID matches your SIP trunk registration -- misconfigure this and your calls get flagged as spam before they even ring. Our &lt;a href="https://dev.to/blog/stir-shaken-vicidial-guide/"&gt;STIR/SHAKEN implementation guide&lt;/a&gt; covers the &lt;code&gt;pjsip.conf&lt;/code&gt; setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Predictive Dialers
&lt;/h2&gt;

&lt;p&gt;Predictive dialing remains the core technology that separates high-volume outbound operations from everyone else. The market is growing fast as AI-powered dialing replaces older statistical models.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Statistic&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;The global predictive dialer software market is expected to reach &lt;strong&gt;$6.6 billion by 2026&lt;/strong&gt; (35% CAGR).&lt;/td&gt;
&lt;td&gt;ResearchAndMarkets, 2021&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;Projected market size: &lt;strong&gt;$25.52 billion by 2030&lt;/strong&gt; (42.3% CAGR from 2025).&lt;/td&gt;
&lt;td&gt;Grand View Research, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;td&gt;Auto-dialers increase sales rep capability by &lt;strong&gt;212%&lt;/strong&gt; compared to manual dialing.&lt;/td&gt;
&lt;td&gt;Tragofone, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;67&lt;/td&gt;
&lt;td&gt;Dialing efficiency benchmark: &lt;strong&gt;25-30%&lt;/strong&gt; (percentage of dials that reach a live person).&lt;/td&gt;
&lt;td&gt;CloudTalk, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;68&lt;/td&gt;
&lt;td&gt;Predictive dialers reduce agent idle time from &lt;strong&gt;25-35 minutes per hour&lt;/strong&gt; (manual dialing) to &lt;strong&gt;under 5 minutes per hour&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;Industry benchmarks, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;69&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;80%&lt;/strong&gt; of cold calls go to voicemail. Effective AMD + voicemail drop recovers 15-20% of that lost time.&lt;/td&gt;
&lt;td&gt;NobleBiz / Convoso, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 212% productivity increase from auto-dialers (stat #66) is real, but only if your dial level is tuned correctly. In VICIdial, the &lt;code&gt;vicidial_campaigns&lt;/code&gt; table controls this. Here is how to check your current dial level and abandon rate in real time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; cron &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
  SELECT campaign_id, auto_dial_level, dial_timeout,
         adaptive_maximum_level, drop_call_seconds
  FROM   vicidial_campaigns
  WHERE  active = 'Y';"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your &lt;code&gt;auto_dial_level&lt;/code&gt; is above 3.0 and your drop rate exceeds 3%, you are trading compliance risk for marginal speed gains. Our &lt;a href="https://dev.to/blog/predictive-vs-progressive-dialing/"&gt;predictive vs. progressive dialing comparison&lt;/a&gt; breaks down when each mode makes sense for your specific campaign type.&lt;/p&gt;




&lt;h2&gt;
  
  
  Costs and Economics
&lt;/h2&gt;

&lt;p&gt;The cost structure of running a call center changed in 2025-2026. AI handles the cheap interactions, which means human agents increasingly handle only the expensive, complex ones -- pushing cost-per-call numbers in both directions depending on how you measure.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Statistic&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;Average cost per inbound call (human agent): &lt;strong&gt;$7.16&lt;/strong&gt;. Phone calls cost &lt;strong&gt;42% more&lt;/strong&gt; than web chat interactions.&lt;/td&gt;
&lt;td&gt;ContactBabel / Maestro QA, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;71&lt;/td&gt;
&lt;td&gt;Voice AI cost per call: &lt;strong&gt;$0.40&lt;/strong&gt;. Human agent cost per call: &lt;strong&gt;$7-12&lt;/strong&gt;. That is a &lt;strong&gt;90-95% cost reduction&lt;/strong&gt; per interaction.&lt;/td&gt;
&lt;td&gt;CX Today, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;AI chatbot interactions cost &lt;strong&gt;$0.50-0.70&lt;/strong&gt; each. Human agent interactions cost &lt;strong&gt;$6-15&lt;/strong&gt; each.&lt;/td&gt;
&lt;td&gt;Tidio, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;73&lt;/td&gt;
&lt;td&gt;Outsourcing rates by location in 2026: &lt;strong&gt;U.S. onshore: $25-45/hour&lt;/strong&gt;. Nearshore (Caribbean): $12-18/hour. Offshore (Asia): $6-14/hour.&lt;/td&gt;
&lt;td&gt;Crescendo AI, 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;Monthly operating costs for a 10-agent call center start at &lt;strong&gt;$67,300&lt;/strong&gt; (payroll $54,167 + overhead).&lt;/td&gt;
&lt;td&gt;Financial Models Lab, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;Payroll makes up &lt;strong&gt;60-70%&lt;/strong&gt; of total call center operating costs.&lt;/td&gt;
&lt;td&gt;Nextiva, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;td&gt;A 5% improvement in customer retention produces a &lt;strong&gt;95% increase&lt;/strong&gt; in profit.&lt;/td&gt;
&lt;td&gt;Desk365, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;77&lt;/td&gt;
&lt;td&gt;U.S. businesses risk losing &lt;strong&gt;$856 billion annually&lt;/strong&gt; due to poor customer service.&lt;/td&gt;
&lt;td&gt;CloudTalk, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;78&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$11,000&lt;/strong&gt; annual real estate savings per remote agent.&lt;/td&gt;
&lt;td&gt;Robert Half, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a full cost breakdown specific to open-source dialers, see our &lt;a href="https://dev.to/blog/vicidial-cost-2026/"&gt;VICIdial cost analysis for 2026&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Customer Satisfaction and Experience
&lt;/h2&gt;

&lt;p&gt;Customers still want to pick up the phone for anything complicated. But their tolerance for hold times has dropped to almost nothing, and they expect the agent to already know their history when they connect.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Statistic&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;79&lt;/td&gt;
&lt;td&gt;U.S. customer satisfaction score: &lt;strong&gt;77.3&lt;/strong&gt; (Q4 2024, on a 100-point scale).&lt;/td&gt;
&lt;td&gt;ACSI, 2024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;Good CSAT score range: &lt;strong&gt;75-85%&lt;/strong&gt;. Target benchmark: &lt;strong&gt;80%+&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;SurveySparrow, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;81&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;76%&lt;/strong&gt; of consumers still prefer phone support for complex issues.&lt;/td&gt;
&lt;td&gt;Salesmate, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;71%&lt;/strong&gt; of Gen Z say phone calls resolve issues faster than any other channel.&lt;/td&gt;
&lt;td&gt;Salesmate, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;83&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;90%&lt;/strong&gt; of consumers expect an omnichannel experience.&lt;/td&gt;
&lt;td&gt;Tidio, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;84&lt;/td&gt;
&lt;td&gt;Omnichannel CSAT: &lt;strong&gt;67%&lt;/strong&gt;. Disconnected multichannel CSAT: &lt;strong&gt;28%&lt;/strong&gt; -- a 39-point gap.&lt;/td&gt;
&lt;td&gt;Nextiva, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;43%&lt;/strong&gt; of customers report being unsatisfied with their most recent service interaction.&lt;/td&gt;
&lt;td&gt;Calabrio, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;88%&lt;/strong&gt; of customers expect faster responses than the previous year.&lt;/td&gt;
&lt;td&gt;Nextiva, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;87&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;32.3%&lt;/strong&gt; of consumers believe customer service should answer with zero hold time.&lt;/td&gt;
&lt;td&gt;Invoca, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;Live chat CSAT: &lt;strong&gt;87%&lt;/strong&gt;. Email CSAT: 61%. Phone CSAT: &lt;strong&gt;44%&lt;/strong&gt;. The gap is partly driven by phone being the escalation channel for already-frustrated customers.&lt;/td&gt;
&lt;td&gt;Enthu AI, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Compliance and Regulation
&lt;/h2&gt;

&lt;p&gt;2025-2026 brought more regulatory change than the previous decade combined. TCPA litigation is at an all-time high, fines are bigger, and state-level mini-TCPA laws added a new layer of complexity.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Statistic&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;TCPA litigation surged &lt;strong&gt;95%&lt;/strong&gt; compared to prior year. &lt;strong&gt;2,788 cases&lt;/strong&gt; filed in 2024, up 67% from 2023.&lt;/td&gt;
&lt;td&gt;Parker Poe, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;Q1 2025 TCPA class action filings ran &lt;strong&gt;112% above&lt;/strong&gt; the prior year.&lt;/td&gt;
&lt;td&gt;Corporate Compliance Insights, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;91&lt;/td&gt;
&lt;td&gt;Average TCPA settlement: &lt;strong&gt;$6.6 million&lt;/strong&gt;. Largest recent penalty: &lt;strong&gt;$300 million&lt;/strong&gt; (auto warranty scheme).&lt;/td&gt;
&lt;td&gt;PacificEast, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;FTC fine per violation: up to &lt;strong&gt;$53,088 per call&lt;/strong&gt;. DNC violations under TSR: up to &lt;strong&gt;$50,120 per call&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;FTC, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;Standard TCPA penalty: &lt;strong&gt;$500 per violation&lt;/strong&gt;. Willful violations: &lt;strong&gt;$1,500 per violation&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;TCPA statute, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;94&lt;/td&gt;
&lt;td&gt;At least &lt;strong&gt;15 states&lt;/strong&gt; now enforce their own mini-TCPA statutes with penalties that can exceed federal fines.&lt;/td&gt;
&lt;td&gt;ClickPoint Software, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;New FCC rule (April 2025): opt-out requests must be processed within &lt;strong&gt;10 business days&lt;/strong&gt;, down from 30 days.&lt;/td&gt;
&lt;td&gt;FCC, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;FCC classified AI-generated voice calls as &lt;strong&gt;robocalls&lt;/strong&gt; subject to TCPA consent requirements.&lt;/td&gt;
&lt;td&gt;FCC, 2024&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On VICIdial (revision 3939+, v2.14b0.5), the compliance-critical campaign settings live in the admin GUI under Campaigns &amp;gt; Detail. The &lt;code&gt;drop_call_seconds&lt;/code&gt; setting controls your safe harbor message timing, and the &lt;code&gt;call_count_limit&lt;/code&gt; field (mapped to the &lt;code&gt;vicidial_list&lt;/code&gt; table) enforces per-number attempt caps. Getting these wrong is how operations end up on the wrong side of a $6.6 million settlement. Our &lt;a href="https://dev.to/blog/call-center-compliance-checklist-2026/"&gt;compliance checklist for 2026&lt;/a&gt; maps every regulatory requirement to the specific admin setting you need to configure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Remote Work and Operations
&lt;/h2&gt;

&lt;p&gt;The pandemic forced the experiment. The data confirmed it works. Remote and hybrid models are now a permanent fixture in call center operations -- primarily because they cut turnover.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Statistic&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;97&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;73%&lt;/strong&gt; of call center leaders offer remote or hybrid work options.&lt;/td&gt;
&lt;td&gt;Zoom, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;98&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;69%&lt;/strong&gt; of contact centers maintain work-from-home programs.&lt;/td&gt;
&lt;td&gt;Alpharun, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99&lt;/td&gt;
&lt;td&gt;Remote work options reduce call center turnover by up to &lt;strong&gt;50%&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;Gitnux, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;Remote contact centers see a &lt;strong&gt;15% reduction&lt;/strong&gt; in average handle time when agents have proper cloud tools.&lt;/td&gt;
&lt;td&gt;Robert Half, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;Hybrid work models produce a &lt;strong&gt;12% increase&lt;/strong&gt; in First Contact Resolution rates.&lt;/td&gt;
&lt;td&gt;Gitnux, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$11,000&lt;/strong&gt; annual savings per remote employee in real estate costs alone.&lt;/td&gt;
&lt;td&gt;Robert Half, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;103&lt;/td&gt;
&lt;td&gt;Cross-border remote teams expected to handle &lt;strong&gt;30%&lt;/strong&gt; of global customer support traffic by 2026.&lt;/td&gt;
&lt;td&gt;Gitnux, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;104&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;74%&lt;/strong&gt; of workers say remote work options make them less likely to leave.&lt;/td&gt;
&lt;td&gt;Robert Half, 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Putting the Numbers to Work
&lt;/h2&gt;

&lt;p&gt;These statistics are not just interesting reading -- they map directly to decisions you can make this week. Here is how the data connects to actual call center operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AMD tuning&lt;/strong&gt; -- 80% of cold calls hit voicemail (stat #45). At 150 dials/day, that is 120 voicemails. Going from 85% to 97% AMD accuracy means 18 extra live conversations per agent per shift. Over 20 agents, that is 360 additional sales opportunities daily.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance math&lt;/strong&gt; -- At $53,088 per violation (stat #92), a single misconfigured campaign that drops 50 calls without a safe harbor message creates $2.65M in potential liability. The VICIdial &lt;code&gt;drop_call_seconds&lt;/code&gt; setting and &lt;code&gt;safe_harbor_message&lt;/code&gt; field exist specifically to prevent this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dialer economics&lt;/strong&gt; -- An open-source VICIdial/Asterisk stack runs at $0.004-0.008 per minute versus $0.02-0.05 for hosted SaaS solutions. At 100 agents making 150 calls/day, that difference compounds to six figures annually. The &lt;code&gt;VD_auto_dialer&lt;/code&gt; process handles the predictive algorithm -- see the &lt;a href="https://dev.to/blog/vicidial-predictive-dialer-settings/"&gt;dialer settings guide&lt;/a&gt; for the &lt;code&gt;adaptive_maximum_level&lt;/code&gt; and &lt;code&gt;dial_timeout&lt;/code&gt; values that matter most.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turnover ROI&lt;/strong&gt; -- With replacement costs at $10K-20K per agent (stat #25) and 40-45% annual turnover, a 100-seat center spends $400K-800K/year just on churn. Remote work cuts turnover by 50% (stat #99). That single policy change saves $200K-400K annually.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Want us to audit your operation against these benchmarks?&lt;/strong&gt; &lt;a href="https://vicistack.com/contact/" rel="noopener noreferrer"&gt;ViciStack&lt;/a&gt; increases call center conversions by 50% in 2 weeks. $5K total -- $1K down, $4K when we hit the number. We also offer ongoing optimization at $1,500/month.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How big is the call center industry in 2026?
&lt;/h3&gt;

&lt;p&gt;The global call center market was valued at $352.4 billion in 2024 and is projected to reach $496 billion by 2027. The U.S. segment alone is worth $28.5 billion. The fastest-growing subsegments are CCaaS (17.4% CAGR) and AI-powered contact center tools (20.8% CAGR).&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a good first call resolution rate?
&lt;/h3&gt;

&lt;p&gt;The industry average sits at 70-74%. A rate above 80% is considered top-tier. FCR varies by industry -- retail leads at ~78%, healthcare averages ~71%, and financial services ranges 67-71%. FCR is widely considered the most important single metric for call center effectiveness because it directly correlates with customer satisfaction and cost per resolution.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the average call center turnover rate?
&lt;/h3&gt;

&lt;p&gt;Annual turnover runs 40-45% in 2025-2026, with some operations hitting 60%. First-year attrition is even worse at 65-70%. The average agent stays 13-15 months. Replacing a single agent costs $10,000-20,000, making a 100-agent center with 40% turnover spend $400K-800K annually just on churn.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does it cost to run a call center?
&lt;/h3&gt;

&lt;p&gt;A 10-agent operation starts at roughly $67,300/month ($54,167 payroll + overhead). The average inbound call costs $7.16 with a human agent. Outsourcing rates range from $6-14/hour offshore to $25-45/hour onshore U.S. AI interactions cost $0.40-0.70 each -- a 90-95% reduction compared to human agents.&lt;/p&gt;

&lt;h3&gt;
  
  
  What percentage of call centers use AI?
&lt;/h3&gt;

&lt;p&gt;88% of contact centers use some form of AI tool, but only 25% have fully integrated AI into daily operations. Gartner projects AI will cut $80 billion in contact center labor costs in 2026. Currently 10% of interactions are automated (up from 1.6% in 2023), and that number is expected to reach 50% by 2027.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does a predictive dialer improve call center performance?
&lt;/h3&gt;

&lt;p&gt;Predictive dialers reduce agent idle time from 25-35 minutes per hour (manual dialing) to under 5 minutes per hour. They increase sales rep capability by 212% compared to manual dialing. The predictive dialer market is expected to hit $6.6 billion by 2026. The key is proper configuration -- an aggressive dial level creates compliance violations, while a conservative one wastes agent time.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are current TCPA fines for call centers?
&lt;/h3&gt;

&lt;p&gt;The FTC can fine up to $53,088 per call. Standard TCPA penalties are $500 per violation ($1,500 for willful violations). DNC violations under TSR carry fines up to $50,120 per call. The average TCPA settlement is $6.6 million, with the largest recent penalty reaching $300 million. TCPA litigation increased 95% year-over-year, and at least 15 states now enforce their own mini-TCPA statutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is VoIP replacing traditional phone systems?
&lt;/h3&gt;

&lt;p&gt;Effectively, yes. 96% of North American enterprises will have cloud or mobile PBX by end of 2026. Legacy POTS prices increased 400% after FCC deregulation, making the economic case impossible to argue. The global VoIP market is valued at $185.34 billion in 2026 and is projected to nearly double to $388.97 billion by 2034. Businesses save $55-65 per user per month by consolidating legacy tools into VoIP.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Statistics last updated March 2026. We update this page quarterly as new industry reports are published. If you spot a stat that needs updating or want to suggest an addition, reach out through our contact page.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;All statistics are sourced from publicly available industry reports, analyst firms, and government data. Source attributions are provided inline. For methodology questions about specific statistics, refer to the original source.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>voip</category>
      <category>asterisk</category>
      <category>sysadmin</category>
      <category>devops</category>
    </item>
    <item>
      <title>VICIdial vs Every Alternative: Honest Comparisons With Real Pricing</title>
      <dc:creator>Jason Shouldice</dc:creator>
      <pubDate>Sat, 28 Mar 2026 04:53:16 +0000</pubDate>
      <link>https://dev.to/gamlin/vicidial-vs-every-alternative-honest-comparisons-with-real-pricing-555a</link>
      <guid>https://dev.to/gamlin/vicidial-vs-every-alternative-honest-comparisons-with-real-pricing-555a</guid>
      <description>&lt;p&gt;We publish comparisons because every other "vs" article on Google is written by the vendor selling you their platform or an affiliate site collecting referral fees. Both have an incentive to lie to you. We don't.&lt;/p&gt;

&lt;p&gt;We run VICIdial deployments for a living, so yes, we have a bias. But our bias is transparent: we think VICIdial is the best option for most outbound-heavy operations running 30+ seats. When it isn't, we say so. We've recommended competitors to clients when the fit was better. That's in the articles below.&lt;/p&gt;

&lt;p&gt;Every comparison on this page includes real pricing (not "starting at" numbers from landing pages), actual feature gaps on both sides, and honest assessments of where VICIdial falls short. We pulled pricing from vendor websites, third-party review sites (Capterra, G2, GetApp), verified user reports, and invoices from operations we've migrated.&lt;/p&gt;

&lt;p&gt;VICIdial's licensing cost: $0. Always has been, always will be. The real cost is infrastructure and labor, which runs $60-130/seat/month all-in depending on scale and whether you self-host or use managed hosting.&lt;/p&gt;

&lt;p&gt;Every alternative, ranked by price tier.&lt;/p&gt;




&lt;h2&gt;
  
  
  VICIdial's Baseline: What You're Actually Comparing Against
&lt;/h2&gt;

&lt;p&gt;Before the head-to-heads, the VICIdial cost structure that every comparison below references. Real production cluster -- three servers, 100 agents, outbound-heavy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# VICIdial 3-server cluster: 100 seats
# DB server: Dual Xeon E-2388G, 128GB RAM, 2x 1TB NVMe RAID1
# 2x Dialer/Web servers: Xeon E-2288G, 64GB RAM, 1TB NVMe

Server hardware (amortized 36mo):  $1,000/mo
Colocation (3x 1U, 10Gbps):       $600/mo
SIP trunking (Telnyx, ~0.007/min): $4,500/mo
System administration:              $3,000/mo
DIDs (200 numbers @ $1.50/mo):     $300/mo
SSL + monitoring + backups:         $150/mo
────────────────────────────────────────────
Total:                              $9,550/mo
Per seat:                           $95.50/seat/mo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The VICIdial predictive dialer config that achieves the dial ratios we reference throughout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/astguiclient.conf (relevant settings)
&lt;/span&gt;&lt;span class="py"&gt;VARserver_ip&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; 10.0.1.10&lt;/span&gt;
&lt;span class="py"&gt;VARDB_server&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; 10.0.1.5&lt;/span&gt;
&lt;span class="py"&gt;VARactive_keepalives&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; 123456&lt;/span&gt;

&lt;span class="c"&gt;# Campaign settings via Admin GUI → Campaigns → Dial Settings
# These achieve 3.5:1 effective dial ratio with &amp;lt;3% abandon
&lt;/span&gt;&lt;span class="py"&gt;auto_dial_level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;3.5&lt;/span&gt;
&lt;span class="py"&gt;adaptive_intensity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;25&lt;/span&gt;
&lt;span class="py"&gt;adaptive_dl_diff_target&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0&lt;/span&gt;
&lt;span class="py"&gt;drop_call_seconds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;5&lt;/span&gt;
&lt;span class="py"&gt;available_only_ratio_tally&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Y&lt;/span&gt;
&lt;span class="py"&gt;hopper_level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;500&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The number that matters: at &lt;code&gt;auto_dial_level&lt;/code&gt; of 3.5 with good list data, VICIdial 2.14-917a agents average 45-55 minutes of talk time per hour. The &lt;code&gt;vicidial_auto_calls&lt;/code&gt; table tracks every active line in real time, and the &lt;code&gt;VD_auto_dialer&lt;/code&gt; cron (in &lt;code&gt;/usr/share/astguiclient/&lt;/code&gt;) adjusts the ratio every 6 seconds based on agent availability from &lt;code&gt;vicidial_agent_log&lt;/code&gt;. Most hosted platforms cap at 35-45 minutes because they don't expose these algorithm controls.&lt;/p&gt;

&lt;p&gt;For the full cost breakdown, see our &lt;a href="https://www.vicistack.com/blog/hosted-vs-self-hosted-predictive-dialer-2026/" rel="noopener noreferrer"&gt;self-hosted vs hosted cost guide&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Enterprise Tier: $150-400/seat/month
&lt;/h2&gt;

&lt;p&gt;These are the platforms Fortune 500 companies put on RFPs. They do everything. They cost accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  VICIdial vs Genesys Cloud CX
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Their pricing:&lt;/strong&gt; $75/seat (CX 1, voice-only) to $240/seat (CX 4, everything). Concurrent licensing runs $110-360/seat. Telecom, AI tokens, premium support, and WFM add-ons are all extra. Realistic all-in for 100-seat outbound: &lt;strong&gt;$200-400/seat/month&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where Genesys wins:&lt;/strong&gt; Omnichannel routing across voice, chat, email, and social in a single platform. Workforce management and quality assurance baked in at higher tiers. Gartner Magic Quadrant Leader five years running. If you need a single vendor for a 500+ seat blended operation with 15 channels, Genesys is the answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where VICIdial wins:&lt;/strong&gt; Outbound dialing performance. VICIdial's adaptive predictive algorithm was purpose-built for high-volume outbound. Genesys's dialer is one feature among hundreds. At 100 seats, you're saving $120,000-300,000/year in licensing alone. Full source code access means no feature gates, no per-seat AI surcharges, no consumption metering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who should pick Genesys:&lt;/strong&gt; Enterprise operations that need omnichannel, WFM, and a single vendor for procurement. Operations where the IT team doesn't want to manage Linux servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/blog/vicidial-vs-genesys-2026/"&gt;Read the full comparison: VICIdial vs Genesys Cloud CX 2026 --&amp;gt;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Also available: &lt;a href="https://dev.to/blog/vicidial-vs-genesys/"&gt;Original VICIdial vs Genesys comparison&lt;/a&gt; with additional migration framework.&lt;/p&gt;




&lt;h3&gt;
  
  
  VICIdial vs RingCentral RingCX
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Their pricing:&lt;/strong&gt; $65/seat for RingCX. Sounds cheap until you add RingEX ($20-35/seat for the business phone layer every agent also needs). Real cost: &lt;strong&gt;$90-120/seat/month&lt;/strong&gt; including telecom overages for high-volume outbound.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where RingCentral wins:&lt;/strong&gt; Unified communications. If your agents need a business phone system &lt;em&gt;and&lt;/em&gt; a contact center in one platform, RingCentral is hard to beat. WebRTC-native, clean admin console, decent AI summaries included at base price. The $65 sticker price includes more out-of-the-box features than any competitor at that tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where VICIdial wins:&lt;/strong&gt; Dialing throughput. RingCX's predictive dialer was added to a UCaaS platform. VICIdial &lt;em&gt;is&lt;/em&gt; a predictive dialer. For operations doing 200+ dials per agent per day, VICIdial's algorithm tuning, ratio control, and AMD configuration give you 15-30% more agent talk time. Plus no per-seat licensing at any scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who should pick RingCentral:&lt;/strong&gt; Blended inbound/outbound shops under 50 seats where agents also need a business phone for internal calls and transfers. Operations that already run RingEX company-wide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/blog/vicidial-vs-ringcentral/"&gt;Read the full comparison: VICIdial vs RingCentral RingCX --&amp;gt;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Mid-Market Tier: $130-200/seat/month
&lt;/h2&gt;

&lt;p&gt;These platforms target 50-200 seat outbound operations. They're built for dialing. The pricing reflects it.&lt;/p&gt;

&lt;h3&gt;
  
  
  VICIdial vs Five9
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Their pricing:&lt;/strong&gt; Digital plan at $119/seat (no voice). Core at $159/seat (the one you actually need). Plus, Pro, and Enterprise tiers are custom/unpublished, estimated at $185-250/seat. Telecom charges, AI overages, SMS costs, and professional services are all separate. Realistic all-in for 100-seat outbound: &lt;strong&gt;$200-275/seat/month&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where Five9 wins:&lt;/strong&gt; AI features that actually work in production. Real-time agent coaching, automated QA scoring, IVA (Intelligent Virtual Agent), and predictive routing are ahead of what you can bolt onto VICIdial without custom development. Their Salesforce integration is also the deepest in the industry. 24/7 support with guaranteed SLAs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where VICIdial wins:&lt;/strong&gt; Cost. At 100 seats, you save $150,000-220,000/year over Five9 Core. VICIdial's predictive algorithm is more configurable -- you control dial ratios, drop rates, AMD sensitivity, and callback timing at a level Five9 doesn't expose. No 50-seat minimums. No multi-year contract lock-in. No auto-renewal traps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who should pick Five9:&lt;/strong&gt; Operations that need Salesforce-native integration and are willing to pay for it. Inbound-heavy or omnichannel shops where Five9's routing engine matters more than raw outbound throughput.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/blog/vicidial-vs-five9-2026/"&gt;Read the full comparison: VICIdial vs Five9 2026 --&amp;gt;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Also available: &lt;a href="https://dev.to/blog/vicidial-vs-five9/"&gt;Original VICIdial vs Five9 comparison&lt;/a&gt; with migration decision framework.&lt;/p&gt;




&lt;h3&gt;
  
  
  VICIdial vs Convoso
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Their pricing:&lt;/strong&gt; Not published. Third-party sources and verified user reports put it at $90-200/seat depending on volume, contract length, and negotiation. Carrier fees, omnichannel add-ons, DID costs, and annual commitment are separate. Realistic all-in for 100-seat outbound: &lt;strong&gt;$175-250/seat/month&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where Convoso wins:&lt;/strong&gt; Ignite DID management. This is their genuine differentiator. AI-driven caller ID scoring, automatic number procurement, real-time DID health optimization. Users report up to 50% higher contact rates after enabling it. If your operation burns through DIDs and contact rates are your bottleneck, Ignite is worth evaluating seriously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where VICIdial wins:&lt;/strong&gt; Convoso started as a VICIdial reseller (SafeSoft Solutions / MarketDialer, 2006-2016). They know VICIdial's strengths because they spent eleven years inside the codebase. VICIdial still wins on cost ($65-130/seat all-in vs. $175-250), customization (full source code vs. platform-limited), and data ownership (your servers, your recordings, your leads).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who should pick Convoso:&lt;/strong&gt; Operations where DID reputation management is the primary pain point and the team doesn't want to build that tooling in-house. Small-to-mid shops (under 50 seats) where Convoso's onboarding speed matters more than long-term cost savings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/blog/vicidial-vs-convoso-2026/"&gt;Read the full comparison: VICIdial vs Convoso 2026 --&amp;gt;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Also available: &lt;a href="https://dev.to/blog/vicidial-vs-convoso/"&gt;Original VICIdial vs Convoso comparison&lt;/a&gt; with the full origin story and Convoso's VICIdial roots.&lt;/p&gt;




&lt;h3&gt;
  
  
  VICIdial vs ReadyMode
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Their pricing:&lt;/strong&gt; Not published. From verified client invoices: $150-199/seat plus $0.025-0.035/minute telecom, $2-5/DID/month, and $2,000-5,000 onboarding. CRM integration and custom reporting are paid add-ons. Realistic all-in for 100-seat outbound: &lt;strong&gt;$200-280/seat/month&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where ReadyMode wins:&lt;/strong&gt; Speed to deployment. Agents can be dialing within 48 hours. Browser-based WebRTC means no softphones, no SIP configuration, no desktop apps. Clean UI that reduces agent training time. Built-in TCPA compliance tools. Good fit for remote teams with high turnover where onboarding speed counts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where VICIdial wins:&lt;/strong&gt; Scale economics. ReadyMode's per-seat cost makes the math ugly past 100 agents. We've migrated three operations from ReadyMode to VICIdial in the past 18 months -- all three cited cost as the primary driver. VICIdial also gives you dialer algorithm controls that ReadyMode doesn't expose: adaptive ratios, per-campaign AMD tuning, custom dial pacing. At 200 seats, the annual savings run $250,000-400,000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who should pick ReadyMode:&lt;/strong&gt; Teams under 50 seats that need to launch fast, don't have Linux expertise, and prioritize UI polish over cost optimization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/blog/vicidial-vs-readymode/"&gt;Read the full comparison: VICIdial vs ReadyMode --&amp;gt;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Budget / Niche Tier: $89-150/seat/month
&lt;/h2&gt;

&lt;p&gt;Smaller platforms, tighter feature sets, lower price points. Good for specific use cases, bad for scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  VICIdial vs CallTools
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Their pricing:&lt;/strong&gt; $89-99/seat/month. Includes platform access, predictive dialing, built-in CRM, basic reporting, and cloud recording. DIDs ($1-3/month each), minute overages, premium reporting, and onboarding ($500-2,000) are extra. Realistic all-in for 50 seats: &lt;strong&gt;$110-130/seat/month&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where CallTools wins:&lt;/strong&gt; Ease of use. The interface is modern, the setup is fast, and a non-technical manager can run campaigns without touching a config file. Bundled minutes keep the billing predictable for small teams. Good onboarding support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where VICIdial wins:&lt;/strong&gt; CallTools has three dialing modes. VICIdial has five (predictive, progressive, manual, ratio, and adaptive). VICIdial's AMD is more tunable. API access on VICIdial is free and unrestricted; CallTools may charge extra. At 50 seats, VICIdial saves roughly $30,000-50,000/year. At 100+ seats, CallTools doesn't even try to compete on price.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who should pick CallTools:&lt;/strong&gt; Small outbound teams (10-30 seats) that want a turnkey solution and don't have the technical staff to manage VICIdial infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/blog/vicidial-vs-calltools/"&gt;Read the full comparison: VICIdial vs CallTools --&amp;gt;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  VICIdial vs XenCall
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Their pricing:&lt;/strong&gt; $125-150/seat/month. Starter at ~$125/seat, Professional at ~$150/seat, Enterprise at custom pricing. DIDs, toll-free numbers, SMS, and onboarding are additional. Realistic all-in for 50 seats: &lt;strong&gt;$140-165/seat/month&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where XenCall wins:&lt;/strong&gt; The built-in CRM is genuinely full-featured -- not just lead management like VICIdial, but pipeline tracking, deal stages, and sales workflow automation. The agent UI is polished and modern. Setup takes hours, not days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where VICIdial wins:&lt;/strong&gt; Cost and flexibility. XenCall at 50 seats costs roughly $90,000/year. VICIdial at 50 seats costs roughly $40,000-55,000/year. VICIdial has more dialing modes, deeper AMD configuration, unlimited API access, and no plan-dependent agent caps. If you need a CRM, you integrate one (Salesforce, SuiteCRM, whatever fits) instead of being locked into XenCall's.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who should pick XenCall:&lt;/strong&gt; Sales teams that want CRM and dialer in one product and don't want to manage integrations. Operations under 30 seats where the premium buys meaningful time savings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/blog/vicidial-vs-xencall/"&gt;Read the full comparison: VICIdial vs XenCall --&amp;gt;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  VICIdial vs GoHighLevel
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Their pricing:&lt;/strong&gt; $97/month (Starter), $297/month (Unlimited), or $497/month (SaaS Pro). Not per-seat. Sounds amazing until you read the rate limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where GoHighLevel wins:&lt;/strong&gt; Marketing automation. Funnels, email drip campaigns, SMS sequences, review management, calendar booking, pipeline CRM -- all in one white-label package. For a 5-person sales team that also needs landing pages and email marketing, GHL is genuinely good value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where GoHighLevel falls apart:&lt;/strong&gt; 10 outbound calls per minute. Across the entire sub-account. Not per agent -- total. Daily cap of 1,000 calls. 1:1 dialing only (no predictive). A single VICIdial server handles more calls in an hour than GHL allows in a day. We've had four clients try to run 50-seat floors on GHL in the past year. All four came back within six weeks asking us to spin up VICIdial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who should pick GoHighLevel:&lt;/strong&gt; Marketing agencies and small sales teams (under 10 people) doing follow-up calls, not high-volume outbound. If you need a dialer, GHL is not it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/blog/vicidial-vs-gohighlevel/"&gt;Read the full comparison: VICIdial vs GoHighLevel --&amp;gt;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Concept Comparisons
&lt;/h2&gt;

&lt;p&gt;Not head-to-head product matchups, but foundational decisions that affect which category of platform you should even be looking at.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hosted vs Self-Hosted Predictive Dialer (2026 Update)
&lt;/h3&gt;

&lt;p&gt;The definitive cost breakdown at five scale points: 25, 50, 100, 200, and 500 seats. Seven hosted platforms priced against self-hosted VICIdial with every line item visible. The crossover point where self-hosted starts winning is lower than most vendors want you to believe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/blog/hosted-vs-self-hosted-predictive-dialer-2026/"&gt;Read: Hosted vs Self-Hosted Predictive Dialer 2026 --&amp;gt;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Also available: &lt;a href="https://dev.to/blog/hosted-vs-self-hosted-dialer-cost/"&gt;Original Hosted vs Self-Hosted cost analysis&lt;/a&gt; with 3-year TCO models for nine platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Predictive vs Progressive vs Preview Dialing
&lt;/h3&gt;

&lt;p&gt;The dialing mode you pick for a campaign is the single most impactful configuration decision in outbound calling. This guide covers how each mode works technically, how VICIdial implements them, compliance implications, agent count thresholds, and a decision framework you can apply to any campaign.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/blog/predictive-vs-progressive-dialing/"&gt;Read: Predictive vs Progressive vs Preview Dialing --&amp;gt;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  VICIdial AMD vs AI-Based AMD
&lt;/h3&gt;

&lt;p&gt;Traditional CPD-based answering machine detection versus machine learning models. Technical breakdown of how each approach works at the signal-processing level, real-world accuracy benchmarks, latency impacts on agent utilization, and cost-per-call math. When to stick with VICIdial's built-in AMD and when to bolt on an AI model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/blog/vicidial-amd-vs-ai-amd/"&gt;Read: VICIdial AMD vs AI-Based AMD --&amp;gt;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Call Center Software Comparison: Buyer's Guide
&lt;/h3&gt;

&lt;p&gt;The broader landscape. 10+ platforms compared using a decision framework organized by team size, budget, and use case. No affiliate links. Written from 15+ years of hands-on operations managing everything from 10-seat insurance shops to 1,600-agent BPOs processing 6 million calls per day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/blog/call-center-software-comparison/"&gt;Read: Call Center Software Buyer's Guide 2026 --&amp;gt;&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pricing Table Nobody Else Publishes
&lt;/h2&gt;

&lt;p&gt;Every vendor on one page. Published seat prices vs. realistic all-in costs for a 100-seat outbound operation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Published Seat Price&lt;/th&gt;
&lt;th&gt;Realistic All-In (100 seats)&lt;/th&gt;
&lt;th&gt;Annual Cost vs. VICIdial&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;VICIdial&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0 (open source)&lt;/td&gt;
&lt;td&gt;$60-130/seat/mo&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GoHighLevel&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$97-497/mo (flat)&lt;/td&gt;
&lt;td&gt;Not viable at 100 seats&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CallTools&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$89-99/seat&lt;/td&gt;
&lt;td&gt;$110-130/seat/mo&lt;/td&gt;
&lt;td&gt;+$60K-84K/yr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RingCentral RingCX&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$65/seat&lt;/td&gt;
&lt;td&gt;$90-120/seat/mo&lt;/td&gt;
&lt;td&gt;+$36K-108K/yr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;XenCall&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$125-150/seat&lt;/td&gt;
&lt;td&gt;$140-165/seat/mo&lt;/td&gt;
&lt;td&gt;+$96K-168K/yr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Convoso&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~$90-200/seat&lt;/td&gt;
&lt;td&gt;$175-250/seat/mo&lt;/td&gt;
&lt;td&gt;+$138K-228K/yr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Five9&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$159/seat (Core)&lt;/td&gt;
&lt;td&gt;$200-275/seat/mo&lt;/td&gt;
&lt;td&gt;+$168K-276K/yr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ReadyMode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$150-199/seat&lt;/td&gt;
&lt;td&gt;$200-280/seat/mo&lt;/td&gt;
&lt;td&gt;+$168K-300K/yr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Genesys CX 3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$155/seat&lt;/td&gt;
&lt;td&gt;$200-400/seat/mo&lt;/td&gt;
&lt;td&gt;+$168K-408K/yr&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ranges are wide because telecom costs vary by volume, carrier choice, and geography. The point isn't the exact number -- it's the magnitude. At 100 seats, you're paying an extra $60K-400K per year for the convenience of not managing your own servers. For some operations, that's a fair trade. For most, it isn't.&lt;/p&gt;

&lt;p&gt;Quick way to calculate your own breakeven:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Calculate annual savings of VICIdial vs any hosted platform&lt;/span&gt;
&lt;span class="c"&gt;# Usage: bash cost-compare.sh &amp;lt;hosted_per_seat&amp;gt; &amp;lt;seat_count&amp;gt;&lt;/span&gt;
&lt;span class="nv"&gt;HOSTED_SEAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;175&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;   &lt;span class="c"&gt;# e.g., Convoso mid-range&lt;/span&gt;
&lt;span class="nv"&gt;SEATS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;100&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="nv"&gt;VICIDIAL_SEAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;95        &lt;span class="c"&gt;# managed hosting, 100-seat cluster&lt;/span&gt;

&lt;span class="nv"&gt;HOSTED_ANNUAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; HOSTED_SEAT &lt;span class="o"&gt;*&lt;/span&gt; SEATS &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;
&lt;span class="nv"&gt;VICIDIAL_ANNUAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; VICIDIAL_SEAT &lt;span class="o"&gt;*&lt;/span&gt; SEATS &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;
&lt;span class="nv"&gt;SAVINGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; HOSTED_ANNUAL &lt;span class="o"&gt;-&lt;/span&gt; VICIDIAL_ANNUAL &lt;span class="k"&gt;))&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Hosted: &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOSTED_ANNUAL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/yr | VICIdial: &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VICIDIAL_ANNUAL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/yr"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Annual savings: &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SAVINGS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="c"&gt;# At 100 seats vs Convoso: $96,000/year savings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Which VICIdial alternative is best for a small team (under 25 seats)?
&lt;/h3&gt;

&lt;p&gt;If you don't have Linux expertise in-house, &lt;a href="https://dev.to/blog/vicidial-vs-calltools/"&gt;CallTools&lt;/a&gt; or &lt;a href="https://dev.to/blog/vicidial-vs-ringcentral/"&gt;RingCentral RingCX&lt;/a&gt; are reasonable choices at this scale. The cost differential is smaller with fewer seats, and the setup time savings matter more. But managed VICIdial hosting (through providers like ViciHost or us) starts around $75-90/seat and handles all the infrastructure work for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which alternative is best for enterprise (200+ seats)?
&lt;/h3&gt;

&lt;p&gt;At 200+ seats, VICIdial's cost advantage becomes massive -- $300K-800K/year in savings over hosted platforms. The only scenario where enterprise alternatives make sense is when you need true omnichannel (voice + chat + email + social) in a single platform with unified reporting. In that case, &lt;a href="https://dev.to/blog/vicidial-vs-genesys-2026/"&gt;Genesys Cloud CX&lt;/a&gt; is the strongest option, though you'll pay for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is VICIdial really free?
&lt;/h3&gt;

&lt;p&gt;The software is free. The licensing cost is $0, forever. It's open-source under AGPLv2. The costs are infrastructure (servers, hosting, SIP trunking) and labor (a sysadmin who knows Linux and Asterisk, or a managed hosting provider). All-in, that works out to &lt;a href="https://dev.to/blog/hosted-vs-self-hosted-predictive-dialer-2026/"&gt;$60-130/seat/month&lt;/a&gt; depending on scale and setup. Still 50-80% cheaper than hosted alternatives at 50+ seats.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why doesn't Convoso publish their pricing?
&lt;/h3&gt;

&lt;p&gt;Because their margins depend on information asymmetry. If every prospect knew that Convoso charges $130-200/seat for a platform that &lt;a href="https://dev.to/blog/vicidial-vs-convoso-2026/"&gt;started as a VICIdial reseller&lt;/a&gt;, the sales conversation would go differently. Unpublished pricing lets them charge different rates based on what they think you'll pay. Five9, NICE, and Genesys do the same thing for their upper tiers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can VICIdial match Five9's AI features?
&lt;/h3&gt;

&lt;p&gt;Not out of the box. Five9's real-time agent coaching, automated QA, and IVA are ahead of VICIdial's built-in capabilities. But VICIdial's open architecture means you can &lt;a href="https://dev.to/blog/vicidial-amd-vs-ai-amd/"&gt;bolt on AI models&lt;/a&gt; for AMD, integrate with open-source speech analytics, and build custom agent assist tools using the API.&lt;/p&gt;

&lt;p&gt;Example: tuning VICIdial's built-in AMD to get 92%+ accuracy (which matches what Five9 advertises):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# extensions.conf — AMD() parameters tuned for outbound sales
; These values reduce false positives from ~20% to under 5%
&lt;/span&gt;&lt;span class="py"&gt;cpd_amd_maximum_word_length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;2500&lt;/span&gt;
&lt;span class="py"&gt;cpd_amd_maximum_number_of_words&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;5&lt;/span&gt;
&lt;span class="py"&gt;cpd_amd_between_words_silence&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;800&lt;/span&gt;
&lt;span class="py"&gt;cpd_amd_minimum_word_length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;100&lt;/span&gt;
&lt;span class="py"&gt;cpd_amd_total_analysis_time&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;5000&lt;/span&gt;
&lt;span class="py"&gt;cpd_amd_silence_threshold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;256&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The question is whether your team has the engineering capacity to build what Five9 gives you pre-packaged. If yes, VICIdial + custom AI costs a fraction. If no, Five9's AI premium is arguably justified.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about platforms not on this list?
&lt;/h3&gt;

&lt;p&gt;We're working on comparisons for NICE CXone, Talkdesk, Dialpad AI Contact Center, and 8x8. In the meantime, our &lt;a href="https://dev.to/blog/call-center-software-comparison/"&gt;Call Center Software Buyer's Guide&lt;/a&gt; covers the broader landscape.&lt;/p&gt;




&lt;h2&gt;
  
  
  Our Offer
&lt;/h2&gt;

&lt;p&gt;We increase call center conversions by 50% in 2 weeks. $5K total ($1K down, $4K on completion). $1,500/month continuity after that. We do this by optimizing VICIdial's predictive algorithm, AMD settings, dial ratios, caller ID management, and agent workflow -- the same variables we compare across every platform on this page.&lt;/p&gt;

&lt;p&gt;If you're running VICIdial and want better numbers, or running a hosted platform and want to see what a migration would save you, &lt;a href="https://dev.to/contact/"&gt;contact us&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>voip</category>
      <category>asterisk</category>
      <category>sysadmin</category>
      <category>devops</category>
    </item>
    <item>
      <title>VICIdial vs Convoso in 2026: Updated Pricing, Features, and What Convoso Still Won't Tell You</title>
      <dc:creator>Jason Shouldice</dc:creator>
      <pubDate>Sat, 28 Mar 2026 01:04:16 +0000</pubDate>
      <link>https://dev.to/gamlin/vicidial-vs-convoso-in-2026-updated-pricing-features-and-what-convoso-still-wont-tell-you-5hcn</link>
      <guid>https://dev.to/gamlin/vicidial-vs-convoso-in-2026-updated-pricing-features-and-what-convoso-still-wont-tell-you-5hcn</guid>
      <description>&lt;p&gt;In 2006, two brothers in Woodland Hills, California started a company called SafeSoft Solutions. Their product was MarketDialer -- a &lt;a href="https://dev.to/blog/hosted-vs-self-hosted-predictive-dialer-2026/"&gt;hosted predictive&lt;/a&gt; dialer built on VICIdial's open-source code. They ran that VICIdial-based platform for eleven years, rebranded to Convoso in September 2016, and eventually rebuilt their platform from scratch using everything they'd learned from VICIdial's codebase and customer base.&lt;/p&gt;

&lt;p&gt;That's not a rumor. That's from Convoso's own marketing content.&lt;/p&gt;

&lt;p&gt;Twenty years later, Convoso has grown into one of the most aggressive competitors in the outbound dialer space. Their marketing targets VICIdial users directly. They rank for every "VICIdial vs" keyword. They publish articles with titles about VICIdial's "hidden costs." And their sales team knows exactly which pain points to hit because they spent over a decade inside the VICIdial ecosystem.&lt;/p&gt;

&lt;p&gt;Some of their criticisms are valid. VICIdial does have a learning curve. The admin UI is dated. Self-hosting requires real technical skill. Convoso didn't invent those problems; they experienced them and then charged people $130-200/seat/month to avoid them.&lt;/p&gt;

&lt;p&gt;The question is whether that markup is justified. In 2026, with updated pricing data and a year of Convoso's Ignite &lt;a href="https://dev.to/blog/contact-rate-optimization/"&gt;DID management&lt;/a&gt; tool in the market, the answer is more nuanced than either side wants to admit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Convoso's Pricing: Still Opaque, Still Expensive
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/blog/vicidial-vs-convoso/"&gt;Convoso doesn&lt;/a&gt;'t publish pricing. They haven't since they launched. To get a number, you have to book a demo, sit through a sales pitch, and negotiate. That's a red flag, but it's also standard for enterprise SaaS. Five9, NICE, and Genesys do the same thing for their upper tiers.&lt;/p&gt;

&lt;p&gt;What we know from third-party sources and verified user reports:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Reported Starting Price&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Capterra (2026)&lt;/td&gt;
&lt;td&gt;~$90/user/month&lt;/td&gt;
&lt;td&gt;"Starting price" for small teams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudTalk (2026)&lt;/td&gt;
&lt;td&gt;$90/user/month base&lt;/td&gt;
&lt;td&gt;Plus carrier fees and add-ons&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GetApp (2026)&lt;/td&gt;
&lt;td&gt;Custom pricing&lt;/td&gt;
&lt;td&gt;No published figure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verified user reports&lt;/td&gt;
&lt;td&gt;$130-200/user/month&lt;/td&gt;
&lt;td&gt;Depends on seat count, features, contract length&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Industry analysis&lt;/td&gt;
&lt;td&gt;$150-175/seat typical&lt;/td&gt;
&lt;td&gt;For 50-100 seat outbound operations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What the seat price doesn't include:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Carrier fees.&lt;/strong&gt; Convoso explicitly states that "standard carrier fees (associated with telecom usage)" are billed separately. For high-volume outbound, expect $0.01-0.02/minute on top of seat licensing. At 100 seats doing 200+ dials per agent per day, that's $8,000-15,000/month in telecom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Omnichannel add-on.&lt;/strong&gt; Email and SMS capabilities are sold as optional add-ons, not included in the base seat price.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DID costs.&lt;/strong&gt; Their Ignite DID management system procures and rotates numbers, but those numbers aren't free. DID costs at scale can run $500-2,000/month depending on rotation volume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Annual commitment.&lt;/strong&gt; Convoso "primarily offers annual plans." They do offer month-to-month, but the pricing is significantly higher. Their current promo is "20% off annual plans," which means the monthly price is 25% higher than the annual price.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Realistic all-in cost for a 100-seat outbound operation:&lt;/strong&gt; $175-250/seat/month when you add telecom, DIDs, and omnichannel.&lt;/p&gt;

&lt;p&gt;Compare that to VICIdial at $65-128/seat/month all-in (servers, SIP, admin, DIDs). Even at the high end, VICIdial is half the cost.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Convoso Built That's Actually Good
&lt;/h2&gt;

&lt;p&gt;I'll give Convoso credit where it's earned. They didn't just rebrand VICIdial with a new logo. They built several things that genuinely address pain points in the outbound dialing space.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Ignite DID Management
&lt;/h3&gt;

&lt;p&gt;This is Convoso's strongest differentiator and the feature that makes their pricing semi-justifiable for some operations.&lt;/p&gt;

&lt;p&gt;Ignite uses AI to score DID health across carriers, automatically procures new numbers when existing ones get flagged, and selects the optimal caller ID for each dial in real time. Users report up to 50% higher &lt;a href="https://dev.to/blog/contact-rate-optimization-guide/"&gt;contact rates&lt;/a&gt; after enabling Ignite.&lt;/p&gt;

&lt;p&gt;DID reputation management is a real problem. Carriers flag numbers that generate high call volumes, and flagged numbers get blocked or marked as "Spam Likely." Managing this manually in VICIdial means monitoring DID health with third-party tools, rotating numbers by hand, and replacing burned numbers reactively instead of proactively.&lt;/p&gt;

&lt;p&gt;VICIdial handles &lt;a href="https://dev.to/blog/vicidial-caller-id-reputation/"&gt;DID rotation&lt;/a&gt; natively -- you can assign multiple caller IDs per campaign and rotate them -- but it doesn't have AI-powered health scoring or automatic procurement. You need to build that workflow yourself or use external tools like CallerID Reputation from Quality Voice &amp;amp; Data.&lt;/p&gt;

&lt;p&gt;This is a legitimate advantage. If DID management is eating your team's time and killing your contact rates, Convoso's Ignite addresses it more elegantly than anything in VICIdial's native toolset.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Voicemail Detection
&lt;/h3&gt;

&lt;p&gt;Convoso's answering &lt;a href="https://dev.to/blog/vicidial-amd-vs-ai-amd/"&gt;machine detection&lt;/a&gt; (AMD) is reportedly more accurate than the standard VICIdial AMD, with users calling it "hyper-accurate." Better AMD means fewer false positives (live humans sent to voicemail messages) and fewer false negatives (agents connected to answering machines).&lt;/p&gt;

&lt;p&gt;But VICIdial's AMD is highly configurable. The difference between "AMD doesn't work" and "AMD works great" in VICIdial is usually a matter of tuning the detection parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;;&lt;/span&gt; &lt;span class="err"&gt;VICIdial&lt;/span&gt; &lt;span class="err"&gt;AMD&lt;/span&gt; &lt;span class="err"&gt;tuning&lt;/span&gt; &lt;span class="err"&gt;parameters&lt;/span&gt;
&lt;span class="py"&gt;AMD_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AMD&lt;/span&gt;
&lt;span class="py"&gt;AMD_send_to_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Y&lt;/span&gt;
&lt;span class="py"&gt;CPD_AMD_action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MESSAGE&lt;/span&gt;
&lt;span class="py"&gt;amd_initial_silence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2600&lt;/span&gt;
&lt;span class="py"&gt;amd_greeting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1500&lt;/span&gt;
&lt;span class="py"&gt;amd_after_greeting_silence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;800&lt;/span&gt;
&lt;span class="py"&gt;amd_total_analysis_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5000&lt;/span&gt;
&lt;span class="py"&gt;amd_minimum_word_length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;100&lt;/span&gt;
&lt;span class="py"&gt;amd_between_words_silence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;50&lt;/span&gt;
&lt;span class="py"&gt;amd_maximum_number_of_words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5&lt;/span&gt;
&lt;span class="py"&gt;amd_silence_threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;256&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most VICIdial deployments running stock AMD settings are underperforming because nobody tuned those parameters. We've improved AMD accuracy from 60% to 92%+ just by adjusting those values to match the specific SIP trunk characteristics of the deployment. But Convoso's AMD works well out of the box without tuning -- and for operations without a VICIdial expert, that matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Modern UI and Onboarding
&lt;/h3&gt;

&lt;p&gt;Convoso's agent interface is modern, clean, and intuitive. New agents can be productive in hours instead of days. The admin interface is similarly polished, with drag-and-drop campaign builders and visual reporting dashboards.&lt;/p&gt;

&lt;p&gt;VICIdial's agent interface is functional but sparse. The admin panel is dense with settings and requires training. This is a real cost -- not in dollars, but in onboarding time and agent comfort. For operations with high agent turnover (common in outbound), the difference in ramp-up time adds up.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. STIR/SHAKEN Attestation
&lt;/h3&gt;

&lt;p&gt;Convoso signs outbound calls with full (A-level) STIR/SHAKEN attestation, which helps calls display as verified rather than "Spam Likely." VICIdial supports STIR/SHAKEN through the SIP carrier, but the implementation depends on your trunk provider's capabilities. Convoso handles it natively.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Convoso Falls Short
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Dialer Configurability
&lt;/h3&gt;

&lt;p&gt;Convoso's predictive dialer works. It auto-adjusts pacing based on agent availability and answer rates. But you can't tune it with the same granularity as VICIdial.&lt;/p&gt;

&lt;p&gt;In VICIdial, you control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dial method (ratio, adapt average, adapt hard limit, adapt tapered)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/blog/vicidial-auto-dial-level-tuning/"&gt;Auto dial level&lt;/a&gt; with manual override&lt;/li&gt;
&lt;li&gt;Adaptive maximum and minimum levels&lt;/li&gt;
&lt;li&gt;Adaptive dropped percentage targets&lt;/li&gt;
&lt;li&gt;Dial intensity curves&lt;/li&gt;
&lt;li&gt;Available-only ratio calculations&lt;/li&gt;
&lt;li&gt;Concurrent calls per agent limits&lt;/li&gt;
&lt;li&gt;Campaign-level and list-level dial rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Convoso gives you predictive, power, and preview modes with some configuration options. But the deep algorithmic tuning that lets you optimize the tradeoff between agent idle time and abandon rates to the tenth of a percent -- that's VICIdial territory.&lt;/p&gt;

&lt;p&gt;In VICIdial, a performance audit takes 10 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check current dial levels and drop rates per campaign&lt;/span&gt;
mysql &lt;span class="nt"&gt;-u&lt;/span&gt; cron vicidial &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"SELECT campaign_id, auto_dial_level, adaptive_dropped_percentage,
   adaptive_maximum_level FROM vicidial_campaigns WHERE active='Y';"&lt;/span&gt;

&lt;span class="c"&gt;# Monitor real-time agent status via port 8089 webphone connections&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"http show status"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; connection

&lt;span class="c"&gt;# Tail the hopper activity to verify lead loading&lt;/span&gt;
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/astguiclient/VD_hopper.log | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'LOADED'&lt;/span&gt;

&lt;span class="c"&gt;# Check SIP registrations on port 5060&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip show peers"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"OK"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Convoso, you'd open a ticket and wait for their support team to tell you what they can adjust.&lt;/p&gt;

&lt;p&gt;For a 200-seat operation where a 0.5% improvement in agent utilization means $300K in annual revenue, that configurability isn't a nice-to-have. It's the difference between making money and bleeding it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Pricing Transparency
&lt;/h3&gt;

&lt;p&gt;This matters more than most people think. When your platform vendor won't publish their prices, every contract negotiation starts from a position of information asymmetry. You don't know what the person down the street is paying. You don't know what the list price is versus the negotiated price. You don't know if your renewal rate will jump 20% because "costs have increased."&lt;/p&gt;

&lt;p&gt;VICIdial costs what servers, SIP trunks, and labor cost. Those prices are publicly available and competitively sourced. Convoso costs whatever their sales team decides your operation can afford.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Dropped Call Reports
&lt;/h3&gt;

&lt;p&gt;Multiple user reviews on G2 and Capterra mention call drops and system downtime. One reviewer reported "constant issues with insane amounts of dropped calls." Another mentioned "days where the system is down for hours at a time at least a couple times a month."&lt;/p&gt;

&lt;p&gt;Those are user reports, not verified metrics, so take them with a grain of salt. But they appear across multiple independent review sources. VICIdial can absolutely have &lt;a href="https://dev.to/blog/vicidial-carrier-selection/"&gt;call quality&lt;/a&gt; issues too -- usually traced to SIP trunk problems, Asterisk configuration, or server overload -- but when that happens, you have direct access to the logs, the packet captures, and the fix.&lt;/p&gt;

&lt;p&gt;With Convoso, you open a ticket and wait.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Data Portability
&lt;/h3&gt;

&lt;p&gt;Convoso is a hosted platform. Your data -- call recordings, lead disposition histories, agent performance records -- lives on their infrastructure. Migrating away means extracting that data through their API or export tools, which may not capture everything.&lt;/p&gt;

&lt;p&gt;With VICIdial, your data lives on your servers. &lt;code&gt;mysqldump&lt;/code&gt; the database, copy the recordings directory, and you have everything. No negotiations, no API rate limits, no "we'll get back to you on that export request."&lt;/p&gt;

&lt;h3&gt;
  
  
  5. No Free Trial
&lt;/h3&gt;

&lt;p&gt;Convoso doesn't offer a free trial. They offer "live customized demos." You can't test the platform with your actual data, your actual agents, and your actual campaigns before committing to an annual contract.&lt;/p&gt;

&lt;p&gt;VICIdial is free to download and install. You can run it in a test environment for as long as you want, with real data, before making any commitment. ViciBox provides a turnkey install ISO that gets you &lt;a href="https://dev.to/blog/vicidial-setup-guide/"&gt;from bare metal to&lt;/a&gt; a working dialer in under an hour.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Feature Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;VICIdial&lt;/th&gt;
&lt;th&gt;Convoso&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Predictive dialing&lt;/td&gt;
&lt;td&gt;Yes (6 dial methods, deep tuning)&lt;/td&gt;
&lt;td&gt;Yes (predictive, power, preview)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AMD&lt;/td&gt;
&lt;td&gt;Yes (highly tunable, requires config)&lt;/td&gt;
&lt;td&gt;Yes (AI-enhanced, works out of box)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DID rotation&lt;/td&gt;
&lt;td&gt;Yes (campaign-level, manual management)&lt;/td&gt;
&lt;td&gt;Yes (Ignite AI-powered, auto-procurement)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;STIR/SHAKEN&lt;/td&gt;
&lt;td&gt;Via carrier&lt;/td&gt;
&lt;td&gt;Native A-level attestation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/blog/dynamic-scripting-for-call-centers/"&gt;Dynamic scripting&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Yes (built-in)&lt;/td&gt;
&lt;td&gt;Yes (built-in)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lead management&lt;/td&gt;
&lt;td&gt;Yes (list loading, filters, recycling)&lt;/td&gt;
&lt;td&gt;Yes (smart queue, priority routing)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inbound + blended&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IVR&lt;/td&gt;
&lt;td&gt;Yes (Asterisk-powered, AGI extensible)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Call recording&lt;/td&gt;
&lt;td&gt;Yes (unlimited, local storage)&lt;/td&gt;
&lt;td&gt;Yes (cloud storage, limits may apply)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dev.to/blog/vicidial-realtime-agent-dashboard/"&gt;Real-time monitoring&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Yes (whisper, barge, listen)&lt;/td&gt;
&lt;td&gt;Yes (plus AI coaching)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRM integration&lt;/td&gt;
&lt;td&gt;Custom API/AGI&lt;/td&gt;
&lt;td&gt;Native (&lt;a href="https://dev.to/blog/vicidial-crm-integration/"&gt;Salesforce, HubSpot&lt;/a&gt;, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compliance tools&lt;/td&gt;
&lt;td&gt;Manual DNC, time-zone management&lt;/td&gt;
&lt;td&gt;Integrated DNC, consent tracking, reassigned number detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Omnichannel&lt;/td&gt;
&lt;td&gt;Voice + email + basic chat&lt;/td&gt;
&lt;td&gt;Voice + email + SMS (add-on)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reporting&lt;/td&gt;
&lt;td&gt;50+ reports + custom SQL&lt;/td&gt;
&lt;td&gt;Customizable dashboards + real-time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;AGI, AMI, &lt;a href="https://dev.to/blog/vicidial-api-integration/"&gt;non-agent API&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/blog/vicidial-golang-api-client/"&gt;REST API&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent UI&lt;/td&gt;
&lt;td&gt;Functional (dated design)&lt;/td&gt;
&lt;td&gt;Modern, intuitive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Admin UI&lt;/td&gt;
&lt;td&gt;Dense, powerful, steep learning curve&lt;/td&gt;
&lt;td&gt;Clean, visual, faster onboarding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source code access&lt;/td&gt;
&lt;td&gt;Yes (open source, GPL)&lt;/td&gt;
&lt;td&gt;No (proprietary)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile app&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;Free software + infrastructure costs&lt;/td&gt;
&lt;td&gt;$90-200+/seat/month + telecom&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Origin Story Context
&lt;/h2&gt;

&lt;p&gt;It would be irresponsible to write this comparison without addressing the elephant in the room.&lt;/p&gt;

&lt;p&gt;Convoso ran on VICIdial's code for eleven years. During that time, they learned every weakness, every pain point, every complaint from their VICIdial-based customers. They then built a proprietary platform designed to address those exact pain points.&lt;/p&gt;

&lt;p&gt;This is smart business. Nobody's arguing otherwise.&lt;/p&gt;

&lt;p&gt;But it also means Convoso's marketing against VICIdial is based on intimate insider knowledge. When they publish articles about VICIdial's "hidden costs," they know exactly which costs to highlight because they dealt with those costs themselves. When they target VICIdial users with comparison content, they know exactly which pain points to press.&lt;/p&gt;

&lt;p&gt;What they don't highlight is that VICIdial has also evolved since 2006. The platform in 2026 is dramatically different from the codebase Convoso forked two decades ago:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent webphone&lt;/strong&gt; with WebRTC support (no softphone needed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST-ish API&lt;/strong&gt; for external integration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic scripting&lt;/strong&gt; engine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time agent monitoring&lt;/strong&gt; with &lt;a href="https://dev.to/blog/vicidial-whisper-coaching/"&gt;whisper and barge&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-server clustering&lt;/strong&gt; for high availability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encrypted recordings&lt;/strong&gt; and TLS transport&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/blog/stir-shaken-vicidial-guide/"&gt;STIR/SHAKEN support&lt;/a&gt;&lt;/strong&gt; via carrier integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The VICIdial of 2006 that Convoso describes in their marketing and the VICIdial of 2026 are not the same platform. The core architecture is stable (which is a feature, not a bug), but the capabilities have expanded significantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Decision Framework
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose Convoso If:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;DID reputation management is your #1 pain point and you want an automated solution&lt;/li&gt;
&lt;li&gt;Your operation has high agent turnover and onboarding speed matters more than per-seat cost&lt;/li&gt;
&lt;li&gt;You need polished CRM integrations (Salesforce, HubSpot) without custom development&lt;/li&gt;
&lt;li&gt;You don't have Linux/Asterisk expertise and don't want to acquire it&lt;/li&gt;
&lt;li&gt;Your per-lead margins can absorb $175-250/seat/month all-in&lt;/li&gt;
&lt;li&gt;Compliance automation (reassigned number detection, consent tracking) is critical for your vertical&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose VICIdial If:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need granular control over predictive dialing algorithms&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/blog/call-center-cost-per-lead-benchmarks/"&gt;Cost per lead&lt;/a&gt; is a primary business metric and you need to keep it tight&lt;/li&gt;
&lt;li&gt;You're scaling past 100 seats and linear per-seat pricing will kill your margins&lt;/li&gt;
&lt;li&gt;Data ownership and portability are non-negotiable&lt;/li&gt;
&lt;li&gt;You need deep customization that goes beyond what any SaaS API allows&lt;/li&gt;
&lt;li&gt;You want to avoid annual contracts and vendor lock-in&lt;/li&gt;
&lt;li&gt;You have (or will hire) the technical team to manage infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose VICIdial + Professional Optimization If:
&lt;/h3&gt;

&lt;p&gt;You're already running VICIdial and considering Convoso because your contact rates are low, your AMD isn't working, or your DID management is a mess.&lt;/p&gt;

&lt;p&gt;Before spending $175+/seat/month, get your existing deployment tuned. We've taken VICIdial operations from mediocre to market-leading by optimizing dialer settings, &lt;a href="https://dev.to/blog/vicidial-amd-guide/"&gt;AMD configuration&lt;/a&gt;, lead recycling rules, and DID rotation strategies. Most "VICIdial doesn't work" problems are configuration problems, not platform problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our offer: we'll audit your VICIdial deployment and increase your conversions by 50% in two weeks. $5,000 flat ($1,000 down, $4,000 on delivery). If we don't hit the target, you keep the $4,000.&lt;/strong&gt; &lt;a href="https://vicistack.com/contact/" rel="noopener noreferrer"&gt;Get started at vicistack.com&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Scenario: 100-Seat Outbound Solar Lead Generation
&lt;/h2&gt;

&lt;p&gt;Let's run the math on a specific scenario. You run 100 agents doing outbound &lt;a href="https://dev.to/blog/vicidial-solar-lead-generation/"&gt;solar lead generation&lt;/a&gt;. You need predictive dialing, AMD, DID rotation, call recording, and CRM integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Convoso
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Line Item&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Seat licenses (100 x $150 est.)&lt;/td&gt;
&lt;td&gt;$15,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carrier fees (100 agents, &lt;a href="https://dev.to/blog/vicidial-performance-tuning/"&gt;high volume&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;$10,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DID procurement (Ignite)&lt;/td&gt;
&lt;td&gt;$800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMS add-on (50 agents)&lt;/td&gt;
&lt;td&gt;$2,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monthly total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$27,800&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Annual total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$333,600&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  VICIdial (Self-Hosted, Managed by ViciStack)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Line Item&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Server cluster (3 servers, Hetzner)&lt;/td&gt;
&lt;td&gt;$600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIP trunking (Telnyx, high volume)&lt;/td&gt;
&lt;td&gt;$5,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DID rotation (200 numbers)&lt;/td&gt;
&lt;td&gt;$300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed hosting + monitoring&lt;/td&gt;
&lt;td&gt;$2,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMS via SignalWire&lt;/td&gt;
&lt;td&gt;$800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monthly total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$9,200&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Annual total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$110,400&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Annual savings with VICIdial: $223,200.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's enough to hire two full-time VICIdial admins and still come out $80K ahead. Or you could reinvest that into leads -- at $30/lead, that's 7,440 additional leads per year.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Convoso built a solid outbound dialer platform. Their Ignite DID management is the best in the hosted space. Their UI is clean. Their AMD works out of the box. If you value those things and your margins support the pricing, Convoso is a reasonable choice.&lt;/p&gt;

&lt;p&gt;But "reasonable" and "optimal" aren't the same thing. For high-volume outbound operations where per-seat economics matter, where you need deep dialer tuning, and where data ownership is non-negotiable, VICIdial remains the more powerful and cost-effective platform -- especially with professional optimization.&lt;/p&gt;

&lt;p&gt;Convoso spent eleven years on VICIdial's code and built something good. VICIdial spent twenty-three years refining its code and built something that's still the foundation 14,000+ installations run on worldwide. Both deserve respect. Your job is picking the one that matches your specific operational reality, not the one with the better landing page.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Last updated: March 2026. Convoso pricing estimates from Capterra, CloudTalk, and verified user reports. VICIdial costs from production deployments managed by ViciStack.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on the ViciStack blog.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>voip</category>
      <category>asterisk</category>
      <category>sysadmin</category>
      <category>devops</category>
    </item>
    <item>
      <title>Hosted vs Self-Hosted Predictive Dialer in 2026: The Real Cost Breakdown at Every Scale</title>
      <dc:creator>Jason Shouldice</dc:creator>
      <pubDate>Sat, 28 Mar 2026 01:04:07 +0000</pubDate>
      <link>https://dev.to/gamlin/hosted-vs-self-hosted-predictive-dialer-in-2026-the-real-cost-breakdown-at-every-scale-3mkm</link>
      <guid>https://dev.to/gamlin/hosted-vs-self-hosted-predictive-dialer-in-2026-the-real-cost-breakdown-at-every-scale-3mkm</guid>
      <description>&lt;p&gt;The predictive dialer market is projected to hit $6.1 billion by 2034, growing at 7% annually. Every year, more vendors enter the hosted space with slick demos and "starting at" prices that look reasonable until invoice month three.&lt;/p&gt;

&lt;p&gt;And every year, the same operations managers run the same spreadsheet and arrive at the same conclusion: &lt;a href="https://dev.to/blog/hosted-vs-self-hosted-dialer-cost/"&gt;hosted dialers&lt;/a&gt; are overpriced for what they deliver, but self-hosted dialers are underpriced for what they demand.&lt;/p&gt;

&lt;p&gt;This article resolves that tension with real numbers. We priced out seven hosted platforms and self-hosted VICIdial at five different scale points (25, 50, 100, 200, and 500 seats), including every cost component that affects total cost of ownership. No "starting at" marketing numbers. No hidden line items on either side.&lt;/p&gt;

&lt;p&gt;If you're evaluating dialer platforms and need to present a cost comparison to your CFO, bookmark this page.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Two Models, Stripped Down
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hosted (cloud-based) predictive dialers&lt;/strong&gt; charge a per-seat monthly subscription. The vendor handles infrastructure, updates, and support. Your costs are predictable but scale linearly with seat count. You trade control for simplicity.&lt;/p&gt;

&lt;p&gt;Examples: Five9, Convoso, &lt;a href="https://dev.to/blog/vicidial-vs-genesys-2026/"&gt;Genesys Cloud CX&lt;/a&gt;, &lt;a href="https://dev.to/blog/vicidial-vs-ringcentral/"&gt;RingCentral RingCX&lt;/a&gt;, NICE CXone, Talkdesk, Dialpad AI Contact Center.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-hosted predictive dialers&lt;/strong&gt; run on your infrastructure (physical servers, colocation, or dedicated cloud servers). The software is either open-source (free) or licensed. Your costs are infrastructure and labor, which scale sub-linearly with seat count. You trade simplicity for control.&lt;/p&gt;

&lt;p&gt;Primary example: VICIdial (open-source, GPL licensed, running on 14,000+ installations globally).&lt;/p&gt;

&lt;p&gt;The decision between them isn't about which is "better." It's about which cost structure matches your operational reality at your specific scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hosted Platform Pricing in 2026: Seven Platforms Compared
&lt;/h2&gt;

&lt;p&gt;We researched published pricing, third-party analysis, and verified user reports for seven hosted platforms. The actual cost for a 100-seat outbound-focused operation:&lt;/p&gt;

&lt;h3&gt;
  
  
  Five9
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Monthly (100 seats)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Core seats (100 x $159)&lt;/td&gt;
&lt;td&gt;$15,900&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telecom (high-volume outbound)&lt;/td&gt;
&lt;td&gt;$15,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI overage (beyond 3K min/seat)&lt;/td&gt;
&lt;td&gt;$2,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage overage&lt;/td&gt;
&lt;td&gt;$500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$33,400&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Per seat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$334&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Minimum 50 seats. 36-month contract with auto-renewal. Professional services: $25K-50K upfront.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Convoso
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Monthly (100 seats)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Seats (100 x ~$150 est.)&lt;/td&gt;
&lt;td&gt;$15,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carrier fees&lt;/td&gt;
&lt;td&gt;$10,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://dev.to/blog/contact-rate-optimization/"&gt;DID management&lt;/a&gt; (Ignite)&lt;/td&gt;
&lt;td&gt;$800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMS add-on&lt;/td&gt;
&lt;td&gt;$1,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$27,300&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Per seat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$273&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Pricing not published -- estimates from Capterra and user reports. Annual contract preferred. No free trial.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Genesys Cloud CX (CX 3)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Monthly (100 seats)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CX 3 seats (100 x $155)&lt;/td&gt;
&lt;td&gt;$15,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telecom (BYOC high-volume)&lt;/td&gt;
&lt;td&gt;$12,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Experience tokens&lt;/td&gt;
&lt;td&gt;$2,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Premium support&lt;/td&gt;
&lt;td&gt;$1,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$31,500&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Per seat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$315&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Professional services: $50K-150K. Add WFM and QM for CX 4 at $240/seat (+$85/seat).&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  RingCentral RingCX
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Monthly (100 seats)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RingCX seats (100 x $65)&lt;/td&gt;
&lt;td&gt;$6,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RingEX Advanced (100 x $25)&lt;/td&gt;
&lt;td&gt;$2,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High-volume telecom overage&lt;/td&gt;
&lt;td&gt;$7,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$16,500&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Per seat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$165&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Requires RingEX base licenses in addition to RingCX. Best value in the hosted market.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  NICE CXone
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Monthly (100 seats)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Seats (100 x ~$135 est.)&lt;/td&gt;
&lt;td&gt;$13,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telecom&lt;/td&gt;
&lt;td&gt;$10,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WFM add-on&lt;/td&gt;
&lt;td&gt;$2,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$25,500&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Per seat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$255&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Pricing not published -- industry estimates. Strong WFM and quality management.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Talkdesk
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Monthly (100 seats)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CX Cloud Elevate (100 x ~$115)&lt;/td&gt;
&lt;td&gt;$11,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telecom&lt;/td&gt;
&lt;td&gt;$8,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI add-ons (Agent Assist, QM)&lt;/td&gt;
&lt;td&gt;$3,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$22,500&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Per seat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$225&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;3-year contract standard. Professional services required for custom integrations.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dialpad AI Contact Center
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Monthly (100 seats)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Seats (100 x ~$95 est.)&lt;/td&gt;
&lt;td&gt;$9,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telecom&lt;/td&gt;
&lt;td&gt;$6,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI add-ons&lt;/td&gt;
&lt;td&gt;$1,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$17,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Per seat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$170&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Requires Dialpad Business Communications base. AI-native but limited outbound dialer depth.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Self-Hosted VICIdial: The Real Numbers
&lt;/h2&gt;

&lt;p&gt;VICIdial is free to download and install. The costs are infrastructure, telecom, and labor. Here's the breakdown at multiple scales.&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure Architecture by Scale
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scale&lt;/th&gt;
&lt;th&gt;DB Server&lt;/th&gt;
&lt;th&gt;Web/Dialer Servers&lt;/th&gt;
&lt;th&gt;Recording Storage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;25 seats&lt;/td&gt;
&lt;td&gt;1 (combined)&lt;/td&gt;
&lt;td&gt;1 (combined)&lt;/td&gt;
&lt;td&gt;Local RAID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50 seats&lt;/td&gt;
&lt;td&gt;1 dedicated&lt;/td&gt;
&lt;td&gt;1 dedicated&lt;/td&gt;
&lt;td&gt;Local RAID + NAS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100 seats&lt;/td&gt;
&lt;td&gt;1 (8-core, 32GB)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;NAS or SAN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;200 seats&lt;/td&gt;
&lt;td&gt;1 (16-core, 64GB)&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;SAN + archival&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500 seats&lt;/td&gt;
&lt;td&gt;2 (primary + replica)&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;SAN + cloud archival&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Detailed Cost Breakdown: 100-Seat Operation
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;th&gt;Annual Cost&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Database server&lt;/td&gt;
&lt;td&gt;$300&lt;/td&gt;
&lt;td&gt;$3,600&lt;/td&gt;
&lt;td&gt;Hetzner AX102 or similar: 16-core, 64GB, NVMe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dialer/web server #1&lt;/td&gt;
&lt;td&gt;$200&lt;/td&gt;
&lt;td&gt;$2,400&lt;/td&gt;
&lt;td&gt;8-core, 32GB, SSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dialer/web server #2&lt;/td&gt;
&lt;td&gt;$200&lt;/td&gt;
&lt;td&gt;$2,400&lt;/td&gt;
&lt;td&gt;Redundancy + load distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recording storage (NAS)&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;td&gt;$1,200&lt;/td&gt;
&lt;td&gt;4TB+ for monthly recordings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIP trunking (Telnyx)&lt;/td&gt;
&lt;td&gt;$5,000&lt;/td&gt;
&lt;td&gt;$60,000&lt;/td&gt;
&lt;td&gt;~$0.007/min, &lt;a href="https://dev.to/blog/vicidial-performance-tuning/"&gt;high volume&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DID numbers (200)&lt;/td&gt;
&lt;td&gt;$300&lt;/td&gt;
&lt;td&gt;$3,600&lt;/td&gt;
&lt;td&gt;Rotation for caller ID health&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System administration&lt;/td&gt;
&lt;td&gt;$2,500&lt;/td&gt;
&lt;td&gt;$30,000&lt;/td&gt;
&lt;td&gt;Managed hosting provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring (Grafana stack)&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;td&gt;$1,200&lt;/td&gt;
&lt;td&gt;Server + &lt;a href="https://dev.to/blog/vicidial-carrier-selection/"&gt;call quality&lt;/a&gt; monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security (SSL, firewall, updates)&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;td&gt;$1,200&lt;/td&gt;
&lt;td&gt;Let's Encrypt + iptables + patches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backup&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;td&gt;$1,200&lt;/td&gt;
&lt;td&gt;Daily database + config backup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$8,900&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$106,800&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Per seat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$89&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1,068&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Cost Comparison Across All Scales
&lt;/h3&gt;

&lt;p&gt;Here's the table that matters. Annual total cost including everything -- licensing, telecom, infrastructure, labor, and support:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Seats&lt;/th&gt;
&lt;th&gt;Five9&lt;/th&gt;
&lt;th&gt;Convoso&lt;/th&gt;
&lt;th&gt;Genesys CX3&lt;/th&gt;
&lt;th&gt;RingCX&lt;/th&gt;
&lt;th&gt;NICE&lt;/th&gt;
&lt;th&gt;Talkdesk&lt;/th&gt;
&lt;th&gt;Dialpad&lt;/th&gt;
&lt;th&gt;VICIdial&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;$116K&lt;/td&gt;
&lt;td&gt;$86K&lt;/td&gt;
&lt;td&gt;$108K&lt;/td&gt;
&lt;td&gt;$55K&lt;/td&gt;
&lt;td&gt;$83K&lt;/td&gt;
&lt;td&gt;$75K&lt;/td&gt;
&lt;td&gt;$57K&lt;/td&gt;
&lt;td&gt;$55K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;$200K&lt;/td&gt;
&lt;td&gt;$164K&lt;/td&gt;
&lt;td&gt;$189K&lt;/td&gt;
&lt;td&gt;$99K&lt;/td&gt;
&lt;td&gt;$153K&lt;/td&gt;
&lt;td&gt;$135K&lt;/td&gt;
&lt;td&gt;$102K&lt;/td&gt;
&lt;td&gt;$72K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;$401K&lt;/td&gt;
&lt;td&gt;$328K&lt;/td&gt;
&lt;td&gt;$378K&lt;/td&gt;
&lt;td&gt;$198K&lt;/td&gt;
&lt;td&gt;$306K&lt;/td&gt;
&lt;td&gt;$270K&lt;/td&gt;
&lt;td&gt;$204K&lt;/td&gt;
&lt;td&gt;$107K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;$730K&lt;/td&gt;
&lt;td&gt;$590K&lt;/td&gt;
&lt;td&gt;$684K&lt;/td&gt;
&lt;td&gt;$372K&lt;/td&gt;
&lt;td&gt;$552K&lt;/td&gt;
&lt;td&gt;$486K&lt;/td&gt;
&lt;td&gt;$372K&lt;/td&gt;
&lt;td&gt;$170K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;$1.7M&lt;/td&gt;
&lt;td&gt;$1.4M&lt;/td&gt;
&lt;td&gt;$1.6M&lt;/td&gt;
&lt;td&gt;$880K&lt;/td&gt;
&lt;td&gt;$1.3M&lt;/td&gt;
&lt;td&gt;$1.1M&lt;/td&gt;
&lt;td&gt;$880K&lt;/td&gt;
&lt;td&gt;$370K&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;All figures annualized, including telecom, add-ons, and infrastructure costs. VICIdial includes managed hosting.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The pattern is clear: hosted platforms scale linearly. VICIdial scales sub-linearly. At 25 seats, VICIdial's infrastructure floor means it competes with (but doesn't always beat) the cheapest hosted options. At 500 seats, VICIdial costs 21-27% of what the expensive hosted platforms charge and 42% of the cheapest.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Crossover Point
&lt;/h3&gt;

&lt;p&gt;VICIdial becomes the cheapest option at approximately &lt;strong&gt;35-40 seats&lt;/strong&gt; for most hosting configurations. Below that, RingCentral RingCX and Dialpad often win on pure cost because their seat pricing doesn't carry the infrastructure floor.&lt;/p&gt;

&lt;p&gt;The exception: if you're already running the server infrastructure for other purposes (existing VoIP deployment, other Linux workloads), VICIdial's marginal cost of adding a dialer is near-zero, making it the cheapest option even at 10 seats.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Hosted Gives You That Self-Hosted Doesn't
&lt;/h2&gt;

&lt;p&gt;Honesty requires acknowledging the real advantages of hosted platforms. What you're buying with that per-seat premium:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Zero Infrastructure Burden
&lt;/h3&gt;

&lt;p&gt;Nobody on your team provisions servers, patches operating systems, tunes databases, or debugs Asterisk at 2 AM. The vendor handles all of it. For operations without technical staff, this isn't a luxury -- it's a necessity.&lt;/p&gt;

&lt;p&gt;The cost of infrastructure management in a self-hosted environment is real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Junior Linux admin:&lt;/strong&gt; $60-80K/year&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Senior VICIdial/Asterisk admin:&lt;/strong&gt; $90-130K/year&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed hosting (ViciStack, ViciHost):&lt;/strong&gt; $1,500-4,000/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can't hire or contract that expertise, self-hosting is not viable regardless of the cost savings.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Compliance Certifications
&lt;/h3&gt;

&lt;p&gt;Five9, Genesys, and NICE hold SOC 2, PCI DSS, HIPAA, and other compliance certifications. For healthcare, financial services, and government operations, those certifications have procurement value that's hard to replicate independently.&lt;/p&gt;

&lt;p&gt;Self-hosted VICIdial can meet the same compliance standards, but you're responsible for the audit, documentation, and ongoing maintenance. HIPAA compliance for a self-hosted dialer typically costs $15,000-30,000 in initial assessment and $5,000-10,000/year in ongoing compliance work.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. AI Features
&lt;/h3&gt;

&lt;p&gt;Every major hosted platform now includes AI capabilities: call summaries, agent coaching, &lt;a href="https://dev.to/blog/speech-analytics-call-center/"&gt;sentiment analysis&lt;/a&gt;, quality scoring. These features are genuinely useful and getting better rapidly.&lt;/p&gt;

&lt;p&gt;Self-hosted VICIdial has no native AI. You can integrate third-party tools (and &lt;a href="https://dev.to/features/ai-quality-control/"&gt;ViciStack builds custom AI layers for VICIdial&lt;/a&gt;), but the out-of-box experience is zero AI.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Omnichannel
&lt;/h3&gt;

&lt;p&gt;If your operation handles voice, email, chat, SMS, and social media through a single agent desktop, hosted platforms handle this natively. Building equivalent omnichannel capability on VICIdial requires significant integration work.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Vendor Support
&lt;/h3&gt;

&lt;p&gt;When something breaks, you open a ticket and the vendor fixes it. You don't debug Asterisk core dumps. You don't trace SIP message flows. You don't optimize MySQL slow queries.&lt;/p&gt;

&lt;p&gt;The quality of that support varies dramatically (Five9 and Genesys get mixed reviews on support responsiveness), but having it available is better than being entirely on your own.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Self-Hosted Gives You That Hosted Doesn't
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Dialer Control
&lt;/h3&gt;

&lt;p&gt;This is the big one for outbound operations.&lt;/p&gt;

&lt;p&gt;Self-hosted VICIdial exposes 2,000+ configuration settings across campaigns, lists, IVRs, agent behavior, and system parameters. You control the predictive algorithm, AMD thresholds, dial ratios, dropped call handling, and lead prioritization at a level no hosted platform matches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;;&lt;/span&gt; &lt;span class="py"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Adaptive predictive dialer tuned for solar lead gen&lt;/span&gt;
&lt;span class="err"&gt;Dial&lt;/span&gt; &lt;span class="py"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ADAPT_HARD_LIMIT&lt;/span&gt;
&lt;span class="err"&gt;Auto&lt;/span&gt; &lt;span class="err"&gt;Dial&lt;/span&gt; &lt;span class="py"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4.0&lt;/span&gt;
&lt;span class="err"&gt;Adaptive&lt;/span&gt; &lt;span class="err"&gt;Maximum&lt;/span&gt; &lt;span class="py"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10.0&lt;/span&gt;
&lt;span class="err"&gt;Adaptive&lt;/span&gt; &lt;span class="err"&gt;Dropped&lt;/span&gt; &lt;span class="py"&gt;Percentage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2.0%&lt;/span&gt;
&lt;span class="err"&gt;Adaptive&lt;/span&gt; &lt;span class="py"&gt;Intensity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.85&lt;/span&gt;
&lt;span class="err"&gt;Available&lt;/span&gt; &lt;span class="err"&gt;Only&lt;/span&gt; &lt;span class="err"&gt;Ratio&lt;/span&gt; &lt;span class="py"&gt;Calcs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Y&lt;/span&gt;
&lt;span class="py"&gt;AMD_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AMD&lt;/span&gt;
&lt;span class="py"&gt;AMD_send_to_message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Y&lt;/span&gt;
&lt;span class="py"&gt;amd_initial_silence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2600&lt;/span&gt;
&lt;span class="py"&gt;amd_greeting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1500&lt;/span&gt;
&lt;span class="py"&gt;amd_after_greeting_silence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;800&lt;/span&gt;
&lt;span class="py"&gt;amd_total_analysis_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5000&lt;/span&gt;
&lt;span class="err"&gt;Hopper&lt;/span&gt; &lt;span class="py"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;500&lt;/span&gt;
&lt;span class="err"&gt;List&lt;/span&gt; &lt;span class="py"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DOWN COUNT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That configuration -- specific to one campaign on one server -- gives you control over 15+ parameters that directly affect &lt;a href="https://dev.to/blog/contact-rate-optimization-guide/"&gt;contact rates&lt;/a&gt;, agent utilization, and compliance. Hosted platforms give you 3-5 knobs to turn. VICIdial gives you the full control panel.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Data Ownership
&lt;/h3&gt;

&lt;p&gt;Self-hosted means your data -- call recordings, lead disposition records, agent performance metrics, CDRs, everything -- lives on your servers. You have direct database access. You can run any query, export any dataset, and migrate to any platform at any time.&lt;/p&gt;

&lt;p&gt;Hosted platforms hold your data on their infrastructure. Exporting is possible but constrained by their API capabilities and rate limits. Migration requires effort, and in some cases, data loss. And if you're in a contract dispute with your vendor, your data is on their servers.&lt;/p&gt;

&lt;p&gt;For operations that consider call data a strategic asset (training AI models, compliance archives, performance analysis), data ownership is non-negotiable.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. No Vendor Lock-in
&lt;/h3&gt;

&lt;p&gt;No 36-month auto-renewing contracts. No 50-seat minimums. No price increases pushed through on renewal. No "we're deprecating this feature you depend on."&lt;/p&gt;

&lt;p&gt;Self-hosted VICIdial runs on your terms. If you want to switch SIP providers, you change a configuration file. If you want to add a server, you provision it. If you want to modify the dialer algorithm, you edit the source code.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Cost Predictability at Scale
&lt;/h3&gt;

&lt;p&gt;Hosted platform costs scale linearly and include variable components (AI tokens, telecom overage, storage fees) that make budgeting unpredictable. A 100-seat Five9 deployment might cost $33K one month and $38K the next based on call volume.&lt;/p&gt;

&lt;p&gt;Self-hosted costs are almost entirely fixed. Servers cost what they cost. SIP trunking bills per minute at a rate you negotiate. There are no surprise AI token bills, no storage overage charges, no "your usage exceeded the plan allowance" notifications.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Performance Tuning
&lt;/h3&gt;

&lt;p&gt;When your hosted dialer has audio quality issues, you open a ticket. When your self-hosted dialer has audio quality issues, you check the RTP streams, trace the SIP path, verify codec negotiation, and fix it.&lt;/p&gt;

&lt;p&gt;When your hosted dialer's predictive algorithm isn't aggressive enough, you ask the vendor to adjust it. When your self-hosted dialer needs tuning, you change the settings in real-time and watch the impact on the next batch of dials.&lt;/p&gt;

&lt;p&gt;For operations where minutes of downtime cost thousands of dollars in lost revenue, the ability to diagnose and fix issues without waiting in a support queue is a material advantage.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hidden Costs of Self-Hosting (We're Being Honest)
&lt;/h2&gt;

&lt;p&gt;Self-hosted is not "free." The software is free. Running it costs real money and real effort. Here are the costs that VICIdial advocates (including us) sometimes understate.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Learning Curve
&lt;/h3&gt;

&lt;p&gt;VICIdial's admin interface is dense. The first time you log in, you'll see dozens of settings per campaign, hundreds of system-level parameters, and documentation that's thorough but scattered across a wiki and forum.&lt;/p&gt;

&lt;p&gt;A new VICIdial admin takes 2-4 weeks to become productive. An experienced one takes 2-3 months to master the platform. Compare that to hosted platforms where basic campaign setup takes hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real cost:&lt;/strong&gt; 40-80 hours of learning time before your first campaign runs well. If you're hiring this expertise, budget $5,000-15,000 in training or consulting.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Ongoing Maintenance
&lt;/h3&gt;

&lt;p&gt;Linux servers need patching. Asterisk has vulnerabilities that need updates. MariaDB needs monitoring and occasional optimization. SSL certificates need renewal. Disk space needs watching. Log files need rotation.&lt;/p&gt;

&lt;p&gt;A weekly maintenance routine looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check Asterisk process and restart if needed&lt;/span&gt;
systemctl status asterisk

&lt;span class="c"&gt;# Monitor /var/spool/asterisk/monitor/ disk usage&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; /var/spool/asterisk/monitor/

&lt;span class="c"&gt;# Rotate old recordings (move to NAS or archive)&lt;/span&gt;
find /var/spool/asterisk/monitor/ &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.wav"&lt;/span&gt; &lt;span class="nt"&gt;-mtime&lt;/span&gt; +90

&lt;span class="c"&gt;# Check MySQL slow queries on port 3306&lt;/span&gt;
mysql &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SHOW GLOBAL STATUS LIKE 'Slow_queries';"&lt;/span&gt;

&lt;span class="c"&gt;# Verify fail2ban is blocking SIP scanners&lt;/span&gt;
iptables &lt;span class="nt"&gt;-L&lt;/span&gt; fail2ban-asterisk &lt;span class="nt"&gt;-n&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;

&lt;span class="c"&gt;# Check /etc/asterisk/sip.conf for unauthorized registrations&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip show peers"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"OK"&lt;/span&gt;

&lt;span class="c"&gt;# Verify crontab is intact&lt;/span&gt;
crontab &lt;span class="nt"&gt;-l&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; vicidial
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Real cost:&lt;/strong&gt; 4-8 hours/week of system administration for a 100-seat deployment. If you're paying a managed provider, that's $1,500-4,000/month. If you have in-house staff, that's time they're not spending on other tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Disaster Recovery
&lt;/h3&gt;

&lt;p&gt;Hosted platforms handle backup and DR for you (usually). Self-hosted means you build your own backup strategy, test your own recovery procedures, and maintain your own redundancy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real cost:&lt;/strong&gt; $200-500/month for backup infrastructure and storage. Plus the time to build, test, and maintain the DR plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Security Responsibility
&lt;/h3&gt;

&lt;p&gt;With hosted platforms, the vendor handles server security, DDoS protection, and vulnerability management. Self-hosted means you handle it.&lt;/p&gt;

&lt;p&gt;VICIdial servers exposed to the internet without proper firewall rules, fail2ban, and access controls are targets for toll fraud and SIP scanning attacks. We've seen unsecured VICIdial installations racking up $50,000 in fraudulent international calls in a single weekend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real cost:&lt;/strong&gt; Proper security setup takes 8-16 hours initially and 2-4 hours/month for ongoing maintenance. Or you use a managed provider who handles it.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The SysAdmin Single-Point-of-Failure Problem
&lt;/h3&gt;

&lt;p&gt;If one person on your team knows VICIdial and they leave, you have a problem. The platform is powerful but not intuitive, and replacing VICIdial expertise on short notice is difficult.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mitigation:&lt;/strong&gt; Use a managed hosting provider (so expertise isn't tied to one employee), document your configuration, and maintain relationships with the VICIdial consulting community.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision Framework by Scale
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Under 25 Seats
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Recommendation: Hosted (RingCentral RingCX or Dialpad)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The infrastructure floor for self-hosted VICIdial makes per-seat costs higher than the cheapest hosted options at this scale. You'd be paying $150+/seat for VICIdial infrastructure that could handle 100 seats but is only serving 25.&lt;/p&gt;

&lt;p&gt;Exception: if you're starting small but plan to grow past 50 seats within 12 months, start with self-hosted to avoid the migration pain later.&lt;/p&gt;

&lt;h3&gt;
  
  
  25-50 Seats
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Recommendation: Either, depending on your technical capacity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the crossover zone. Self-hosted VICIdial breaks even with hosted platforms around 35-40 seats. If you have or can hire technical staff, self-hosted starts making financial sense. If you don't, hosted is still the pragmatic choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  50-100 Seats
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Recommendation: Self-hosted VICIdial (with managed hosting if no in-house expertise)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At this scale, self-hosted VICIdial costs 40-60% of what hosted platforms charge. The savings -- $100,000-200,000 annually -- justify either hiring a Linux admin or contracting managed hosting. The operational complexity is manageable, and you're in VICIdial's sweet spot.&lt;/p&gt;

&lt;h3&gt;
  
  
  100-200 Seats
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Recommendation: Self-hosted VICIdial (strongly)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The cost delta is massive. $200,000-500,000 in annual savings depending on which hosted platform you're comparing against. At this scale, you should have in-house technical staff plus a managed hosting relationship for backup.&lt;/p&gt;

&lt;h3&gt;
  
  
  200+ Seats
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Recommendation: Self-hosted VICIdial (unless you need enterprise omnichannel)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At 500 seats, VICIdial saves $500K-1.3M annually vs hosted platforms. The only scenario where hosted makes sense at this scale is if you genuinely need enterprise omnichannel, global compliance certifications, and AI capabilities that would cost more to build in-house than the hosted premium.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Migration Factor
&lt;/h2&gt;

&lt;p&gt;Switching from hosted to self-hosted (or vice versa) is not free. Factor these costs into your decision:&lt;/p&gt;

&lt;h3&gt;
  
  
  Hosted to Self-Hosted Migration
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Estimated Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VICIdial deployment + configuration&lt;/td&gt;
&lt;td&gt;$5,000-15,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data migration (leads, dispositions, recordings)&lt;/td&gt;
&lt;td&gt;$5,000-20,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent retraining&lt;/td&gt;
&lt;td&gt;$2,000-5,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parallel running period (1-2 months)&lt;/td&gt;
&lt;td&gt;Double costs during overlap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SIP trunk setup and testing&lt;/td&gt;
&lt;td&gt;$1,000-3,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$15,000-45,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Self-Hosted to Hosted Migration
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Estimated Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hosted platform implementation&lt;/td&gt;
&lt;td&gt;$15,000-50,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data export from VICIdial&lt;/td&gt;
&lt;td&gt;$2,000-5,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data import to hosted platform&lt;/td&gt;
&lt;td&gt;$5,000-15,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent retraining&lt;/td&gt;
&lt;td&gt;$3,000-8,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recording migration (if applicable)&lt;/td&gt;
&lt;td&gt;$1,000-10,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$25,000-90,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The migration cost means this isn't a decision to make lightly. Switching platforms mid-operation disrupts production. Plan accordingly.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 3-Year TCO Comparison
&lt;/h2&gt;

&lt;p&gt;Decision-makers think in multi-year terms. Here's the 3-year total cost of ownership including implementation, migration (if applicable), and ongoing operations:&lt;/p&gt;

&lt;h3&gt;
  
  
  100-Seat Operation: 3-Year TCO
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Year 1&lt;/th&gt;
&lt;th&gt;Year 2&lt;/th&gt;
&lt;th&gt;Year 3&lt;/th&gt;
&lt;th&gt;3-Year Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Five9&lt;/td&gt;
&lt;td&gt;$441K&lt;/td&gt;
&lt;td&gt;$401K&lt;/td&gt;
&lt;td&gt;$401K&lt;/td&gt;
&lt;td&gt;$1,243K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Convoso&lt;/td&gt;
&lt;td&gt;$358K&lt;/td&gt;
&lt;td&gt;$328K&lt;/td&gt;
&lt;td&gt;$328K&lt;/td&gt;
&lt;td&gt;$1,014K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Genesys CX3&lt;/td&gt;
&lt;td&gt;$528K&lt;/td&gt;
&lt;td&gt;$378K&lt;/td&gt;
&lt;td&gt;$378K&lt;/td&gt;
&lt;td&gt;$1,284K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RingCX&lt;/td&gt;
&lt;td&gt;$222K&lt;/td&gt;
&lt;td&gt;$198K&lt;/td&gt;
&lt;td&gt;$198K&lt;/td&gt;
&lt;td&gt;$618K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VICIdial (managed)&lt;/td&gt;
&lt;td&gt;$127K&lt;/td&gt;
&lt;td&gt;$107K&lt;/td&gt;
&lt;td&gt;$107K&lt;/td&gt;
&lt;td&gt;$341K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VICIdial (in-house admin)&lt;/td&gt;
&lt;td&gt;$187K&lt;/td&gt;
&lt;td&gt;$167K&lt;/td&gt;
&lt;td&gt;$167K&lt;/td&gt;
&lt;td&gt;$521K&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Year 1 includes implementation/setup costs. VICIdial in-house admin includes $80K/year for a dedicated admin.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Over three years, VICIdial with managed hosting saves &lt;strong&gt;$277K-943K&lt;/strong&gt; compared to hosted alternatives. Even with a dedicated in-house admin, the savings range from &lt;strong&gt;$97K to $763K&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Our Recommendation
&lt;/h2&gt;

&lt;p&gt;If you're reading this article, you're probably running a 50+ seat outbound operation and evaluating whether the hosted platform premium is worth what you're paying. For most outbound-focused operations above 50 seats, it's not.&lt;/p&gt;

&lt;p&gt;Self-hosted VICIdial with professional management delivers 80%+ of what hosted platforms offer for outbound dialing -- the use case that actually generates your revenue -- at 30-50% of the cost. The 20% you're missing (AI features, native omnichannel, modern UI) can be addressed through targeted integration work that costs a fraction of the per-seat premium you'd pay for a hosted platform.&lt;/p&gt;

&lt;p&gt;We handle the hard parts of self-hosted VICIdial: deployment, optimization, monitoring, and ongoing management. We take the platform and add the enterprise features -- &lt;a href="https://dev.to/blog/ai-call-center-quality-control/"&gt;AI quality&lt;/a&gt; monitoring, &lt;a href="https://dev.to/blog/stir-shaken-vicidial-guide/"&gt;STIR/SHAKEN compliance&lt;/a&gt;, real-time performance dashboards, and optimized dialer configurations -- that bridge the gap between open-source and enterprise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our offer: we'll audit your current dialer deployment (hosted or self-hosted) and build a custom TCO comparison showing exactly what you'd save. If the numbers make sense and you switch to optimized VICIdial, we guarantee a 50% conversion increase in two weeks. $5,000 flat ($1,000 down, $4,000 on delivery).&lt;/strong&gt; &lt;a href="https://vicistack.com/contact/" rel="noopener noreferrer"&gt;Start the conversation at vicistack.com&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Reference: Which Platform, Which Scale
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your Situation&lt;/th&gt;
&lt;th&gt;Best Choice&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Under 25 seats, no tech staff&lt;/td&gt;
&lt;td&gt;RingCentral RingCX&lt;/td&gt;
&lt;td&gt;Lowest cost, fastest setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Under 25 seats, growing fast&lt;/td&gt;
&lt;td&gt;VICIdial (managed)&lt;/td&gt;
&lt;td&gt;Avoid migration later&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25-50 seats, no tech staff&lt;/td&gt;
&lt;td&gt;RingCentral or Talkdesk&lt;/td&gt;
&lt;td&gt;Good value, managed everything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25-50 seats, have tech staff&lt;/td&gt;
&lt;td&gt;VICIdial (self-managed)&lt;/td&gt;
&lt;td&gt;Crossover point, savings start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50-100 seats, outbound-heavy&lt;/td&gt;
&lt;td&gt;VICIdial (managed hosting)&lt;/td&gt;
&lt;td&gt;Clear cost winner, excellent dialer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100-200 seats, outbound-heavy&lt;/td&gt;
&lt;td&gt;VICIdial (managed + in-house)&lt;/td&gt;
&lt;td&gt;Massive savings, full control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;200+ seats, outbound-heavy&lt;/td&gt;
&lt;td&gt;VICIdial (full in-house team)&lt;/td&gt;
&lt;td&gt;$500K+ annual savings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Any scale, omnichannel-heavy&lt;/td&gt;
&lt;td&gt;Genesys CX3 or Five9&lt;/td&gt;
&lt;td&gt;Native multi-channel required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Any scale, compliance-critical&lt;/td&gt;
&lt;td&gt;Five9 or Genesys&lt;/td&gt;
&lt;td&gt;Pre-built certifications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Already on VICIdial, underperforming&lt;/td&gt;
&lt;td&gt;Professional VICIdial optimization&lt;/td&gt;
&lt;td&gt;Fix config before switching platforms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;em&gt;Last updated: March 2026. Pricing data from vendor websites, Platform28, CloudTalk, Capterra, and verified user reports. VICIdial costs from production deployments managed by ViciStack. All figures are estimates and should be verified with current vendor quotes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on the ViciStack blog.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>voip</category>
      <category>asterisk</category>
      <category>sysadmin</category>
      <category>devops</category>
    </item>
    <item>
      <title>Workforce Management for Call Centers: Erlang C, Schedule Adherence, and the Forecasting Math That Keeps You Staffed</title>
      <dc:creator>Jason Shouldice</dc:creator>
      <pubDate>Fri, 27 Mar 2026 23:24:48 +0000</pubDate>
      <link>https://dev.to/gamlin/workforce-management-for-call-centers-erlang-c-schedule-adherence-and-the-forecasting-math-that-27hf</link>
      <guid>https://dev.to/gamlin/workforce-management-for-call-centers-erlang-c-schedule-adherence-and-the-forecasting-math-that-27hf</guid>
      <description>&lt;p&gt;&lt;strong&gt;Last updated: March 2026 | Reading time: ~27 minutes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A 2025 benchmark spanning 38 countries found that 99% of WFM practitioners said workforce management is essential to business success. That is practically unanimous -- and yet most call centers under 100 agents do WFM by gut feel.&lt;/p&gt;

&lt;p&gt;The floor supervisor eyeballs the queue, sends someone to lunch when it looks slow, and scrambles for bodies when the hold times spike. It works until it doesn't, and when it doesn't, you are either overstaffed (burning payroll on agents who sit in READY doing nothing) or understaffed (burning customers who hang up after 4 minutes on hold).&lt;/p&gt;

&lt;p&gt;Organizations that implement proper WFM see 15-25% reduction in labor costs while simultaneously improving service levels. That is not a tradeoff -- that is getting both sides of the equation right at the same time.&lt;/p&gt;

&lt;p&gt;This guide covers the actual &lt;a href="https://dev.to/blog/contact-rate-optimization/"&gt;math behind&lt;/a&gt; &lt;a href="https://dev.to/blog/call-center-staffing-formula/"&gt;call center staffing&lt;/a&gt;: Erlang C calculations, shrinkage factors, schedule adherence tracking, forecasting from historical data, and the VICIdial-specific tools to make it work. No six-figure WFM platform required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Erlang C: The Staffing Math That Actually Works
&lt;/h2&gt;

&lt;p&gt;The Erlang C formula was invented by Danish mathematician Agner Erlang in 1917 to solve telephone traffic problems. Over a century later, it is still the standard for call center staffing calculations. Not because nobody has tried to improve it, but because it works.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Core Concept
&lt;/h3&gt;

&lt;p&gt;Erlang C answers one question: given a specific call volume and average handle time, how many agents do you need to meet a target service level?&lt;/p&gt;

&lt;p&gt;The inputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Call volume&lt;/strong&gt; -- number of calls per time interval (usually 30 minutes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Average Handle Time (AHT)&lt;/strong&gt; -- talk time plus after-call work, in seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Level target&lt;/strong&gt; -- percentage of calls answered within a time threshold (e.g., 80% of calls answered within 20 seconds)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The output:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minimum agents required&lt;/strong&gt; to meet that service level&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Worked Example
&lt;/h3&gt;

&lt;p&gt;Let us walk through a real calculation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inputs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;200 calls per hour (100 per 30-minute interval)&lt;/li&gt;
&lt;li&gt;Average Handle Time: 180 seconds (3 minutes)&lt;/li&gt;
&lt;li&gt;Service Level target: 80% of calls answered within 20 seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Calculate Traffic Intensity (Erlangs)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Traffic Intensity = (Calls per interval × AHT) / Interval duration in seconds
Traffic Intensity = (100 × 180) / 1800 = 10 Erlangs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;10 Erlangs means you need the equivalent of 10 agents continuously busy just to handle the call volume. But that gives you zero buffer -- every agent is on a call every second, and any new call goes to queue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Iterate Agent Count Until Service Level Is Met&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You test increasing numbers of agents against the Erlang C formula until the calculated service level meets your target:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agents&lt;/th&gt;
&lt;th&gt;Service Level&lt;/th&gt;
&lt;th&gt;Prob of Waiting&lt;/th&gt;
&lt;th&gt;Avg Speed of Answer&lt;/th&gt;
&lt;th&gt;Occupancy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;39%&lt;/td&gt;
&lt;td&gt;67%&lt;/td&gt;
&lt;td&gt;33 sec&lt;/td&gt;
&lt;td&gt;91%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;64%&lt;/td&gt;
&lt;td&gt;43%&lt;/td&gt;
&lt;td&gt;16 sec&lt;/td&gt;
&lt;td&gt;83%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;28%&lt;/td&gt;
&lt;td&gt;8 sec&lt;/td&gt;
&lt;td&gt;77%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;89%&lt;/td&gt;
&lt;td&gt;17%&lt;/td&gt;
&lt;td&gt;5 sec&lt;/td&gt;
&lt;td&gt;71%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;94%&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;3 sec&lt;/td&gt;
&lt;td&gt;67%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At 13 agents, you hit 80% service level -- your target. But look at the occupancy column. At 11 agents, occupancy is 91%. That means agents are on calls 91% of the time with almost no breathing room. Sustained occupancy above 85% leads to &lt;a href="https://dev.to/blog/call-center-agent-burnout/"&gt;burnout&lt;/a&gt;, increased handle times, higher error rates, and turnover.&lt;/p&gt;

&lt;p&gt;At 13 agents, occupancy drops to 77% -- a sustainable range. At 14 agents, you exceed your service level target with comfortable occupancy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Add Shrinkage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Agents are not available 100% of their scheduled time. They take breaks, attend meetings, do training, call in sick, and go on vacation. The percentage of scheduled time that does not produce agent availability is called shrinkage.&lt;/p&gt;

&lt;p&gt;Industry average shrinkage is 30%, broken down roughly as:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shrinkage Category&lt;/th&gt;
&lt;th&gt;Percentage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Breaks (lunch + micro)&lt;/td&gt;
&lt;td&gt;10-12%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After-call work&lt;/td&gt;
&lt;td&gt;5-8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meetings and coaching&lt;/td&gt;
&lt;td&gt;3-5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Training&lt;/td&gt;
&lt;td&gt;2-4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Absenteeism (sick, PTO)&lt;/td&gt;
&lt;td&gt;5-8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;System downtime&lt;/td&gt;
&lt;td&gt;1-2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;26-39%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Apply shrinkage to your raw agent count:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agents needed = Raw agents / (1 - Shrinkage rate)
Agents needed = 13 / (1 - 0.30) = 13 / 0.70 = 18.6 ≈ 19 agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need 19 scheduled agents to have 13 actually available and working the phones at any given time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Erlang C Calculator Script
&lt;/h3&gt;

&lt;p&gt;If you want to run these calculations yourself without a spreadsheet, here is a Python implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;erlang_c.py - Call center staffing calculator&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;lru_cache&lt;/span&gt;

&lt;span class="nd"&gt;@lru_cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxsize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;erlang_c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;traffic&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Calculate Erlang C probability of waiting.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;traffic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;  &lt;span class="c1"&gt;# system is overloaded
&lt;/span&gt;
    &lt;span class="c1"&gt;# Erlang B (probability of blocking)
&lt;/span&gt;    &lt;span class="n"&gt;inv_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;inv_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;inv_b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;traffic&lt;/span&gt;
    &lt;span class="n"&gt;erlang_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;inv_b&lt;/span&gt;

    &lt;span class="c1"&gt;# Erlang C = Erlang B / (1 - rho * (1 - Erlang B))
&lt;/span&gt;    &lt;span class="n"&gt;rho&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;traffic&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt;
    &lt;span class="n"&gt;ec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;erlang_b&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rho&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;erlang_b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_service_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;traffic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aht&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Calculate service level for given parameters.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;pw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;erlang_c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;traffic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rho&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;traffic&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt;
    &lt;span class="n"&gt;sl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;pw&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agents&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;traffic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;target_time&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;aht&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sl&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_agents_needed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calls_per_interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aht_seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval_seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;target_sl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shrinkage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;max_occupancy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Find minimum agents to meet service level and occupancy targets.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;traffic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calls_per_interval&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;aht_seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;interval_seconds&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;traffic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;traffic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculate_service_level&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;traffic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aht_seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;occupancy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;traffic&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sl&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;target_sl&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;occupancy&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;max_occupancy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;raw_agents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt;
            &lt;span class="n"&gt;scheduled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_agents&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;shrinkage&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traffic_erlangs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;traffic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raw_agents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;raw_agents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;service_level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sl&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;occupancy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;occupancy&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prob_waiting&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;erlang_c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;traffic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shrinkage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shrinkage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scheduled_agents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scheduled&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="c1"&gt;# Example: 100 calls per 30 min, 3 min AHT, 80/20 service level
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;find_agents_needed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;calls_per_interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aht_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;interval_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;target_sl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;target_time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;shrinkage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.30&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Traffic intensity:   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;traffic_erlangs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; Erlangs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Raw agents needed:   &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;raw_agents&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Service level:       &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;service_level&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Occupancy:           &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;occupancy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prob of waiting:     &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prob_waiting&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;With &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;shrinkage&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;% shrinkage: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scheduled_agents&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; scheduled agents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this for every 30-minute interval in your day. The output tells you exactly how many agents you need scheduled for each time slot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forecasting: Predicting Tomorrow's Call Volume
&lt;/h2&gt;

&lt;p&gt;Erlang C tells you how many agents you need &lt;em&gt;if you know the call volume&lt;/em&gt;. Forecasting tells you what the call volume will be. Get the forecast wrong and your staffing will be wrong regardless of how perfect your Erlang C math is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pulling Historical Data from VICIdial
&lt;/h3&gt;

&lt;p&gt;Start with 8-12 weeks of historical data broken into 30-minute intervals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;call_day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DAYOFWEEK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;day_of_week&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FLOOR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;MINUTE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;interval_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;LPAD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;':'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MINUTE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'00'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'30'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;interval_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;call_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;length_in_sec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_talk_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;length_in_sec&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;est_aht&lt;/span&gt;  &lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;after&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;call&lt;/span&gt; &lt;span class="k"&gt;work&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_closer_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="n"&gt;WEEK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;call_day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;call_day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For outbound campaigns, pull from &lt;code&gt;vicidial_log&lt;/code&gt; instead and focus on the connected calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;call_day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DAYOFWEEK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;day_of_week&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FLOOR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;MINUTE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;interval_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_dials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'B'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'DC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'N'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'NP'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'AFTHRS'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;connected_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;length_in_sec&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;length_in_sec&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_talk_time&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="n"&gt;WEEK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;call_day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;call_day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Building the Forecast
&lt;/h3&gt;

&lt;p&gt;The simplest effective forecasting method is a &lt;strong&gt;weighted moving average&lt;/strong&gt; that accounts for day-of-week patterns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Calculate the average call volume for each 30-minute interval, grouped by day of week&lt;/li&gt;
&lt;li&gt;Weight recent weeks more heavily (last 4 weeks get 60% weight, prior 8 weeks get 40%)&lt;/li&gt;
&lt;li&gt;Apply known adjustments for holidays, marketing campaigns, or seasonal patterns
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;forecast.py - Call volume forecast from historical data&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_forecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;historical_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;forecast_weeks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Build a weighted forecast from historical interval data.

    historical_data: list of dicts with day_of_week, interval_id, call_count, week_num
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Group by (day_of_week, interval_id)
&lt;/span&gt;    &lt;span class="n"&gt;intervals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;max_week&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;week_num&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;historical_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;historical_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;day_of_week&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;interval_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;weeks_ago&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max_week&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;week_num&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;weeks_ago&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
        &lt;span class="n"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;calls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;call_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;forecast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="nf"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;total_weight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;weighted_avg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;calls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;weight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total_weight&lt;/span&gt;
        &lt;span class="n"&gt;forecast&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;dow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;predicted_calls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weighted_avg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data_points&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;medium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;forecast&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Forecast Accuracy Targets
&lt;/h3&gt;

&lt;p&gt;Good WFM operations hit these accuracy bands:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Forecast Level&lt;/th&gt;
&lt;th&gt;Target Accuracy&lt;/th&gt;
&lt;th&gt;Acceptable&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Daily total&lt;/td&gt;
&lt;td&gt;Within 5%&lt;/td&gt;
&lt;td&gt;Within 10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30-minute interval&lt;/td&gt;
&lt;td&gt;Within 10%&lt;/td&gt;
&lt;td&gt;Within 15%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weekly total&lt;/td&gt;
&lt;td&gt;Within 3%&lt;/td&gt;
&lt;td&gt;Within 7%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your interval forecasts are off by more than 15% consistently, your historical data window is either too short, contaminated by anomalies, or your business has a pattern your model is not capturing.&lt;/p&gt;

&lt;p&gt;Track forecast accuracy every day:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;forecast_day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FLOOR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;MINUTE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;interval_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;actual_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predicted_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predicted_calls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;predicted_calls&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;variance_pct&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_closer_log&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;forecast_table&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;forecast_date&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;FLOOR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;MINUTE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;interval_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CURDATE&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;forecast_day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;interval_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Schedule Adherence: Making Sure the Plan Survives Contact With Reality
&lt;/h2&gt;

&lt;p&gt;You can have a perfect forecast and perfect Erlang C staffing, and still blow your service level if agents don't follow the schedule. Schedule adherence measures whether agents are doing what the schedule says they should be doing, when the schedule says they should be doing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Adherence Formula
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Adherence % = (Scheduled Time - Non-Adherent Time) / Scheduled Time × 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Non-adherent time includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Late logins (scheduled at 8:00, logged in at 8:12)&lt;/li&gt;
&lt;li&gt;Early logoffs (left at 4:45 instead of 5:00)&lt;/li&gt;
&lt;li&gt;Extended breaks (15-minute break turned into 25 minutes)&lt;/li&gt;
&lt;li&gt;Unauthorized auxiliary/pause time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; An agent scheduled for 480 minutes (8 hours) who arrives 15 minutes late, takes an extra 10 minutes on breaks, and logs off 5 minutes early has 30 minutes of non-adherent time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Adherence = (480 - 30) / 480 × 100 = 93.75%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adherence vs. Conformance
&lt;/h3&gt;

&lt;p&gt;These get confused constantly. They measure different things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Adherence&lt;/strong&gt; = doing the right thing at the right time. Were you logged in when you were scheduled to be? Were you on break when you were scheduled for break?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conformance&lt;/strong&gt; = doing the right amount of total work. Did you work 8 hours total? (You might have come in late and stayed late -- conformance would be fine, adherence would not.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You need both. An agent who works 8 hours but shifts their schedule by 30 minutes might have 100% conformance but 85% adherence -- and that 30-minute gap is exactly when you were understaffed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tracking Adherence in VICIdial
&lt;/h3&gt;

&lt;p&gt;VICIdial tracks agent status changes in the &lt;code&gt;vicidial_agent_log&lt;/code&gt; table. Pull adherence data with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;work_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;first_login&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;last_event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'READY'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;pause_sec&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ready_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'INCALL'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;pause_sec&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;talk_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'PAUSED'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;pause_sec&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pause_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pause_sec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'READY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'INCALL'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;pause_sec&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pause_sec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;productive_pct&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_agent_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;event_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;productive_pct&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agents at the bottom of the productive_pct ranking are your adherence problems. Look at their pause code usage to understand why:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sub_status&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pause_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pause_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pause_sec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_pause_seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pause_sec&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_pause_seconds&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_agent_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;event_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'PAUSED'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sub_status&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_pause_seconds&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an agent's "BREAK" &lt;a href="https://dev.to/blog/vicidial-pause-codes-accountability/"&gt;pause codes&lt;/a&gt; average 22 minutes when breaks are scheduled for 15 minutes, that is a 7-minute adherence leak per break. Over 3 breaks per day, 5 days per week, that is 105 minutes of lost capacity per agent per week.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adherence Targets
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Performance Level&lt;/th&gt;
&lt;th&gt;Adherence %&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Top performer&lt;/td&gt;
&lt;td&gt;97%+&lt;/td&gt;
&lt;td&gt;Recognize and reward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meeting expectations&lt;/td&gt;
&lt;td&gt;92-96%&lt;/td&gt;
&lt;td&gt;Normal operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Below target&lt;/td&gt;
&lt;td&gt;85-91%&lt;/td&gt;
&lt;td&gt;Coaching conversation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unacceptable&lt;/td&gt;
&lt;td&gt;Below 85%&lt;/td&gt;
&lt;td&gt;Written warning territory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Industry benchmark&lt;/td&gt;
&lt;td&gt;90-95%&lt;/td&gt;
&lt;td&gt;Where most centers target&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A realistic target for most operations is 92-95%. Pushing for 99% adherence creates a micromanagement culture that drives turnover -- which costs you far more than the 5% of lost adherence time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intraday Management: Adjusting in Real Time
&lt;/h2&gt;

&lt;p&gt;No forecast is perfect. Intraday management is the discipline of watching actual performance against the forecast and making adjustments before service levels crater.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-Time Metrics to Monitor
&lt;/h3&gt;

&lt;p&gt;Track these in VICIdial's real-time reports during operating hours:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Admin &amp;gt; Reports &amp;gt; Real-Time Report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use VICIdial's &lt;a href="https://dev.to/blog/vicidial-realtime-report-guide/"&gt;real-time report&lt;/a&gt; to track the key numbers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Yellow Alert&lt;/th&gt;
&lt;th&gt;Red Alert&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Calls in queue&lt;/td&gt;
&lt;td&gt;0-3&lt;/td&gt;
&lt;td&gt;4-8&lt;/td&gt;
&lt;td&gt;9+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Longest wait time&lt;/td&gt;
&lt;td&gt;Under 20 sec&lt;/td&gt;
&lt;td&gt;20-60 sec&lt;/td&gt;
&lt;td&gt;Over 60 sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agents in READY&lt;/td&gt;
&lt;td&gt;10-15% of total&lt;/td&gt;
&lt;td&gt;5-10%&lt;/td&gt;
&lt;td&gt;Under 5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agents in PAUSED&lt;/td&gt;
&lt;td&gt;Under 15% of total&lt;/td&gt;
&lt;td&gt;15-20%&lt;/td&gt;
&lt;td&gt;Over 20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service level (rolling 30 min)&lt;/td&gt;
&lt;td&gt;80%+&lt;/td&gt;
&lt;td&gt;70-80%&lt;/td&gt;
&lt;td&gt;Under 70%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Intraday Adjustment Playbook
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario: Call volume 20% above forecast&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cancel non-essential training and meetings -- pull those agents back to the phones&lt;/li&gt;
&lt;li&gt;Offer overtime to agents who already went home (text them, let them accept via app)&lt;/li&gt;
&lt;li&gt;Adjust break schedules -- shorten breaks by 5 minutes, stagger them wider&lt;/li&gt;
&lt;li&gt;If you have a blended inbound/outbound operation, pause outbound campaigns to free agents for inbound&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In VICIdial, you can shift agents between campaigns in real-time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Admin &amp;gt; Users &amp;gt; Agent Transfer
    Select agent &amp;gt; Move to Campaign: INBOUND_QUEUE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Scenario: Call volume 20% below forecast&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Offer Voluntary Time Off (VTO) to avoid paying agents to sit idle&lt;/li&gt;
&lt;li&gt;Pull agents into coaching sessions or training that was scheduled for later&lt;/li&gt;
&lt;li&gt;Run blended outbound dials to keep agents productive&lt;/li&gt;
&lt;li&gt;Do not send everyone home -- volume can spike back up unpredictably&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Building an Intraday Dashboard
&lt;/h3&gt;

&lt;p&gt;VICIdial's real-time report gives you the raw data. Build a dashboard that compares actual vs. forecast in real-time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# intraday-check.sh - Compare actual call volume against forecast&lt;/span&gt;
&lt;span class="nv"&gt;INTERVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%H:%M | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;: &lt;span class="s1"&gt;'{
    if ($2 &amp;lt; 30) printf "%s:00", $1;
    else printf "%s:30", $1;
}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;DOW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%u&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;ACTUAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; cron &lt;span class="nt"&gt;-pPASS&lt;/span&gt; vicidial &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
    SELECT COUNT(*) FROM vicidial_closer_log
    WHERE call_date &amp;gt;= CONCAT(CURDATE(), ' ', '&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;INTERVAL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;', ':00')
    AND call_date &amp;lt; CONCAT(CURDATE(), ' ', '&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;INTERVAL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;', ':00') + INTERVAL 30 MINUTE"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;FORECAST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; cron &lt;span class="nt"&gt;-pPASS&lt;/span&gt; vicidial &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
    SELECT predicted_calls FROM wfm_forecast
    WHERE day_of_week = &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DOW&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; AND interval_start = '&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;INTERVAL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FORECAST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FORECAST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;VARIANCE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"scale=1; (&lt;/span&gt;&lt;span class="nv"&gt;$ACTUAL&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="nv"&gt;$FORECAST&lt;/span&gt;&lt;span class="s2"&gt;) / &lt;/span&gt;&lt;span class="nv"&gt;$FORECAST&lt;/span&gt;&lt;span class="s2"&gt; * 100"&lt;/span&gt; | bc&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="nv"&gt;$INTERVAL&lt;/span&gt;&lt;span class="s2"&gt;] Actual: &lt;/span&gt;&lt;span class="nv"&gt;$ACTUAL&lt;/span&gt;&lt;span class="s2"&gt; | Forecast: &lt;/span&gt;&lt;span class="nv"&gt;$FORECAST&lt;/span&gt;&lt;span class="s2"&gt; | Variance: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VARIANCE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%"&lt;/span&gt;

    &lt;span class="c"&gt;# Alert if variance exceeds 15%&lt;/span&gt;
    &lt;span class="nv"&gt;ABS_VAR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$VARIANCE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ABS_VAR&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt; 15"&lt;/span&gt; | bc &lt;span class="nt"&gt;-l&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  WARNING: Variance exceeds 15% threshold"&lt;/span&gt;
    &lt;span class="k"&gt;fi
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this every 30 minutes via cron. Add email or Slack alerts when variance exceeds your threshold.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling: Turning Numbers Into Actual Shifts
&lt;/h2&gt;

&lt;p&gt;The forecast tells you how many agents you need per interval. Scheduling turns that into actual human schedules with shift starts, break times, and off days.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Core Scheduling Challenge
&lt;/h3&gt;

&lt;p&gt;You need 19 agents at 10:00 AM and 12 agents at 2:00 PM. You can not schedule 19 people for 10 AM and 12 different people for 2 PM -- agents work full shifts. The art of scheduling is building shift patterns that match the demand curve as closely as possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shift Types That Improve Coverage
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shift Type&lt;/th&gt;
&lt;th&gt;Hours&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Standard 8-hour&lt;/td&gt;
&lt;td&gt;8:00-4:30 or 9:00-5:30&lt;/td&gt;
&lt;td&gt;Baseline coverage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Split shift&lt;/td&gt;
&lt;td&gt;8:00-12:00 + 4:00-8:00&lt;/td&gt;
&lt;td&gt;Covering morning and evening peaks with a midday gap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Staggered start&lt;/td&gt;
&lt;td&gt;Shifts starting every 30 min from 7:30-9:30&lt;/td&gt;
&lt;td&gt;Smoothing the login surge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Part-time 4-hour&lt;/td&gt;
&lt;td&gt;10:00-2:00 or 4:00-8:00&lt;/td&gt;
&lt;td&gt;Peak coverage without full-shift cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overlap shift&lt;/td&gt;
&lt;td&gt;11:00-7:30&lt;/td&gt;
&lt;td&gt;Bridging the transition between morning and evening crews&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The most common mistake is scheduling everyone to start at the same time. If 50 agents all log in at 9:00 AM, you have zero coverage at 8:45 and a massive surplus at 9:05. Stagger starts across 30-minute windows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Break Scheduling
&lt;/h3&gt;

&lt;p&gt;Breaks must be staggered too. If all 50 agents go to lunch at noon, your noon-1 PM interval collapses. Spread lunch breaks across 11:30 AM to 1:30 PM in 15-minute waves.&lt;/p&gt;

&lt;p&gt;In VICIdial, you can enforce break windows using the Timeclock system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Admin &amp;gt; Timeclock &amp;gt; Shift Definition
    Shift ID:           MORNING_A
    Start Time:         08:00
    End Time:           16:30
    Lunch Start:        11:30
    Lunch End:          12:00
    Break 1 Start:      10:00
    Break 1 End:        10:15
    Break 2 Start:      14:00
    Break 2 End:        14:15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create multiple shift definitions with staggered break times and assign agents to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The WFM Weekly Cycle
&lt;/h2&gt;

&lt;p&gt;Workforce management is an ongoing cycle, not a one-time setup. Here is the weekly rhythm:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monday:&lt;/strong&gt; Review last week's forecast accuracy. Where were you off? Update the forecast model with last week's actuals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tuesday:&lt;/strong&gt; Build next week's schedule based on updated forecast. Post schedules at least 5 days in advance so agents can plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wednesday:&lt;/strong&gt; Run adherence reports for the current week. Coach agents who are consistently below 90%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thursday:&lt;/strong&gt; Review intraday performance for the week. Are there intervals where you are consistently over or under? Adjust next week's schedule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Friday:&lt;/strong&gt; Run the Erlang C calculator against next week's forecast. Verify that scheduled agents cover required agents for every interval. Flag any gaps for overtime or flex scheduling.&lt;/p&gt;

&lt;h3&gt;
  
  
  WFM Metrics Dashboard
&lt;/h3&gt;

&lt;p&gt;Build a weekly dashboard that tracks these numbers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;This Week&lt;/th&gt;
&lt;th&gt;Trend&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Forecast accuracy (daily)&lt;/td&gt;
&lt;td&gt;Within 5%&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forecast accuracy (interval)&lt;/td&gt;
&lt;td&gt;Within 10%&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average adherence&lt;/td&gt;
&lt;td&gt;92-95%&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service level&lt;/td&gt;
&lt;td&gt;80/20&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average occupancy&lt;/td&gt;
&lt;td&gt;75-85%&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shrinkage&lt;/td&gt;
&lt;td&gt;Under 35%&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent utilization&lt;/td&gt;
&lt;td&gt;85-90%&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Track trends week over week. A single bad week is noise. Three bad weeks in a row is a pattern that needs investigation.&lt;/p&gt;

&lt;p&gt;The operations team at &lt;a href="https://vicistack.com/" rel="noopener noreferrer"&gt;ViciStack&lt;/a&gt; builds WFM processes alongside dialer optimization because they are two sides of the same coin. The best dialer configuration in the world does not help if you do not have enough agents logged in to take the calls it connects. If your staffing math feels like guesswork and you want it replaced with actual forecasting, &lt;a href="https://vicistack.com/contact/" rel="noopener noreferrer"&gt;we can help with that&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>voip</category>
      <category>asterisk</category>
      <category>sysadmin</category>
      <category>devops</category>
    </item>
    <item>
      <title>Speech Analytics for Call Centers: From Call Recordings to Automated QA Without a Six-Figure Platform</title>
      <dc:creator>Jason Shouldice</dc:creator>
      <pubDate>Fri, 27 Mar 2026 23:12:15 +0000</pubDate>
      <link>https://dev.to/gamlin/speech-analytics-for-call-centers-from-call-recordings-to-automated-qa-without-a-six-figure-3fo3</link>
      <guid>https://dev.to/gamlin/speech-analytics-for-call-centers-from-call-recordings-to-automated-qa-without-a-six-figure-3fo3</guid>
      <description>&lt;p&gt;&lt;strong&gt;Last updated: March 2026 | Reading time: ~26 minutes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is the dirty secret of call center QA: most operations review 1-2% of their calls. A QA analyst listens to maybe 5-10 recordings per agent per month, fills out a scorecard, and hopes that sample is representative.&lt;/p&gt;

&lt;p&gt;It is not. Two percent coverage means 98% of your calls -- including compliance violations, missed upsells, and the call where your best agent snapped at a customer -- go completely unreviewed.&lt;/p&gt;

&lt;p&gt;Speech analytics changes that equation. Automated transcription and analysis can process 100% of your calls, flag the ones that matter, and hand your QA team a prioritized list instead of a random sample. McKinsey data shows contact centers using speech analytics see a 10% improvement in customer satisfaction scores. Sprinklr reports 20-30% cost savings and a 40% productivity boost when speech analytics is implemented properly.&lt;/p&gt;

&lt;p&gt;The problem is price. Enterprise speech analytics platforms from CallMiner, Verint, and NICE run $50K-$200K per year for a mid-sized operation. That prices out most call centers under 100 agents.&lt;/p&gt;

&lt;p&gt;But the underlying technology -- transcription via Whisper, sentiment analysis via open-source NLP models, keyword detection via pattern matching -- is available for the cost of a decent GPU server and some engineering time. If you already run &lt;a href="https://dev.to/blog/vicidial-call-recording/"&gt;VICIdial call recording&lt;/a&gt;, you are sitting on a goldmine of unanalyzed audio data.&lt;/p&gt;

&lt;p&gt;This guide covers how to build a working speech analytics pipeline: from call recordings to transcripts to automated &lt;a href="https://dev.to/blog/vicidial-qa-scoring/"&gt;QA scoring&lt;/a&gt;, using tools you can actually afford.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Speech Analytics Actually Does
&lt;/h2&gt;

&lt;p&gt;Strip away the vendor marketing and speech analytics breaks down into four concrete capabilities:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Transcription (Speech-to-Text)
&lt;/h3&gt;

&lt;p&gt;Convert audio recordings into searchable text. This is the foundation. Without accurate transcripts, nothing else works.&lt;/p&gt;

&lt;p&gt;Modern transcription accuracy with Whisper-class models runs 92-97% word error rate on clean call center audio. That is good enough for keyword detection and sentiment analysis, though you will still want human review on compliance-critical calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Keyword and Phrase Detection
&lt;/h3&gt;

&lt;p&gt;Search transcripts for specific words and phrases. The two main use cases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compliance monitoring&lt;/strong&gt; -- detect when agents skip required disclosures, make unauthorized promises, or use prohibited language.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sales intelligence&lt;/strong&gt; -- identify competitor mentions, objection patterns, buying signals, and pricing discussions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Sentiment Analysis
&lt;/h3&gt;

&lt;p&gt;Score the emotional tone of the conversation. Most implementations track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agent sentiment&lt;/strong&gt; -- are they frustrated, bored, engaged, professional?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customer sentiment&lt;/strong&gt; -- are they angry, confused, interested, ready to buy?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sentiment trajectory&lt;/strong&gt; -- did the call start negative and end positive (good recovery) or start positive and end negative (lost sale)?&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Automated QA Scoring
&lt;/h3&gt;

&lt;p&gt;Combine transcription, keywords, and sentiment into an automated quality score for each call. Instead of manually scoring 5 calls per agent per month, the system scores every call and surfaces the outliers for human review.&lt;/p&gt;

&lt;p&gt;Opus Research found that 68% of companies using speech analytics saw it as a cost-saving tool, and 52% saw direct revenue improvement. The ROI comes from doing more with less -- not replacing QA analysts, but focusing their time on the 5% of calls that actually need human attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Recording Pipeline
&lt;/h2&gt;

&lt;p&gt;Speech analytics starts with audio files. If your recordings are bad, your transcripts will be bad, and your analytics will be useless.&lt;/p&gt;

&lt;h3&gt;
  
  
  VICIdial Recording Configuration
&lt;/h3&gt;

&lt;p&gt;Set recording at the campaign level for full coverage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Campaign &amp;gt; Detail &amp;gt; Recording: ALLFORCE
Campaign &amp;gt; Detail &amp;gt; Recording Method: STEREO
Campaign &amp;gt; Detail &amp;gt; Recording Filename: FULLDATE_AGENT_CUSTPHONE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ALLFORCE&lt;/strong&gt; records every call regardless of agent action. ALLCALLS lets agents control recording (they will forget or skip it).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;STEREO&lt;/strong&gt; records agent and customer on separate channels. This is critical for speech analytics because it lets you run sentiment analysis on each speaker independently. MONO mixes both channels, making speaker separation impossible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filename format&lt;/strong&gt; with date, agent, and customer phone makes it easy to correlate recordings with CDR data later.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Recording Storage and Format
&lt;/h3&gt;

&lt;p&gt;VICIdial stores recordings in &lt;code&gt;/var/spool/asterisk/monitor/&lt;/code&gt; by default, organized by date. The default format is WAV (uncompressed).&lt;/p&gt;

&lt;p&gt;For speech analytics processing, you want WAV files -- not MP3. Transcription models work better with uncompressed audio. If storage is a concern, compress after transcription:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check recording storage usage&lt;/span&gt;
&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; /var/spool/asterisk/monitor/
&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; /var/spool/asterisk/monitor/&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y/%m/%d&lt;span class="si"&gt;)&lt;/span&gt;/

&lt;span class="c"&gt;# Count recordings per day&lt;/span&gt;
find /var/spool/asterisk/monitor/&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y/%m/%d&lt;span class="si"&gt;)&lt;/span&gt;/ &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.wav"&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A typical 50-agent operation generates 3-5 GB of WAV recordings per day. That is about 1.5 TB per year. Budget your storage accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Centralizing Recordings for Processing
&lt;/h3&gt;

&lt;p&gt;If you are running a multi-server VICIdial cluster, recordings live on whichever server handled the call. You need to centralize them before processing.&lt;/p&gt;

&lt;p&gt;Set up an rsync job to pull recordings to your analytics server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# sync-recordings.sh - Pull recordings from VICIdial servers to analytics box&lt;/span&gt;
&lt;span class="nv"&gt;ANALYTICS_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/data/recordings"&lt;/span&gt;
&lt;span class="nv"&gt;VICIDIAL_SERVERS&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;"vici-web1"&lt;/span&gt; &lt;span class="s2"&gt;"vici-tel1"&lt;/span&gt; &lt;span class="s2"&gt;"vici-tel2"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;TODAY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y/%m/%d&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;server &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VICIDIAL_SERVERS&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;rsync &lt;span class="nt"&gt;-avz&lt;/span&gt; &lt;span class="nt"&gt;--include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*.wav"&lt;/span&gt; &lt;span class="nt"&gt;--exclude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;server&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:/var/spool/asterisk/monitor/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TODAY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ANALYTICS_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TODAY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this via cron every hour during operating hours. Set it up on the analytics server (not the VICIdial servers -- you don't want rsync load on your telephony boxes during peak dialing).&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying the Transcription Engine
&lt;/h2&gt;

&lt;p&gt;This is where the magic (and the compute cost) lives. You need a speech-to-text model that can process hundreds of recordings per day with acceptable accuracy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: OpenAI Whisper (Open Source, Self-Hosted)
&lt;/h3&gt;

&lt;p&gt;Whisper is OpenAI's open-source speech recognition model. The &lt;code&gt;large-v3&lt;/code&gt; model delivers near-human accuracy on English call center audio. It runs on any NVIDIA GPU with 10+ GB VRAM.&lt;/p&gt;

&lt;p&gt;Install Whisper on your GPU server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;openai-whisper

&lt;span class="c"&gt;# Or for faster inference, use faster-whisper (CTranslate2 backend)&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;faster-whisper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;faster-whisper is the practical choice for production. It runs 4x faster than vanilla Whisper with the same accuracy, and uses half the VRAM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Batch Transcription Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;batch_transcribe.py - Transcribe call recordings using faster-whisper&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;faster_whisper&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WhisperModel&lt;/span&gt;

&lt;span class="n"&gt;MODEL_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;large-v3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;DEVICE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cuda&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;        &lt;span class="c1"&gt;# use "cpu" if no GPU (much slower)
&lt;/span&gt;&lt;span class="n"&gt;COMPUTE_TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;float16&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# use "int8" on older GPUs for speed
&lt;/span&gt;&lt;span class="n"&gt;RECORDINGS_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/recordings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TRANSCRIPTS_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/transcripts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BATCH_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WhisperModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MODEL_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEVICE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compute_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;COMPUTE_TYPE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transcribe_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wav_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Transcribe a single WAV file and return segments with timestamps.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transcribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wav_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;beam_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wav_path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language_probability&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;language_probability&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;duration_seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;full_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="n"&gt;full_text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;full_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_day&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Process all recordings for a given date.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;day_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RECORDINGS_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day_dir&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No recordings directory for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;date_str&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;out_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TRANSCRIPTS_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;wav_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;already_done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pending&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;wav_files&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;already_done&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;date_str&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; new recordings to transcribe &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;already_done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; already done)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wav_path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;BATCH_SIZE&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transcribe_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wav_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;out_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;out_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wav_path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.wav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Transcribed &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="n"&gt;BATCH_SIZE&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  ERROR transcribing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;wav_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;target_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;process_day&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On an NVIDIA RTX 3090 with faster-whisper large-v3, expect about 30 seconds of processing per minute of audio. A 3-minute call takes ~90 seconds to transcribe. A 50-agent operation generating 500 calls per day (average 4 minutes each) needs about 17 hours of GPU time -- just barely doable in 24 hours on a single GPU.&lt;/p&gt;

&lt;p&gt;For larger operations, run multiple GPUs or use the &lt;code&gt;medium&lt;/code&gt; model (2x faster, ~3% lower accuracy). The accuracy tradeoff is worth it above 1,000 calls per day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Cloud Transcription APIs
&lt;/h3&gt;

&lt;p&gt;If you do not want to manage GPU infrastructure:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Cost per Minute&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;Latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deepgram&lt;/td&gt;
&lt;td&gt;$0.0043&lt;/td&gt;
&lt;td&gt;95%+&lt;/td&gt;
&lt;td&gt;Real-time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AssemblyAI&lt;/td&gt;
&lt;td&gt;$0.0065&lt;/td&gt;
&lt;td&gt;94%+&lt;/td&gt;
&lt;td&gt;Near real-time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Speech-to-Text&lt;/td&gt;
&lt;td&gt;$0.009&lt;/td&gt;
&lt;td&gt;93%+&lt;/td&gt;
&lt;td&gt;Near real-time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Transcribe&lt;/td&gt;
&lt;td&gt;$0.024&lt;/td&gt;
&lt;td&gt;92%+&lt;/td&gt;
&lt;td&gt;Batch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At $0.0043/minute (Deepgram), a 50-agent operation with 500 calls averaging 4 minutes costs about $86/day or ~$2,580/month. Cheaper than enterprise speech analytics platforms, but it adds up.&lt;/p&gt;

&lt;p&gt;Self-hosted Whisper costs you only the GPU hardware (~$1,500 one-time for a used RTX 3090 or $300/month for a cloud GPU instance), making it the better choice if you have the engineering capacity to maintain it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Keyword and Compliance Detection
&lt;/h2&gt;

&lt;p&gt;With transcripts in hand, the next layer is keyword detection. This is the simplest and highest-ROI part of the pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compliance Keyword Lists
&lt;/h3&gt;

&lt;p&gt;Define keyword groups based on what your QA team needs to catch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compliance_required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Phrases agents MUST say on every call"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"phrases"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"this call may be recorded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"this call is being recorded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"for quality and training purposes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"my name is"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"alert_on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"missing"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compliance_prohibited"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Phrases agents must NEVER say"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"phrases"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"i guarantee"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"guaranteed results"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"no risk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"100 percent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"i promise"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"alert_on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"present"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"competitor_mentions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Competitor names for competitive intelligence"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"phrases"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"five9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"convoso"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"genesys"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"talkdesk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"ringcentral"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nice incontact"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dialpad"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"alert_on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"present"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"buying_signals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Positive buying indicators"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"phrases"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"how much does it cost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"what are the terms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"when can we start"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"send me the contract"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"sounds good"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"alert_on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"present"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"objection_patterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Common customer objections"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"phrases"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"too expensive"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"not interested"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"already have"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"call me back"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"take me off your list"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"alert_on"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"present"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Keyword Detection Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;keyword_scan.py - Scan transcripts for compliance and intelligence keywords&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="n"&gt;KEYWORDS_FILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/analytics/keyword_config.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TRANSCRIPTS_DIR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/transcripts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_keywords&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KEYWORDS_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scan_transcript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Scan a single transcript against all keyword groups.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;text_lower&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;full_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;group_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;matched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phrases&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text_lower&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alert_on&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phrases&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text_lower&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;group&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;group_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing_required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing_phrases&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;severity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
                &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alert_on&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;present&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;severity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prohibited&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;group_name&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;info&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;group&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;group_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;detected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matched_phrases&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;severity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prohibited&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;group_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scan_day&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Scan all transcripts for a given date.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;keywords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_keywords&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;day_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TRANSCRIPTS_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day_dir&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;json_file&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;transcript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scan_transcript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;flagged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;date_str&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; calls scanned, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flagged&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; flagged (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flagged&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;high_severity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;fl&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;severity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fl&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;high_severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  HIGH SEVERITY FLAGS: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;high_severity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; calls need immediate review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;high_severity&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: score=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                  &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flags=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fl&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;group&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fl&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;flags&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs in seconds even on thousands of transcripts because it is just string matching. No GPU needed. Schedule it to run right after transcription completes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Sentiment Analysis
&lt;/h2&gt;

&lt;p&gt;Sentiment scoring tells you how calls &lt;em&gt;feel&lt;/em&gt;, not just what was said. An agent who hits every compliance checkbox but sounds dead inside is still going to lose sales.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lightweight Sentiment Scoring
&lt;/h3&gt;

&lt;p&gt;You do not need a massive NLP model for call center sentiment. A fine-tuned transformer running on CPU handles it fine. The &lt;code&gt;cardiffnlp/twitter-roberta-base-sentiment-latest&lt;/code&gt; model from Hugging Face is a solid starting point -- it is small, fast, and accurate enough for conversation segments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;sentiment_score.py - Score transcript segments for sentiment&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;sentiment_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentiment-analysis&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cardiffnlp/twitter-roberta-base-sentiment-latest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# CPU; use 0 for GPU
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;LABEL_MAP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;positive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;neutral&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;negative&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;score_transcript&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Score each segment and compute call-level sentiment metrics.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcript_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="n"&gt;scored_segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sentiment_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;])[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;scored_segments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentiment_value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;LABEL_MAP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;scored_segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentiment_value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scored_segments&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;first_third&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;
    &lt;span class="n"&gt;last_third&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total_segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scored_segments&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;average_sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;opening_sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_third&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_third&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;closing_sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_third&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_third&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentiment_trajectory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_third&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_third&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_third&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_third&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;negative_segment_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scored_segments&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;negative&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;positive_segment_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scored_segments&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;positive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scored_segments&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What the Sentiment Numbers Mean
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Good Range&lt;/th&gt;
&lt;th&gt;Warning&lt;/th&gt;
&lt;th&gt;Action Required&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Average Sentiment&lt;/td&gt;
&lt;td&gt;0.1 to 0.5&lt;/td&gt;
&lt;td&gt;-0.1 to 0.1&lt;/td&gt;
&lt;td&gt;Below -0.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opening Sentiment&lt;/td&gt;
&lt;td&gt;0.2+&lt;/td&gt;
&lt;td&gt;0.0 to 0.2&lt;/td&gt;
&lt;td&gt;Below 0.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Closing Sentiment&lt;/td&gt;
&lt;td&gt;0.3+&lt;/td&gt;
&lt;td&gt;0.0 to 0.3&lt;/td&gt;
&lt;td&gt;Below 0.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentiment Trajectory&lt;/td&gt;
&lt;td&gt;Positive (going up)&lt;/td&gt;
&lt;td&gt;Flat&lt;/td&gt;
&lt;td&gt;Negative (going down)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Negative Segment %&lt;/td&gt;
&lt;td&gt;Under 15%&lt;/td&gt;
&lt;td&gt;15-30%&lt;/td&gt;
&lt;td&gt;Over 30%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The most important metric is &lt;strong&gt;sentiment trajectory&lt;/strong&gt;. A call that starts negative and ends positive means the agent recovered the situation. A call that starts positive and ends negative means the agent lost the customer during the conversation -- and that is where your coaching should focus.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting Analytics to Your QA Workflow
&lt;/h2&gt;

&lt;p&gt;The analytics pipeline produces three outputs: transcripts, keyword flags, and sentiment scores. The last step is turning those into actionable QA workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Priority-Based Review Queue
&lt;/h3&gt;

&lt;p&gt;Instead of random sampling, build a review queue that prioritizes calls based on risk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_review_queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyword_results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sentiment_results&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Merge keyword and sentiment data into a prioritized review queue.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;sentiment_lookup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sentiment_results&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;kr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;keyword_results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sentiment_lookup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
        &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="c1"&gt;# High-severity keyword flags
&lt;/span&gt;        &lt;span class="n"&gt;high_flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;kr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;severity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;high_flags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;

        &lt;span class="c1"&gt;# Low keyword QA score
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;

        &lt;span class="c1"&gt;# Negative sentiment trajectory
&lt;/span&gt;        &lt;span class="n"&gt;trajectory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentiment_trajectory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;trajectory&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;

        &lt;span class="c1"&gt;# High negative segment count
&lt;/span&gt;        &lt;span class="n"&gt;neg_pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;negative_segment_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;sent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;total_segments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;neg_pct&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priority&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keyword_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentiment_avg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;average_sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentiment_trajectory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;trajectory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review_reasons&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;high_flags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review_reasons&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;compliance_flag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;trajectory&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review_reasons&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;negative_trajectory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review_reasons&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low_qa_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priority&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives your QA team a sorted list where the worst calls float to the top. A compliance violation with negative sentiment trajectory gets reviewed first. A clean call with neutral sentiment gets skipped entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent-Level Reporting
&lt;/h3&gt;

&lt;p&gt;Aggregate the per-call data to agent-level metrics for coaching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyword_score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_qa_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avg_sentiment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_sentiment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sentiment_trajectory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_trajectory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;keyword_score&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;low_score_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;has_compliance_flag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;compliance_flags&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;call_analytics&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;avg_qa_score&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agents with consistently low QA scores or high compliance flag counts need targeted coaching. Agents with negative sentiment trajectories might be burning out -- route them to the conversation about workload and &lt;a href="https://dev.to/blog/call-center-agent-burnout/"&gt;break scheduling&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Full Pipeline Schedule
&lt;/h3&gt;

&lt;p&gt;Put it all together with a cron schedule on your analytics server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# crontab for speech analytics pipeline&lt;/span&gt;
&lt;span class="c"&gt;# Sync recordings every hour during business hours&lt;/span&gt;
0 8-20 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 1-5 /data/scripts/sync-recordings.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/analytics/sync.log 2&amp;gt;&amp;amp;1

&lt;span class="c"&gt;# Transcribe previous day's recordings overnight&lt;/span&gt;
0 22 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; python3 /data/scripts/batch_transcribe.py &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; yesterday +%Y-%m-%d&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/analytics/transcribe.log 2&amp;gt;&amp;amp;1

&lt;span class="c"&gt;# Run keyword scan after transcription&lt;/span&gt;
0 4 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; python3 /data/scripts/keyword_scan.py &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; yesterday +%Y-%m-%d&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/analytics/keywords.log 2&amp;gt;&amp;amp;1

&lt;span class="c"&gt;# Run sentiment scoring after keywords&lt;/span&gt;
0 5 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; python3 /data/scripts/sentiment_score.py &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; yesterday +%Y-%m-%d&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/analytics/sentiment.log 2&amp;gt;&amp;amp;1

&lt;span class="c"&gt;# Generate review queue and agent reports&lt;/span&gt;
0 6 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; python3 /data/scripts/build_reports.py &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; yesterday +%Y-%m-%d&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/analytics/reports.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By 6 AM, yesterday's calls are fully transcribed, scanned, scored, and prioritized. Your QA team starts their day with a ready-to-go review queue instead of randomly picking recordings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Comparison: Build vs. Buy
&lt;/h2&gt;

&lt;p&gt;Here is the honest math on building this yourself versus buying an enterprise platform:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Self-Hosted Cost&lt;/th&gt;
&lt;th&gt;Enterprise Platform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Transcription (500 calls/day)&lt;/td&gt;
&lt;td&gt;$300/mo (GPU) or $2,500/mo (API)&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentiment Analysis&lt;/td&gt;
&lt;td&gt;$0 (open-source model, CPU)&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keyword Detection&lt;/td&gt;
&lt;td&gt;$0 (pattern matching)&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QA Dashboard&lt;/td&gt;
&lt;td&gt;Engineering time (40-80 hours)&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-Time Alerts&lt;/td&gt;
&lt;td&gt;Engineering time (20-40 hours)&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent Coaching Workflow&lt;/td&gt;
&lt;td&gt;Manual process&lt;/td&gt;
&lt;td&gt;Automated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total Year 1&lt;/td&gt;
&lt;td&gt;$4K-$30K + 60-120 hrs engineering&lt;/td&gt;
&lt;td&gt;$50K-$200K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total Year 2+&lt;/td&gt;
&lt;td&gt;$4K-$30K/year&lt;/td&gt;
&lt;td&gt;$50K-$200K/year&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The break-even point depends on your engineering capacity. If you have a developer who can build and maintain the pipeline, self-hosting saves $20K-$170K per year. If you don't, the engineering cost might push you toward a mid-tier vendor like Observe.AI or Level AI that costs $30K-$60K.&lt;/p&gt;

&lt;p&gt;For VICIdial operations specifically, the self-hosted route makes more sense because you already have the infrastructure -- the recordings, the database, the server environment. The teams at &lt;a href="https://vicistack.com/" rel="noopener noreferrer"&gt;ViciStack&lt;/a&gt; have deployed this pipeline across multiple call centers, and the typical implementation takes 2-3 weeks from recording configuration to working QA dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Do First
&lt;/h2&gt;

&lt;p&gt;Start with transcription. Just get your recordings turned into text. Even without keyword detection or sentiment analysis, searchable transcripts change how your QA team works.&lt;/p&gt;

&lt;p&gt;The next step is the compliance keyword scan -- it is the highest-ROI piece because a single compliance violation can cost more than the entire analytics pipeline.&lt;/p&gt;

&lt;p&gt;Sentiment scoring comes last. It is valuable for coaching and agent development, but it does not prevent fires the way compliance monitoring does.&lt;/p&gt;

&lt;p&gt;If you want this built and running in two weeks instead of two months, &lt;a href="https://vicistack.com/contact/" rel="noopener noreferrer"&gt;talk to us&lt;/a&gt;. We have done this integration enough times that the architecture decisions are already made -- it is just configuration and deployment at this point.&lt;/p&gt;

</description>
      <category>voip</category>
      <category>asterisk</category>
      <category>sysadmin</category>
      <category>devops</category>
    </item>
    <item>
      <title>SMS Campaign Integration for Call Centers: 10DLC Registration, Dialer Workflows, and Cadences That Convert</title>
      <dc:creator>Jason Shouldice</dc:creator>
      <pubDate>Fri, 27 Mar 2026 22:56:02 +0000</pubDate>
      <link>https://dev.to/gamlin/sms-campaign-integration-for-call-centers-10dlc-registration-dialer-workflows-and-cadences-that-2h56</link>
      <guid>https://dev.to/gamlin/sms-campaign-integration-for-call-centers-10dlc-registration-dialer-workflows-and-cadences-that-2h56</guid>
      <description>&lt;p&gt;&lt;strong&gt;Last updated: March 2026 | Reading time: ~26 minutes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your agents dial 400 numbers a day and talk to maybe 60 of the people attached to them. The other 340 calls go to voicemail, get screened, or ring out. Meanwhile, 90% of text messages get read within 3 minutes of delivery.&lt;/p&gt;

&lt;p&gt;That gap -- between phone calls that don't connect and text messages that get read almost instantly -- is where most call centers are leaving money on the table. The existing guide on &lt;a href="https://dev.to/blog/sms-campaign-call-center/"&gt;SMS campaigns for call centers&lt;/a&gt; covers the compliance fundamentals and cadence theory. This guide goes into the technical integration: how to wire SMS into your VICIdial dialer workflow so texts fire automatically based on call outcomes, and how to build multi-channel cadences that actually improve your &lt;a href="https://dev.to/blog/contact-rate-optimization-guide/"&gt;contact rate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But first, the compliance piece, because if you get that wrong, nothing else matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  10DLC Registration: The Non-Optional First Step
&lt;/h2&gt;

&lt;p&gt;Since February 1, 2025, US carriers block 100% of unregistered Application-to-Person (A2P) traffic sent over 10-digit long code (10DLC) numbers. There are no exceptions, no grace period, and no workaround. If your messages are not registered, they are not delivered.&lt;/p&gt;

&lt;h3&gt;
  
  
  What 10DLC Is
&lt;/h3&gt;

&lt;p&gt;10DLC is a system that lets businesses send text messages using standard 10-digit phone numbers (the same numbers you use for calling) instead of short codes (5-6 digit numbers). The registration happens in two parts through The Campaign Registry (TCR):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Brand Registration&lt;/strong&gt; -- register your business entity. TCR verifies your EIN, legal name, and contact information. This is a one-time step per business.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Campaign Registration&lt;/strong&gt; -- register each SMS use case. A sales follow-up campaign is separate from an appointment reminder campaign. Each campaign gets its own throughput limits and content rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Registration Process and Timeline
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Timeline&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Brand registration&lt;/td&gt;
&lt;td&gt;1-5 business days&lt;/td&gt;
&lt;td&gt;$4 one-time (standard)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brand vetting (higher throughput)&lt;/td&gt;
&lt;td&gt;3-10 business days&lt;/td&gt;
&lt;td&gt;$40 one-time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Campaign registration&lt;/td&gt;
&lt;td&gt;1-7 business days&lt;/td&gt;
&lt;td&gt;$10/month per campaign&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total from start to sending&lt;/td&gt;
&lt;td&gt;1-3 weeks&lt;/td&gt;
&lt;td&gt;~$54 to start&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The timeline varies by provider. Twilio, Telnyx, and Bandwidth all handle TCR registration through their dashboards. If you are already using one of these for SIP trunking, use the same provider for SMS to simplify the setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Throughput Limits by Trust Score
&lt;/h3&gt;

&lt;p&gt;After registration, your brand gets a trust score that determines how many messages you can send:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trust Score&lt;/th&gt;
&lt;th&gt;Message Segments per Second&lt;/th&gt;
&lt;th&gt;Daily Cap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Low (unvetted)&lt;/td&gt;
&lt;td&gt;0.2&lt;/td&gt;
&lt;td&gt;~17,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;~86,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High (vetted)&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;td&gt;~260,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a call center running 50 agents at 400 dials per day, you generate roughly 200-300 SMS messages per day (assuming you text every no-answer and voicemail). Even the lowest trust tier handles that volume with room to spare. But if you plan to run marketing blasts or appointment reminders in addition to disposition-triggered texts, get the brand vetting done upfront.&lt;/p&gt;

&lt;h2&gt;
  
  
  TCPA Compliance for Text Messages
&lt;/h2&gt;

&lt;p&gt;The TCPA applies to text messages exactly like it applies to phone calls. The fines are the same: $500 per violation for unintentional, $1,500 per violation for willful. On a list of 10,000 contacts, that is $5 million to $15 million in theoretical exposure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consent Requirements
&lt;/h3&gt;

&lt;p&gt;Since late 2023, the FCC requires &lt;strong&gt;one-to-one consent&lt;/strong&gt;. A lead who gave permission to Company A does not automatically consent to messages from Company B, even if both companies bought the lead from the same source.&lt;/p&gt;

&lt;p&gt;Your consent record must include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Required consent documentation per subscriber:
- Phone number
- Timestamp of consent (UTC)
- IP address (for web opt-ins)
- Exact consent language shown to the subscriber
- Source (web form URL, paper form scan, verbal recording)
- Campaign(s) consented to
- Expected message frequency disclosed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store this in your CRM or a dedicated consent database. You need it to defend against TCPA claims, and "we had consent but can not prove it" is the same as "we did not have consent" in court.&lt;/p&gt;

&lt;h3&gt;
  
  
  Opt-Out Handling
&lt;/h3&gt;

&lt;p&gt;As of April 2025, businesses must honor opt-out requests within 10 business days and accept revocation through any reasonable method. In practice, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Respond to STOP, UNSUBSCRIBE, CANCEL, END, and QUIT keywords automatically&lt;/li&gt;
&lt;li&gt;Process opt-outs from email requests, phone calls, and &lt;a href="https://dev.to/blog/vicidial-agent-screen-customization/"&gt;web forms&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Remove the number from all SMS campaigns (not just the one they replied to)&lt;/li&gt;
&lt;li&gt;Send a confirmation message after processing the opt-out&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every outbound SMS must include opt-out instructions. The standard footer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reply STOP to unsubscribe. Msg &amp;amp; data rates may apply.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Timing Restrictions
&lt;/h3&gt;

&lt;p&gt;Same as calling: no messages before 8 AM or after 9 PM in the recipient's local time zone. Your messaging gateway needs timezone awareness, just like your dialer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring SMS Into VICIdial Workflows
&lt;/h2&gt;

&lt;p&gt;VICIdial does not have native SMS sending. You need an external messaging gateway connected via API triggers. The integration pattern is straightforward: VICIdial dispositions a call, a script detects the disposition, and fires an SMS through your provider's API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture Overview
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VICIdial Agent dispositions call → vicidial_log updated
          ↓
Polling script reads new dispositions every 30-60 seconds
          ↓
Disposition-to-SMS mapping determines which template to send
          ↓
API call to Telnyx/Twilio/SignalWire sends the message
          ↓
SMS delivery status logged to sms_log table
          ↓
Inbound replies routed back to agent screen or queue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Disposition Polling Script
&lt;/h3&gt;

&lt;p&gt;This script runs as a cron job, checking for new call dispositions and firing SMS messages based on the outcome:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;sms_trigger.py - Send SMS based on VICIdial call dispositions&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mysql.connector&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="c1"&gt;# Configuration
&lt;/span&gt;&lt;span class="n"&gt;DB_CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cron&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VICI_DB_PASS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;database&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vicidial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Telnyx API (swap for Twilio/SignalWire as needed)
&lt;/span&gt;&lt;span class="n"&gt;TELNYX_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TELNYX_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;TELNYX_FROM_NUMBER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+15551234567&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TELNYX_MESSAGING_PROFILE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TELNYX_MSG_PROFILE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Disposition-to-SMS mapping
&lt;/span&gt;&lt;span class="n"&gt;DISPOSITION_SMS_MAP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;template&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hi {first_name}, we tried reaching you about {campaign_topic}. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Text back a good time to talk. Reply STOP to opt out.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delay_seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_sends&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cooldown_hours&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;template&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hi {first_name}, we left you a voicemail about {campaign_topic}. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Have a quick question? Text us back. Reply STOP to opt out.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delay_seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_sends&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cooldown_hours&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CALLBK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;template&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hi {first_name}, confirming your callback for {callback_date}. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Text YES to confirm or suggest a new time. Reply STOP to opt out.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delay_seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_sends&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cooldown_hours&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SALE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;template&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Thanks {first_name}! Your enrollment is confirmed. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your rep {agent_name} is your point of contact. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reply STOP to opt out.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delay_seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_sends&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cooldown_hours&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_new_dispositions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;since_minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Pull recent call dispositions from VICIdial.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;DB_CONFIG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dictionary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        SELECT
            v.uniqueid, v.lead_id, v.user AS agent_user,
            v.status AS disposition, v.phone_number,
            v.call_date, v.campaign_id,
            l.first_name, l.last_name
        FROM vicidial_log v
        JOIN vicidial_list l ON v.lead_id = l.lead_id
        WHERE v.call_date &amp;gt;= NOW() - INTERVAL %s MINUTE
            AND v.status IN (&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NA&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AM&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;CALLBK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SALE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)
            AND v.phone_number NOT IN (
                SELECT phone_number FROM sms_dnc_list
            )
            AND v.phone_number NOT IN (
                SELECT phone_number FROM sms_log
                WHERE sent_at &amp;gt;= NOW() - INTERVAL 24 HOUR
                AND disposition = v.status
            )
        ORDER BY v.call_date DESC
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;since_minutes&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_sms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Send SMS via Telnyx API.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.telnyx.com/v2/messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TELNYX_API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TELNYX_FROM_NUMBER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+1&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;to_number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messaging_profile_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TELNYX_MESSAGING_PROFILE&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_sms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;disposition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Log SMS send to database for tracking and deduplication.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;DB_CONFIG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        INSERT INTO sms_log
            (lead_id, phone_number, disposition, message, status, sent_at)
        VALUES (%s, %s, %s, %s, %s, NOW())
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;disposition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_dispositions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Main processing loop.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;dispositions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_new_dispositions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;since_minutes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;dispo&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dispositions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sms_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DISPOSITION_SMS_MAP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dispo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disposition&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;sms_config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sms_config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;template&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dispo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;there&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;campaign_topic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your recent inquiry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;callback_date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your scheduled time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;agent_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;dispo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your representative&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Check timing restriction (8 AM - 9 PM local)
&lt;/span&gt;        &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;log_sms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dispo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lead_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;dispo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone_number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="n"&gt;dispo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disposition&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deferred_time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;send_sms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dispo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone_number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;log_sms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dispo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lead_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;dispo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone_number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;dispo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disposition&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;process_dispositions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Database Tables for SMS Tracking
&lt;/h3&gt;

&lt;p&gt;Create the tracking tables in your VICIdial database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;sms_log&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;phone_number&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;disposition&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;ENUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'sent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'failed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'delivered'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'deferred_time'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'opted_out'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'sent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sent_at&lt;/span&gt; &lt;span class="nb"&gt;DATETIME&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;delivered_at&lt;/span&gt; &lt;span class="nb"&gt;DATETIME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;response_text&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;response_at&lt;/span&gt; &lt;span class="nb"&gt;DATETIME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_phone_date&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sent_at&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_lead&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_status&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sent_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;InnoDB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;sms_dnc_list&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;phone_number&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;opted_out_at&lt;/span&gt; &lt;span class="nb"&gt;DATETIME&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;opt_out_keyword&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;source&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'sms_reply'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;InnoDB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;sms_templates&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;template_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;disposition&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;message_text&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;delay_seconds&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_sends&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cooldown_hours&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="nb"&gt;TINYINT&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;DATETIME&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;idx_dispo_campaign&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;disposition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;InnoDB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cron Schedule
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run disposition-triggered SMS every 2 minutes during operating hours&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;/2 8-20 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 1-6 python3 /opt/sms-integration/sms_trigger.py &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/sms/trigger.log 2&amp;gt;&amp;amp;1

&lt;span class="c"&gt;# Process inbound SMS replies every minute&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt; 8-21 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; python3 /opt/sms-integration/sms_inbound.py &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/sms/inbound.log 2&amp;gt;&amp;amp;1

&lt;span class="c"&gt;# Daily SMS report&lt;/span&gt;
0 7 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; python3 /opt/sms-integration/sms_report.py &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/sms/daily_report.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Multi-Touch Cadence Design
&lt;/h2&gt;

&lt;p&gt;Sending a single text after a missed call is table stakes. The real conversion improvement comes from building multi-touch cadences that alternate voice and text across multiple days.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 7-Day High-Intent Cadence
&lt;/h3&gt;

&lt;p&gt;For warm leads (web form submissions, inbound inquiries):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Day 1&lt;/td&gt;
&lt;td&gt;+0 min&lt;/td&gt;
&lt;td&gt;Call&lt;/td&gt;
&lt;td&gt;Immediate call attempt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 1&lt;/td&gt;
&lt;td&gt;+1 min&lt;/td&gt;
&lt;td&gt;SMS&lt;/td&gt;
&lt;td&gt;"We just tried calling about your inquiry..."&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 1&lt;/td&gt;
&lt;td&gt;+4 hours&lt;/td&gt;
&lt;td&gt;Call&lt;/td&gt;
&lt;td&gt;Second attempt, different time slot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 1&lt;/td&gt;
&lt;td&gt;+4.5 hours&lt;/td&gt;
&lt;td&gt;SMS&lt;/td&gt;
&lt;td&gt;"Still hoping to connect..." (only if no answer)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 2&lt;/td&gt;
&lt;td&gt;AM&lt;/td&gt;
&lt;td&gt;Call&lt;/td&gt;
&lt;td&gt;Morning attempt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 2&lt;/td&gt;
&lt;td&gt;PM&lt;/td&gt;
&lt;td&gt;SMS&lt;/td&gt;
&lt;td&gt;Value-add content (not just "call us back")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 3&lt;/td&gt;
&lt;td&gt;PM&lt;/td&gt;
&lt;td&gt;Call&lt;/td&gt;
&lt;td&gt;Afternoon attempt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 4&lt;/td&gt;
&lt;td&gt;AM&lt;/td&gt;
&lt;td&gt;SMS&lt;/td&gt;
&lt;td&gt;Social proof or &lt;a href="https://dev.to/blog/vicidial-roi-case-study/"&gt;case study&lt;/a&gt; link&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 5&lt;/td&gt;
&lt;td&gt;PM&lt;/td&gt;
&lt;td&gt;Call&lt;/td&gt;
&lt;td&gt;Last high-effort attempt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 7&lt;/td&gt;
&lt;td&gt;AM&lt;/td&gt;
&lt;td&gt;SMS&lt;/td&gt;
&lt;td&gt;Final text with direct calendar link&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The 14-Day Cold Outbound Cadence
&lt;/h3&gt;

&lt;p&gt;For purchased leads or cold lists:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;th&gt;Template Focus&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Day 1&lt;/td&gt;
&lt;td&gt;Call&lt;/td&gt;
&lt;td&gt;Initial contact attempt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 1&lt;/td&gt;
&lt;td&gt;SMS&lt;/td&gt;
&lt;td&gt;Brief introduction + value prop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 3&lt;/td&gt;
&lt;td&gt;Call&lt;/td&gt;
&lt;td&gt;Second attempt, different time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 5&lt;/td&gt;
&lt;td&gt;SMS&lt;/td&gt;
&lt;td&gt;Industry stat or pain point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 7&lt;/td&gt;
&lt;td&gt;Call&lt;/td&gt;
&lt;td&gt;Third attempt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 7&lt;/td&gt;
&lt;td&gt;SMS&lt;/td&gt;
&lt;td&gt;Case study or testimonial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 10&lt;/td&gt;
&lt;td&gt;Call&lt;/td&gt;
&lt;td&gt;Fourth attempt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 12&lt;/td&gt;
&lt;td&gt;SMS&lt;/td&gt;
&lt;td&gt;Direct offer or incentive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 14&lt;/td&gt;
&lt;td&gt;Call + SMS&lt;/td&gt;
&lt;td&gt;Final attempt + breakup message&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The breakup message on Day 14 is surprisingly effective. Something like: "Last attempt to reach you about [topic]. If the timing isn't right, no hard feelings. Text LATER if you want us to try again next month."&lt;/p&gt;

&lt;h3&gt;
  
  
  Cadence Performance Benchmarks
&lt;/h3&gt;

&lt;p&gt;Track these metrics per cadence:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Good&lt;/th&gt;
&lt;th&gt;Average&lt;/th&gt;
&lt;th&gt;Poor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SMS delivery rate&lt;/td&gt;
&lt;td&gt;97%+&lt;/td&gt;
&lt;td&gt;93-96%&lt;/td&gt;
&lt;td&gt;Below 93%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMS response rate&lt;/td&gt;
&lt;td&gt;8-15%&lt;/td&gt;
&lt;td&gt;4-7%&lt;/td&gt;
&lt;td&gt;Below 4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMS opt-out rate&lt;/td&gt;
&lt;td&gt;Below 2%&lt;/td&gt;
&lt;td&gt;2-5%&lt;/td&gt;
&lt;td&gt;Above 5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Call + SMS &lt;a href="https://dev.to/blog/contact-rate-optimization/"&gt;contact rate&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;35-45%&lt;/td&gt;
&lt;td&gt;25-34%&lt;/td&gt;
&lt;td&gt;Below 25%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cadence-to-conversion&lt;/td&gt;
&lt;td&gt;8-12%&lt;/td&gt;
&lt;td&gt;4-7%&lt;/td&gt;
&lt;td&gt;Below 4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key metric is &lt;strong&gt;Call + SMS contact rate&lt;/strong&gt; compared to call-only contact rate. If you are running 18% contact rate on calls alone and 35% with the SMS cadence layered in, that is a 94% improvement in conversations &lt;a href="https://dev.to/blog/call-center-cost-per-lead-benchmarks/"&gt;per lead&lt;/a&gt; -- on the same list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Inbound SMS Replies
&lt;/h2&gt;

&lt;p&gt;When a lead texts back, that reply needs to reach an agent fast. The response window for inbound texts is measured in minutes, not hours. A lead who texts "what time works?" at 2 PM and gets a reply at 5 PM has already moved on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Routing Replies to Agents
&lt;/h3&gt;

&lt;p&gt;Build an inbound SMS processor that checks for opt-out keywords first, then routes real replies to the agent who handled the original call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;sms_inbound.py - Process inbound SMS replies&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mysql.connector&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;DB_CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cron&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VICI_DB_PASS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;database&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vicidial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;OPT_OUT_KEYWORDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unsubscribe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cancel&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_inbound_messages&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fetch new inbound messages from provider and process them.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Pull unprocessed inbound messages from webhook table
&lt;/span&gt;    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;DB_CONFIG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dictionary&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        SELECT id, from_number, message_text, received_at
        FROM sms_inbound_queue WHERE processed = 0
        ORDER BY received_at ASC
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;phone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from_number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;text_lower&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# Check for opt-out
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text_lower&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;OPT_OUT_KEYWORDS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
                INSERT IGNORE INTO sms_dnc_list
                (phone_number, opted_out_at, opt_out_keyword) VALUES (%s, NOW(), %s)
            &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text_lower&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="c1"&gt;# Send confirmation
&lt;/span&gt;            &lt;span class="nf"&gt;send_sms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You have been unsubscribed. No further messages will be sent.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Find the original agent and create a callback
&lt;/span&gt;            &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
                SELECT v.user, v.lead_id, v.campaign_id
                FROM vicidial_log v
                JOIN sms_log s ON v.lead_id = s.lead_id
                WHERE s.phone_number = %s
                ORDER BY v.call_date DESC LIMIT 1
            &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
            &lt;span class="n"&gt;original&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Insert callback for the original agent
&lt;/span&gt;                &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
                    INSERT INTO vicidial_callbacks
                    (lead_id, list_id, campaign_id, status, user,
                     recipient, callback_time, comments)
                    SELECT lead_id, list_id, %s, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;LIVE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, %s,
                           &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;USERONLY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, NOW(), %s
                    FROM vicidial_list WHERE lead_id = %s
                &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;campaign_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                      &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SMS reply: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message_text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lead_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

        &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UPDATE sms_inbound_queue SET processed = 1 WHERE id = %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],))&lt;/span&gt;

    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a VICIdial callback assigned to the original agent when a lead replies by text. The agent sees the callback in their queue with the SMS reply in the comments field, giving them context before they dial back.&lt;/p&gt;

&lt;h3&gt;
  
  
  SMS Reply Timing
&lt;/h3&gt;

&lt;p&gt;Track your reply-to-callback time. The lead texted you -- they are engaged right now. Every minute of delay reduces the probability of a conversion:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reply Time&lt;/th&gt;
&lt;th&gt;Conversion Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Under 5 min&lt;/td&gt;
&lt;td&gt;Peak engagement, highest close rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5-15 min&lt;/td&gt;
&lt;td&gt;Good, slight drop-off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15-60 min&lt;/td&gt;
&lt;td&gt;Noticeable decline in engagement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1-4 hours&lt;/td&gt;
&lt;td&gt;Lead has moved on, reconnection harder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4+ hours&lt;/td&gt;
&lt;td&gt;Basically a cold re-contact&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your agents are too busy to handle callbacks quickly, create a dedicated "SMS response" campaign in VICIdial with higher priority routing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring SMS ROI
&lt;/h2&gt;

&lt;p&gt;Track SMS performance separately from voice to understand the incremental value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disposition&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;trigger_dispo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;leads_texted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'delivered'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;delivered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response_text&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response_text&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
        &lt;span class="n"&gt;GREATEST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;response_rate_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SALE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'XFER'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;conversions_after_sms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SALE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'XFER'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
        &lt;span class="n"&gt;GREATEST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sms_assisted_conv_pct&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sms_log&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sent_at&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sent_at&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SALE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'XFER'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;sms_templates&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disposition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disposition&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sent_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disposition&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sms_assisted_conv_pct&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query shows which disposition triggers produce the most SMS-assisted conversions. If your "NA" (no answer) texts lead to 6% conversions within 7 days but your "AM" (answering machine) texts produce only 1%, allocate more of your messaging budget to the NA workflow and rethink the AM template.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Build First
&lt;/h2&gt;

&lt;p&gt;If you are starting from zero SMS integration, here is the priority order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;10DLC registration&lt;/strong&gt; -- start today, it takes 1-3 weeks. Do not wait.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consent audit&lt;/strong&gt; -- verify you have documented one-to-one consent for every contact you plan to text. If you don't, you can not text them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single disposition trigger&lt;/strong&gt; -- start with "NA" (no answer) only. Send one text within 60 seconds of a missed call. This is the highest-ROI single addition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opt-out handling&lt;/strong&gt; -- make sure STOP works before you send a single message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-touch cadence&lt;/strong&gt; -- once the basic trigger works, expand to the 7-day cadence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reporting and optimization&lt;/strong&gt; -- measure delivery, response, and conversion rates. A/B test templates.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The teams at &lt;a href="https://vicistack.com/" rel="noopener noreferrer"&gt;ViciStack&lt;/a&gt; wire SMS into dialer workflows as part of every &lt;a href="https://dev.to/blog/contact-rate-optimization-guide/"&gt;contact rate optimization&lt;/a&gt; engagement because the combination of voice and text consistently outperforms either channel alone by 40-80%. If you want disposition-triggered SMS running on your VICIdial instance without spending months on the integration, &lt;a href="https://vicistack.com/contact/" rel="noopener noreferrer"&gt;we build that&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>voip</category>
      <category>asterisk</category>
      <category>sysadmin</category>
      <category>devops</category>
    </item>
    <item>
      <title>Call Center Agent Burnout Prevention: Scheduling Fixes, Retention Tactics, and the Occupancy Math Nobody Talks About</title>
      <dc:creator>Jason Shouldice</dc:creator>
      <pubDate>Fri, 27 Mar 2026 22:55:51 +0000</pubDate>
      <link>https://dev.to/gamlin/call-center-agent-burnout-prevention-scheduling-fixes-retention-tactics-and-the-occupancy-math-4j7e</link>
      <guid>https://dev.to/gamlin/call-center-agent-burnout-prevention-scheduling-fixes-retention-tactics-and-the-occupancy-math-4j7e</guid>
      <description>&lt;p&gt;&lt;strong&gt;Last updated: March 2026 | Reading time: ~25 minutes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The call center industry loses 40-45% of its agents every year. Not to competitors. Not to career changes. To burnout.&lt;/p&gt;

&lt;p&gt;Gallup research shows burned-out employees are 63% more likely to take sick days, 50% less likely to discuss performance goals with their managers, and show 13% less confidence in their work. In a call center, that translates directly to longer handle times, lower conversion rates, and the quiet disengagement that precedes a resignation letter.&lt;/p&gt;

&lt;p&gt;The cost is staggering. Replacing a single call center agent runs $10,000 to $21,000 when you factor in recruitment, training, lost productivity during ramp-up, and the customer impact of putting a green agent on the phones. For a 50-agent operation with 40% turnover, that is $200K-$420K per year burned on replacement costs alone.&lt;/p&gt;

&lt;p&gt;And here is the part that really stings: the agents who burn out first are usually the good ones. Your top performers take the hardest calls, handle the most escalations, and carry the team when the queue is deep. When they leave, they take institutional knowledge, customer relationships, and team morale with them.&lt;/p&gt;

&lt;p&gt;The existing post on &lt;a href="https://dev.to/blog/call-center-agent-burnout/"&gt;call center burnout&lt;/a&gt; covers the financial cost and early warning signs. This guide goes deeper into the operational fixes -- the scheduling changes, occupancy math, and VICIdial configuration that actually prevent burnout instead of just measuring it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Occupancy Rate Problem
&lt;/h2&gt;

&lt;p&gt;Occupancy rate is the percentage of an agent's logged-in time spent handling calls (talk time plus after-call work) versus waiting for calls. It is the single most important metric for burnout prevention, and most call center managers either ignore it or push it in the wrong direction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why High Occupancy Kills Your Agents
&lt;/h3&gt;

&lt;p&gt;The math seems simple: if agents are on calls 95% of the time, you are getting maximum value from your payroll. Every minute an agent spends in READY status waiting for a call is a minute you are paying for without production.&lt;/p&gt;

&lt;p&gt;That logic is correct for exactly one day. By day three, agents running at 95% occupancy have zero recovery time between calls. Their handle times start creeping up (fatigued agents take longer to process calls). Their conversion rates drop. Their sick day usage spikes. Within 4-6 weeks, they are either on a performance improvement plan or updating their resume.&lt;/p&gt;

&lt;p&gt;The data is clear: occupancy rates above 90% correlate with increased sick leave, longer queues (counterintuitively), falling customer satisfaction, and higher turnover.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Optimal Occupancy Range
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Occupancy&lt;/th&gt;
&lt;th&gt;Agent Experience&lt;/th&gt;
&lt;th&gt;Business Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;65-70%&lt;/td&gt;
&lt;td&gt;Comfortable, lots of breathing room&lt;/td&gt;
&lt;td&gt;Overstaffed -- payroll waste&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;75-80%&lt;/td&gt;
&lt;td&gt;Sustainable, good work-life balance&lt;/td&gt;
&lt;td&gt;Sweet spot for most operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;80-85%&lt;/td&gt;
&lt;td&gt;Busy but manageable&lt;/td&gt;
&lt;td&gt;Acceptable for experienced teams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;85-90%&lt;/td&gt;
&lt;td&gt;Stressful, limited recovery time&lt;/td&gt;
&lt;td&gt;Burnout risk increases sharply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90-95%&lt;/td&gt;
&lt;td&gt;Back-to-back calls, no breaks&lt;/td&gt;
&lt;td&gt;High burnout, handle time inflation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;95%+&lt;/td&gt;
&lt;td&gt;Unsustainable&lt;/td&gt;
&lt;td&gt;Agents will quit within weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Target 75-85% for inbound operations and 70-80% for outbound. Outbound agents need more recovery time because they are dealing with rejection on every other call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measuring Occupancy in VICIdial
&lt;/h3&gt;

&lt;p&gt;Pull your current occupancy rates per agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;work_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'INCALL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'DISPO'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;pause_sec&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;handle_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'READY'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;pause_sec&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;idle_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'PAUSED'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;pause_sec&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pause_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pause_sec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'INCALL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'DISPO'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;pause_sec&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
        &lt;span class="n"&gt;GREATEST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'INCALL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'DISPO'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'READY'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;pause_sec&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;occupancy_pct&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_agent_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;event_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="n"&gt;total_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;14400&lt;/span&gt;  &lt;span class="o"&gt;#&lt;/span&gt; &lt;span class="k"&gt;at&lt;/span&gt; &lt;span class="n"&gt;least&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="n"&gt;logged&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;occupancy_pct&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agents at the top of this list -- the ones above 90% -- are your highest burnout risk. They are also probably your most productive agents, which is why nobody has flagged it as a problem yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixing Occupancy Through Dial Level Adjustment
&lt;/h3&gt;

&lt;p&gt;For outbound campaigns, occupancy is directly tied to your &lt;a href="https://dev.to/blog/vicidial-auto-dial-level-tuning/"&gt;dial level settings&lt;/a&gt;. A high dial level means more connected calls per agent, which means higher occupancy.&lt;/p&gt;

&lt;p&gt;If occupancy is above 85%, reduce the dial level ceiling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Campaign &amp;gt; Detail &amp;gt; Auto Dial Level: reduce from 5.0 to 3.5
Campaign &amp;gt; Detail &amp;gt; Adaptive Intensity: set to -1 (conservative bias)
Campaign &amp;gt; Detail &amp;gt; Available Only Ratio Tally: Y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For inbound queues, the fix is staffing. If occupancy is above 85%, you need more agents in the queue -- period. No configuration change fixes understaffing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scheduling Optimization for Burnout Prevention
&lt;/h2&gt;

&lt;p&gt;Most burnout conversations focus on workload and culture. They skip the structural problem: the schedule itself creates burnout conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Back-to-Back Block Problem
&lt;/h3&gt;

&lt;p&gt;A standard 8-hour shift with a 30-minute lunch and two 15-minute breaks creates three blocks of continuous call time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Block 1: 8:00 AM - 10:00 AM (2 hours straight)&lt;/li&gt;
&lt;li&gt;Break: 10:00 - 10:15&lt;/li&gt;
&lt;li&gt;Block 2: 10:15 AM - 12:30 PM (2.25 hours straight)&lt;/li&gt;
&lt;li&gt;Lunch: 12:30 - 1:00&lt;/li&gt;
&lt;li&gt;Block 3: 1:00 PM - 3:00 PM (2 hours straight)&lt;/li&gt;
&lt;li&gt;Break: 3:00 - 3:15&lt;/li&gt;
&lt;li&gt;Block 4: 3:15 PM - 5:00 PM (1.75 hours straight)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is two-hour stretches of continuous call handling. For an agent taking calls every 3-4 minutes, that is 30-40 consecutive customer interactions before they get a break. Mental fatigue sets in around call 15-20. Quality drops. Patience drops. Handle times increase.&lt;/p&gt;

&lt;h3&gt;
  
  
  Micro-Break Scheduling
&lt;/h3&gt;

&lt;p&gt;Insert 5-10 minute micro-breaks between the standard breaks. The schedule becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Block 1: 8:00 - 9:15 (1.25 hours)&lt;/li&gt;
&lt;li&gt;Micro-break: 9:15 - 9:25 (10 min)&lt;/li&gt;
&lt;li&gt;Block 2: 9:25 - 10:30 (1.05 hours)&lt;/li&gt;
&lt;li&gt;Standard break: 10:30 - 10:45&lt;/li&gt;
&lt;li&gt;Block 3: 10:45 - 12:00 (1.25 hours)&lt;/li&gt;
&lt;li&gt;Lunch: 12:00 - 12:30&lt;/li&gt;
&lt;li&gt;Block 4: 12:30 - 1:45 (1.25 hours)&lt;/li&gt;
&lt;li&gt;Micro-break: 1:45 - 1:55 (10 min)&lt;/li&gt;
&lt;li&gt;Block 5: 1:55 - 3:00 (1.05 hours)&lt;/li&gt;
&lt;li&gt;Standard break: 3:00 - 3:15&lt;/li&gt;
&lt;li&gt;Block 6: 3:15 - 4:30 (1.25 hours)&lt;/li&gt;
&lt;li&gt;Micro-break: 4:30 - 4:35 (5 min)&lt;/li&gt;
&lt;li&gt;Block 7: 4:35 - 5:00 (25 min)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Maximum continuous call time drops from 2.25 hours to 1.25 hours. You lose about 25 minutes of call time per agent per day. But the research on flexible scheduling shows it reduces burnout by 20% and improves work-life balance satisfaction by 62%. The quality improvement and reduced turnover more than compensate for the lost minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing Micro-Breaks in VICIdial
&lt;/h3&gt;

&lt;p&gt;VICIdial does not have built-in micro-break scheduling, but you can implement it through &lt;a href="https://dev.to/blog/vicidial-pause-codes-accountability/"&gt;pause codes and&lt;/a&gt; supervisor workflows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Admin &amp;gt; Pause Codes &amp;gt; Add Pause Code
    Pause Code:         MICRO
    Pause Code Name:    Scheduled Micro-Break
    Billable:           PAUSE
    Pause Code Type:    MGMT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a supervisory script that moves agents to the MICRO pause code on schedule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# micro-break-scheduler.sh - Rotate agents through micro-breaks&lt;/span&gt;
&lt;span class="c"&gt;# Run every 75 minutes during operating hours&lt;/span&gt;

&lt;span class="nv"&gt;CAMPAIGN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"YOUR_CAMPAIGN"&lt;/span&gt;
&lt;span class="nv"&gt;BREAK_DURATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;600  &lt;span class="c"&gt;# 10 minutes in seconds&lt;/span&gt;

&lt;span class="c"&gt;# Get list of agents currently in INCALL or READY&lt;/span&gt;
&lt;span class="nv"&gt;AGENTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; cron &lt;span class="nt"&gt;-pPASS&lt;/span&gt; vicidial &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
    SELECT user FROM vicidial_live_agents
    WHERE campaign_id='&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CAMPAIGN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'
    AND status IN ('READY','WAITING')
    ORDER BY last_state_change ASC
    LIMIT 5"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;  &lt;span class="c"&gt;# rotate 5 agents at a time&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;agent &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$AGENTS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: Sending &lt;/span&gt;&lt;span class="nv"&gt;$agent&lt;/span&gt;&lt;span class="s2"&gt; to micro-break"&lt;/span&gt;
    &lt;span class="c"&gt;# Use the non-agent API to pause the agent&lt;/span&gt;
    curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://your-vici-server/vicidial/non_agent_api.php?&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
source=api&amp;amp;user=apiuser&amp;amp;pass=apipass&amp;amp;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
function=pause_agent&amp;amp;agent_user=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;agent&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;value=MICRO"&lt;/span&gt;

    &lt;span class="c"&gt;# Schedule unpause after break duration&lt;/span&gt;
    &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="nv"&gt;$BREAK_DURATION&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://your-vici-server/vicidial/non_agent_api.php?&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
source=api&amp;amp;user=apiuser&amp;amp;pass=apipass&amp;amp;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
function=pause_agent&amp;amp;agent_user=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;agent&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;value=RESUME"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &amp;amp;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Post-Escalation Cooldown
&lt;/h3&gt;

&lt;p&gt;Hostile customer calls are the single biggest burnout accelerator. An agent who just got screamed at for 10 minutes should not be immediately routed another call. They need 3-5 minutes to decompress.&lt;/p&gt;

&lt;p&gt;Implement a post-escalation cooldown using disposition-triggered pauses. When an agent dispositions a call with an escalation code (ESCL, HOSTILE, SUPVR), automatically pause them for a cooldown period:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Campaign &amp;gt; Detail &amp;gt; After Call Work: enable
Campaign &amp;gt; Dispo Status &amp;gt; HOSTILE: set Required Wait = 300 (5 minutes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, train supervisors to watch the real-time report for escalation dispositions and manually grant cooldown pauses. It is less automated but builds the supervisor-agent relationship that prevents burnout long-term.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restructuring Performance Metrics
&lt;/h2&gt;

&lt;p&gt;Volume-based KPIs drive burnout. When agents are measured primarily on calls per hour, they are incentivized to rush through interactions, skip proper follow-up, and avoid taking breaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Volume Trap
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Burnout Risk&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Calls per hour&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Rewards speed over quality, punishes breaks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Talk time targets&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Discourages proper needs assessment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After-call work limits&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Rushes disposition and note-taking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adherence only&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Treats humans as schedule robots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conversion rate&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Rewards outcomes, not activity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quality score&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Rewards skill development&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customer satisfaction&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Aligns agent behavior with business goals&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Building a Balanced Scorecard
&lt;/h3&gt;

&lt;p&gt;Replace single-metric evaluations with a weighted scorecard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent Performance Score = (
    Conversion Rate × 0.30 +
    Quality Score × 0.25 +
    Customer Satisfaction × 0.20 +
    Adherence × 0.15 +
    Volume Index × 0.10
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Volume index is calls handled divided by the team average -- it accounts for productivity without making it the primary measure. An agent who converts at 2x the team rate with 80% of the team's call volume is more valuable than an agent who dials faster but converts at half the rate.&lt;/p&gt;

&lt;p&gt;Track this in VICIdial by combining data from multiple reports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SALE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'XFER'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;conversions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SALE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'XFER'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
        &lt;span class="n"&gt;GREATEST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;conversion_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length_in_sec&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_talk_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length_in_sec&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length_in_sec&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_connected_talk&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="n"&gt;total_calls&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;conversion_pct&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agents who rank high on conversion rate but average on call volume should be recognized, not penalized for "low activity."&lt;/p&gt;

&lt;h2&gt;
  
  
  Early Warning Detection System
&lt;/h2&gt;

&lt;p&gt;Burnout does not happen overnight. It builds over 2-4 weeks before the agent reaches the breaking point. If you track the right signals, you can intervene before you lose them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Five Early Warning Signals
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Handle Time Drift&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An agent whose average handle time increases by 15-20% over 2-3 weeks is slowing down from fatigue. Pull weekly AHT trends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;YEARWEEK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;week_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;length_in_sec&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_handle_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;call_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="n"&gt;WEEK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;length_in_sec&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;YEARWEEK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;week_num&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an agent's AHT went from 180 seconds to 220 seconds over 3 weeks, that is a 22% increase. That is burnout signal number one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Adherence Erosion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An agent who was consistently at 95% adherence and drops to 88% over 2 weeks is checking out. They are arriving late, taking longer breaks, or logging off early. Their body is still showing up but their engagement is gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Pause Code Inflation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Watch for increasing use of non-productive pause codes. An agent who goes from 2 "personal" pauses per day to 5 is avoiding the phone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;work_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;sub_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'PERSONAL'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;personal_pauses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;sub_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'RESTROOM'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;restroom_pauses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;sub_status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PERSONAL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'RESTROOM'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;pause_sec&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;unproductive_minutes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_agent_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;event_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'PAUSED'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;work_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Consecutive Under-Performance Days&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Track days where an agent's conversion rate falls below 70% of their historical average. One bad day is normal. Five consecutive bad days is a pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Sick Day Clustering&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Gallup's data shows burned-out employees are 63% more likely to take sick days. Watch for patterns: Monday absences, Friday absences, and increasing frequency of single-day callouts are classic burnout indicators.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building an Automated Alert System
&lt;/h3&gt;

&lt;p&gt;Combine these signals into a burnout risk score and alert managers before it is too late:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;burnout_alert.py - Weekly burnout risk scoring for agents&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_burnout_risk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Score burnout risk 0-100 based on trailing indicators.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="c1"&gt;# Handle time drift (compare last 2 weeks vs prior 6 weeks)
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;agent_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aht_change_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;agent_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aht_change_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;

    &lt;span class="c1"&gt;# Adherence drop
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;agent_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adherence_drop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;agent_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adherence_drop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;

    &lt;span class="c1"&gt;# Pause code inflation
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;agent_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;personal_pause_increase_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;agent_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;personal_pause_increase_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

    &lt;span class="c1"&gt;# Consecutive underperformance days
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;agent_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;consecutive_bad_days&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;agent_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;consecutive_bad_days&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

    &lt;span class="c1"&gt;# Sick day frequency
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;agent_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sick_days_last_30&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;risk&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Alert thresholds
# 0-25:  Normal - no action needed
# 26-50: Elevated - supervisor should check in this week
# 51-75: High - schedule one-on-one within 48 hours
# 76+:   Critical - immediate intervention required
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this weekly on Monday mornings. Managers get a ranked list of agents by burnout risk, with specific signals highlighted. The agent at 72 risk with rising AHT and dropping adherence needs a conversation this week -- not next month at their scheduled review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retention Strategies That Actually Work
&lt;/h2&gt;

&lt;p&gt;Prevention is better than intervention. Here are the retention tactics with documented impact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flexible Scheduling
&lt;/h3&gt;

&lt;p&gt;Remote and hybrid call centers see 28-32% turnover versus 40-45% for traditional on-site operations. That is a 10+ percentage point reduction just from schedule flexibility.&lt;/p&gt;

&lt;p&gt;Even without remote work, offering shift choice (morning/afternoon/evening preferences), compressed work weeks (4x10 instead of 5x8), and shift swaps reduces the "trapped" feeling that drives turnover.&lt;/p&gt;

&lt;h3&gt;
  
  
  One-on-One Meetings
&lt;/h3&gt;

&lt;p&gt;Employees receiving twice the number of one-on-one meetings are 67% less likely to be disengaged. Bi-weekly 15-minute one-on-ones with direct supervisors are the single highest-ROI retention activity you can implement.&lt;/p&gt;

&lt;p&gt;Structure the one-on-one around three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What is working well for you right now?&lt;/li&gt;
&lt;li&gt;What is your biggest frustration this week?&lt;/li&gt;
&lt;li&gt;What do you need from me that you are not getting?&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Career Pathing
&lt;/h3&gt;

&lt;p&gt;94% of employees say they would stay longer at a company that invests in their learning and development. Define explicit progression paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent (Level 1) → Senior Agent (Level 2) → Team Lead
                                         → QA Analyst
                                         → Training Specialist
                                         → WFM Analyst

Timeline: 6-12 months per level
Requirements: documented performance metrics + skills assessment
Pay increase: 8-15% per level
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The path has to be real. If an agent hits every metric for 12 months and there is no promotion available, you have not built a career path -- you have built false hope, and they will leave faster than if you had never mentioned it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recognition Programs
&lt;/h3&gt;

&lt;p&gt;Small, frequent recognition beats annual bonuses. A $25 gift card for "best save of the week" costs nothing but creates the kind of positive feedback loop that keeps agents engaged.&lt;/p&gt;

&lt;p&gt;Track wins and recognition in VICIdial by tagging exceptional dispositions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Admin &amp;gt; Disposition Statuses &amp;gt; Add Status
    Status:      SAVEWIN
    Status Name: Customer Save - Recognize Agent
    Selectable:  Y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run a weekly report on SAVEWIN dispositions to identify agents who went above and beyond.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost-Benefit Math
&lt;/h2&gt;

&lt;p&gt;Here is the business case for preventing burnout, calculated for a 50-agent operation with 40% turnover:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;Before Prevention&lt;/th&gt;
&lt;th&gt;After Prevention&lt;/th&gt;
&lt;th&gt;Savings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Annual turnover rate&lt;/td&gt;
&lt;td&gt;40% (20 agents)&lt;/td&gt;
&lt;td&gt;25% (13 agents)&lt;/td&gt;
&lt;td&gt;7 fewer replacements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per replacement&lt;/td&gt;
&lt;td&gt;$15,000&lt;/td&gt;
&lt;td&gt;$15,000&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Annual replacement cost&lt;/td&gt;
&lt;td&gt;$300,000&lt;/td&gt;
&lt;td&gt;$195,000&lt;/td&gt;
&lt;td&gt;$105,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Productivity loss (ramp-up)&lt;/td&gt;
&lt;td&gt;$160,000&lt;/td&gt;
&lt;td&gt;$104,000&lt;/td&gt;
&lt;td&gt;$56,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Micro-break time cost&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$48,000&lt;/td&gt;
&lt;td&gt;-$48,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One-on-one supervisor time&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$12,000&lt;/td&gt;
&lt;td&gt;-$12,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Net annual savings&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$101,000&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;$101,000 in net savings for a 50-agent operation, and that does not count the quality improvements from experienced agents staying longer or the customer satisfaction gains from lower AHT variability.&lt;/p&gt;

&lt;p&gt;The teams at &lt;a href="https://vicistack.com/" rel="noopener noreferrer"&gt;ViciStack&lt;/a&gt; build burnout prevention into every &lt;a href="https://dev.to/blog/contact-rate-optimization/"&gt;call center optimization&lt;/a&gt; engagement because you can not increase conversions by 50% if your best agents keep quitting. Dialer optimization and agent retention are the same project. If you are losing good people and want to fix the root cause instead of patching symptoms, &lt;a href="https://vicistack.com/contact/" rel="noopener noreferrer"&gt;that is what we do&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>voip</category>
      <category>asterisk</category>
      <category>sysadmin</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
