ViciDial Lead Recycling & List Management Best Practices
Master advanced lead recycling strategies, implement intelligent list management workflows, and optimize your ViciDial contact center performance with production-tested configurations and real-world examples.
Prerequisites
Before implementing the strategies in this tutorial, ensure you have:
- ViciDial 2.14+ installed and running (2.14.1 or later recommended)
- Administrator access to the ViciDial web interface at
/vicidial/admin.php - SSH access to the ViciDial server with sudo privileges
- MySQL/MariaDB command-line access to the
asteriskdatabase - At least one active campaign configured
- Basic understanding of ViciDial list structure and lead statuses
- Backup of your database before implementing changes
Verify your installation:
mysql -u root -p asterisk -e "SELECT VERSION();"
asterisk -v
grep "vicidial" /etc/asterisk/extensions-vicidial.conf | head -5
Understanding Lead Lifecycle in ViciDial
Lead recycling is the process of requeuing contacts after specific outcomes, allowing multiple attempts with different agents, strategies, or time intervals. Understanding the lead lifecycle is foundational to effective recycling.
Lead Status Codes and Their Meanings
ViciDial uses standardized status codes stored in the vicidial_log table. Key statuses relevant to recycling:
| Status Code | Meaning | Recyclable | Default Seconds |
|---|---|---|---|
| NEW | Untouched lead | Yes | 0 |
| CALL | Active call | No | N/A |
| CALLACK | Callback scheduled | No | User-defined |
| BUSY | Busy signal | Yes | 3600 |
| NOANSWER | No answer | Yes | 1800 |
| NOTCALLABLE | Invalid number | No | 0 |
| BADNUMBER | Bad format | No | 0 |
| DNC | Do Not Call | No | 0 |
| XFER | Transfer attempted | Yes | 900 |
| SALE | Successful | No | 0 |
| DEAD | Duplicate/Dead | No | 0 |
The Recycling Decision Tree
Lead Called
├─ Answer
│ ├─ Sale → No recycle
│ ├─ Not Interested → Recycle (optional)
│ └─ Wrong Number → No recycle (flag DNC)
├─ No Answer
│ ├─ Retry ≤ 3 → Recycle NOW
│ ├─ Retry 4-5 → Recycle in 24h
│ └─ Retry > 5 → Archive
└─ Busy
├─ Retry ≤ 2 → Recycle in 30 min
└─ Retry > 2 → Recycle in 2h
Database Schema for Recycling Management
Key Tables
Understanding the database structure is essential for custom recycling logic.
-- View current lead status and recycle information
SELECT
vl.lead_id,
vl.phone_number,
vl.status,
vl.call_date,
vl.length_in_sec,
vl.user,
vl.campaign_id,
COUNT(*) as attempt_count
FROM vicidial_log vl
WHERE vl.campaign_id = 'TESTCAMP'
AND vl.phone_number = '5551234567'
GROUP BY vl.lead_id
ORDER BY vl.call_date DESC;
Core Recycling Tables
vicidial_list — Contains lead master records:
DESC vicidial_list;
Key fields for recycling:
-
lead_id— Unique identifier -
phone_number— Contact number -
status— Current lead status -
list_id— Associated campaign list -
gmt_offset_now— Timezone for callback scheduling -
created_date— Lead entry date -
last_local_call_time— Last contact attempt timestamp
vicidial_log — Call attempt history:
DESC vicidial_log;
Key fields:
-
lead_id— Links to vicidial_list -
call_date— Attempt timestamp -
status— Outcome status -
length_in_sec— Duration -
user— Agent identifier -
campaign_id— Campaign code -
phone_number— Dialed number -
call_type— INBOUND/OUTBOUND
vicidial_campaign — Campaign configuration:
DESC vicidial_campaign;
Critical recycling fields:
-
campaign_id— Unique identifier -
lead_recycle_method— Recycling strategy -
reuse_trunk_level— Trunk reuse behavior -
caller_id_method— Caller ID assignment
Configuring Lead Recycling Strategies
Method 1: Campaign-Level Automatic Recycling
Access the campaign configuration and enable recycling globally:
# Log into ViciDial web interface
# Navigate to: Admin → Campaigns → [Your Campaign]
# Look for these settings:
In the database, verify campaign recycling configuration:
SELECT
campaign_id,
lead_recycle_method,
auto_dial_level,
voicemail_ext,
hopper_level
FROM vicidial_campaign
WHERE campaign_id = 'TESTCAMP';
Update recycling method:
UPDATE vicidial_campaign
SET lead_recycle_method = 1
WHERE campaign_id = 'TESTCAMP';
Recycling methods:
-
0— No recycling -
1— Standard recycling (usesvicidial_closer_logstatus settings) -
2— Advanced recycling (requires custom scripting)
Method 2: Status-Based Recycling Rules
Create granular recycling behavior through status configuration in vicidial_closer_log:
-- View current status recycling rules
SELECT
campaign_id,
status,
recyclable,
rank
FROM vicidial_closer_log
WHERE campaign_id = 'TESTCAMP'
ORDER BY status;
This table determines which statuses trigger recycling. Insert recycling rules:
-- Recycle NOANSWER leads after 30 minutes
INSERT INTO vicidial_closer_log
(campaign_id, status, recyclable, seconds_to_next_call, rank)
VALUES
('TESTCAMP', 'NOANSWER', 'Y', 1800, 10)
ON DUPLICATE KEY UPDATE
recyclable = 'Y',
seconds_to_next_call = 1800;
-- Recycle BUSY after 15 minutes
INSERT INTO vicidial_closer_log
(campaign_id, status, recyclable, seconds_to_next_call, rank)
VALUES
('TESTCAMP', 'BUSY', 'Y', 900, 20)
ON DUPLICATE KEY UPDATE
recyclable = 'Y',
seconds_to_next_call = 900;
-- Do NOT recycle DNCPROSPECT
INSERT INTO vicidial_closer_log
(campaign_id, status, recyclable, seconds_to_next_call, rank)
VALUES
('TESTCAMP', 'DNCPROSPECT', 'N', 0, 5)
ON DUPLICATE KEY UPDATE
recyclable = 'N',
seconds_to_next_call = 0;
Method 3: Custom Recycling with AGI Scripts
For advanced recycling logic, implement AGI (Asterisk Gateway Interface) scripts. Create a custom recycling script:
cat > /usr/share/astguiclient/agi-bin/recycle_custom.agi << 'EOF'
#!/usr/bin/perl
# Custom Lead Recycling AGI Script for ViciDial
# Place in: /usr/share/astguiclient/agi-bin/
use strict;
use warnings;
use Asterisk::AGI;
use DBI;
my $agi = new Asterisk::AGI;
my $lead_id = $agi->get_variable("lead_id");
my $campaign_id = $agi->get_variable("campaign_id");
my $status = $agi->get_variable("call_status");
my $attempt_count = $agi->get_variable("attempt_count");
# Connect to database
my $dbh = DBI->connect(
"DBI:mysql:asterisk:localhost",
"asteriskuser",
"DBPASSWORD",
{AutoCommit => 1}
) or die "Cannot connect: $DBI::errstr";
# Recycling logic
my $recycle = 0;
my $recycle_seconds = 0;
if ($status eq 'NOANSWER') {
if ($attempt_count < 3) {
$recycle = 1;
$recycle_seconds = 1800; # 30 minutes
} elsif ($attempt_count < 5) {
$recycle = 1;
$recycle_seconds = 86400; # 24 hours
}
} elsif ($status eq 'BUSY') {
if ($attempt_count < 4) {
$recycle = 1;
$recycle_seconds = 900; # 15 minutes
}
} elsif ($status eq 'XFER' && $attempt_count < 2) {
$recycle = 1;
$recycle_seconds = 3600; # 1 hour
}
# Update vicidial_list with recycle status
if ($recycle) {
my $sth = $dbh->prepare(
"UPDATE vicidial_list
SET status = ?,
last_local_call_time = DATE_ADD(NOW(), INTERVAL ? SECOND)
WHERE lead_id = ?"
);
$sth->execute('RECYCLE', $recycle_seconds, $lead_id);
$agi->set_variable("recycled", 1);
}
$dbh->disconnect();
exit(0);
EOF
chmod +x /usr/share/astguiclient/agi-bin/recycle_custom.agi
Call this AGI from your dialplan:
; Add to /etc/asterisk/extensions-vicidial.conf
[vicidial-handler]
exten => s,1,AGI(agi-bin/recycle_custom.agi)
exten => s,n,Hangup()
Implementing Intelligent List Management
List Segmentation Strategy
Separate leads into logical groups for targeted recycling:
-- Create a custom list segmentation view
CREATE OR REPLACE VIEW lead_segments AS
SELECT
vl.lead_id,
vl.phone_number,
vl.list_id,
vl.campaign_id,
CASE
WHEN COUNT(*) <= 1 THEN 'FRESH'
WHEN COUNT(*) BETWEEN 2 AND 3 THEN 'WARM'
WHEN COUNT(*) > 3 THEN 'HOT'
END as segment,
COUNT(*) as attempt_count,
MAX(vlog.call_date) as last_attempt
FROM vicidial_list vl
LEFT JOIN vicidial_log vlog ON vl.lead_id = vlog.lead_id
GROUP BY vl.lead_id;
-- Query segmented leads
SELECT * FROM lead_segments WHERE campaign_id = 'TESTCAMP' AND segment = 'FRESH';
Automatic List Purging and Archive
Prevent list bloat by archiving old, unrecyclable leads:
-- Archive DEAD leads older than 60 days
CREATE PROCEDURE archive_old_leads()
BEGIN
INSERT INTO vicidial_list_archive
SELECT * FROM vicidial_list
WHERE status IN ('DEAD', 'NOTCALLABLE', 'BADNUMBER')
AND DATE_SUB(NOW(), INTERVAL 60 DAY) > created_date;
DELETE FROM vicidial_list
WHERE status IN ('DEAD', 'NOTCALLABLE', 'BADNUMBER')
AND DATE_SUB(NOW(), INTERVAL 60 DAY) > created_date;
END;
-- Execute once daily via cron
CALL archive_old_leads();
Add to crontab:
# Run daily at 2 AM
0 2 * * * mysql -u root -p'PASSWORD' asterisk -e "CALL archive_old_leads;"
Dynamic Callback Scheduling
Implement time-zone aware callbacks for optimal contact:
-- Schedule callbacks during business hours (9 AM - 6 PM local time)
UPDATE vicidial_list vl
SET status = 'CALLBACK',
last_local_call_time = DATE_ADD(
NOW(),
INTERVAL (
(9 - HOUR(CONVERT_TZ(NOW(), 'UTC', 'America/Chicago')))
+ IF(HOUR(CONVERT_TZ(NOW(), 'UTC', 'America/Chicago')) >= 18, 24, 0)
) HOUR
)
WHERE lead_id IN (
SELECT lead_id FROM vicidial_log
WHERE status = 'CALLBACK'
AND DATE_SUB(NOW(), INTERVAL 24 HOUR) < call_date
)
AND campaign_id = 'TESTCAMP';
Monitoring and Reporting Lead Recycling
Real-Time Recycling Dashboard Query
Monitor active recycling in real-time:
SELECT
COUNT(DISTINCT lead_id) as recyclable_leads,
SUM(CASE WHEN status = 'RECYCLE' THEN 1 ELSE 0 END) as pending_recycle,
SUM(CASE WHEN status = 'CALLBACK' THEN 1 ELSE 0 END) as scheduled_callbacks,
campaign_id,
MAX(last_local_call_time) as next_available
FROM vicidial_list
WHERE campaign_id = 'TESTCAMP'
AND status IN ('RECYCLE', 'CALLBACK', 'BUSY', 'NOANSWER')
GROUP BY campaign_id;
Recycling Effectiveness Metrics
Measure recycling performance:
-- Calculate recycle-to-sale conversion rate
SELECT
vlog.campaign_id,
COUNT(DISTINCT vlog.lead_id) as total_attempts,
COUNT(DISTINCT CASE WHEN vlog.status = 'SALE' THEN vlog.lead_id END) as sales,
ROUND(
(COUNT(DISTINCT CASE WHEN vlog.status = 'SALE' THEN vlog.lead_id END)
/ COUNT(DISTINCT vlog.lead_id) * 100), 2
) as conversion_rate_pct,
AVG(CASE WHEN vlog.status = 'SALE' THEN vlog.length_in_sec ELSE NULL END) as avg_sale_duration
FROM vicidial_log vlog
WHERE vlog.campaign_id = 'TESTCAMP'
AND vlog.call_date >= DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY vlog.campaign_id;
Lead Aging Report
Identify leads requiring urgency or removal:
-- List leads by age and status
SELECT
vl.lead_id,
vl.phone_number,
vl.status,
DATEDIFF(NOW(), vl.created_date) as days_in_system,
COUNT(vlog.lead_id) as total_attempts,
MAX(vlog.call_date) as last_attempt,
DATEDIFF(NOW(), MAX(vlog.call_date)) as days_since_last_attempt
FROM vicidial_list vl
LEFT JOIN vicidial_log vlog ON vl.lead_id = vlog.lead_id
WHERE vl.campaign_id = 'TESTCAMP'
GROUP BY vl.lead_id
HAVING days_in_system > 90
ORDER BY last_attempt DESC;
Configuring Recycling via CLI Commands
Bulk Recycle Operations
Recycle entire batches of leads using command-line operations:
# Recycle all NOANSWER leads from a specific campaign
mysql -u asteriskuser -p'PASSWORD' asterisk << EOF
UPDATE vicidial_list
SET status = 'RECYCLE',
last_local_call_time = DATE_ADD(NOW(), INTERVAL 1800 SECOND)
WHERE campaign_id = 'TESTCAMP'
AND status = 'NOANSWER'
AND DATE_SUB(NOW(), INTERVAL 6 HOUR) > last_local_call_time;
EOF
Check Recycling Queue Status
Verify pending recycled leads:
# Connect to Asterisk CLI and check lead queue
asterisk -rx "agi show" | grep -i recycle
# Monitor ViciDial dialer process
ps aux | grep -E "vici_|astguiclient"
# Check for recycling script errors
tail -50 /var/log/asterisk/messages | grep -i recycle
Force Immediate Recycle
Manually trigger recycle for specific leads:
# SSH to ViciDial server
# Run manual recycle operation
mysql -u asteriskuser -p'PASSWORD' asterisk -e \
"UPDATE vicidial_list
SET status = 'RECYCLE',
last_local_call_time = NOW()
WHERE lead_id = 12345
AND campaign_id = 'TESTCAMP';"
# Verify update
mysql -u asteriskuser -p'PASSWORD' asterisk -e \
"SELECT lead_id, phone_number, status, last_local_call_time
FROM vicidial_list
WHERE lead_id = 12345;"
Advanced: Time-Zone Aware Recycling
Handle multi-zone campaigns properly:
-- Create time-zone aware recycling procedure
DELIMITER $$
CREATE PROCEDURE recycle_by_timezone(
IN p_campaign_id VARCHAR(20)
)
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE v_lead_id INT;
DECLARE v_phone VARCHAR(20);
DECLARE v_gmt_offset INT;
DECLARE v_local_hour INT;
DECLARE cur CURSOR FOR
SELECT lead_id, phone_number, gmt_offset_now
FROM vicidial_list
WHERE campaign_id = p_campaign_id
AND status IN ('NOANSWER', 'BUSY');
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
read_loop: LOOP
FETCH cur INTO v_lead_id, v_phone, v_gmt_offset;
IF done THEN
LEAVE read_loop;
END IF;
-- Calculate local hour for this lead's timezone
SET v_local_hour = HOUR(DATE_ADD(NOW(), INTERVAL v_gmt_offset HOUR));
-- Only recycle during business hours (9 AM - 6 PM)
IF v_local_hour >= 9 AND v_local_hour < 18 THEN
UPDATE vicidial_list
SET status = 'RECYCLE',
last_local_call_time = NOW()
WHERE lead_id = v_lead_id;
END IF;
END LOOP;
CLOSE cur;
END$$
DELIMITER ;
-- Execute
CALL recycle_by_timezone('TESTCAMP');
Best Practices and Optimization
Lead Fatigue Prevention
Avoid over-calling contacts:
-- Set maximum attempts per lead
ALTER TABLE vicidial_list ADD COLUMN max_attempts INT DEFAULT 5;
-- Prevent recycling beyond maximum
UPDATE vicidial_list
SET status = 'MAXED'
WHERE (SELECT COUNT(*) FROM vicidial_log
WHERE lead_id = vicidial_list.lead_id) >= max_attempts
AND campaign_id = 'TESTCAMP';
Quality Assurance
Monitor recycling quality metrics:
#!/bin/bash
# Script: check_recycle_health.sh
CAMPAIGN="TESTCAMP"
THRESHOLD=10 # Alert if recycle queue exceeds 10%
TOTAL_LEADS=$(mysql -N -u asteriskuser -p'PASSWORD' asterisk \
-e "SELECT COUNT(*) FROM vicidial_list WHERE campaign_id='$CAMPAIGN';")
RECYCLE_LEADS=$(mysql -N -u asteriskuser -p'PASSWORD' asterisk \
-e "SELECT COUNT(*) FROM vicidial_list WHERE campaign_id='$CAMPAIGN' AND status='RECYCLE';")
PERCENTAGE=$((RECYCLE_LEADS * 100 / TOTAL_LEADS))
echo "Campaign: $CAMPAIGN"
echo "Total Leads: $TOTAL_LEADS"
echo "Recycled Leads: $RECYCLE_LEADS"
echo "Recycle Queue: $PERCENTAGE%"
if [ $PERCENTAGE -gt $THRESHOLD ]; then
echo "WARNING: Recycle queue exceeds $THRESHOLD% threshold!"
fi
Run via cron:
0 * * * * /usr/local/bin/check_recycle_health.sh | mail -s "Recycle Health Alert" admin@company.com
Database Maintenance
Regular optimization for recycling performance:
#!/bin/bash
# Add to crontab - Run weekly
mysql -u asteriskuser -p'PASSWORD' asterisk << EOF
-- Optimize tables for query performance
OPTIMIZE TABLE vicidial_list;
OPTIMIZE TABLE vicidial_log;
OPTIMIZE TABLE vicidial_closer_log;
-- Rebuild indexes
ANALYZE TABLE vicidial_list;
ANALYZE TABLE vicidial_log;
-- Check for corrupted records
CHECK TABLE vicidial_list;
CHECK TABLE vicidial_log;
EOF
Troubleshooting Lead Recycling Issues
Issue: Leads Not Recycling
Symptom: Status remains unchanged after expected recycle interval.
Diagnosis:
# 1. Check if campaign recycling is enabled
mysql -u asteriskuser -p'PASSWORD' asterisk -e \
"SELECT campaign_id, lead_recycle_method FROM vicidial_campaign WHERE campaign_id='TESTCAMP';"
# Expected output: lead_recycle_method should be 1 or higher
# 2. Verify vicidial_dialer process is running
ps aux | grep vicidial | grep -v grep
# 3. Check for errors in logs
tail -100 /var/log/asterisk/messages | grep -i recycle
grep "ERROR\|recycle\|RECYCLE" /var/log/asterisk/messages | tail -20
Solution:
# Enable recycling in campaign
mysql -u asteriskuser -p'PASSWORD' asterisk -e \
"UPDATE vicidial_campaign SET lead_recycle_method = 1 WHERE campaign_id = 'TESTCAMP';"
# Restart vicidial_dialer service
sudo service vicidial_dialer restart
# Monitor service startup
tail -f /var/log/asterisk/messages | grep vicidial
Issue: Recycled Leads Not Dialing
Symptom: RECYCLE status leads remain in queue but don't get dialed.
Diagnosis:
# Check hopper queue depth
mysql -u asteriskuser -p'PASSWORD' asterisk -e \
"SELECT COUNT(*) as hopper_count FROM vicidial_hopper WHERE campaign_id='TESTCAMP';"
# View next leads to be dialed
mysql -u asteriskuser -p'PASSWORD' asterisk -e \
"SELECT lead_id, phone_number, status, last_local_call_time
FROM vicidial_list
WHERE campaign_id = 'TESTCAMP'
AND status = 'RECYCLE'
ORDER BY last_local_call_time
LIMIT 10;"
# Check if leads meet recycle time requirement
mysql -u asteriskuser -p'PASSWORD' asterisk -e \
"SELECT lead_id, phone_number, status,
TIMESTAMPDIFF(SECOND, NOW(), last_local_call_time) as seconds_until_recycle
FROM vicidial_list
WHERE campaign_id = 'TESTCAMP'
AND status = 'RECYCLE'
LIMIT 5;"
Solution:
# If leads show negative seconds_until_recycle, they're ready
# Check if hopper is full (capacity issue)
mysql -u asteriskuser -p'PASSWORD' asterisk -e \
"SELECT * FROM vicidial_campaign WHERE campaign_id = 'TESTCAMP';" | grep -E "hopper_level|lead_recycle"
# If hopper is too small, update it
mysql -u asteriskuser -p'PASSWORD' asterisk -e \
"UPDATE vicidial_campaign SET hopper_level = 200 WHERE campaign_id = 'TESTCAMP';"
# Force immediate hopper fill
asterisk -rx "vicidial hopper $CAMPAIGN"
Issue: Excessive Recycling / Lead Fatigue
Symptom: Leads dialed repeatedly in short intervals, agents complaining about repetition.
Diagnosis:
# Identify heavily recycled leads
mysql -u asteriskuser -p'PASSWORD' asterisk -e \
"SELECT
vl.lead_id,
vl.phone_number,
COUNT(*) as call_attempts,
MIN(vlog.call_date) as first_call,
MAX(vlog.call_date) as last_call,
TIMESTAMPDIFF(DAY, MIN(vlog.call_date), MAX(vlog.call_date)) as days_span
FROM vicidial_list vl
INNER JOIN vicidial_log vlog ON vl.lead_id = vlog.lead_id
WHERE vl.campaign_id = 'TESTCAMP'
GROUP BY vl.lead_id
HAVING call_attempts > 10
ORDER BY call_attempts DESC
LIMIT 20;"
Solution:
# Implement attempt limits in closer_log
mysql -u asteriskuser -p'PASSWORD' asterisk -e \
"INSERT INTO vicidial_closer_log
(campaign_id, status, recyclable, seconds_to_next_call, rank)
VALUES ('TESTCAMP', 'NOANSWER', 'Y', 3600, 10)
ON DUPLICATE KEY UPDATE seconds_to_next_call = 3600;"
# Archive over-attempted leads
mysql -u asteriskuser -p'PASSWORD' asterisk -e \
"UPDATE vicidial_list vl
SET status = 'ARCHIVE'
WHERE vl.campaign_id = 'TESTCAMP'
AND (SELECT COUNT(*) FROM vicidial_log WHERE lead_id = vl.lead_id) > 8;"
Issue: Recycling Causing Database Lock
Symptom: Database queries slow, connection timeout errors, agents report lag.
Diagnosis:
# Check for long-running queries
mysql -u asteriskuser -p'PASSWORD' asterisk -e "SHOW PROCESSLIST;" | grep -i UPDATE
# Monitor table locks
mysqladmin -u asteriskuser -p'PASSWORD' -i 2 processlist | grep -E "UPDATE|LOCK"
# Check innodb status
mysql -u asteriskuser -p'PASSWORD' asterisk -e "SHOW ENGINE INNODB STATUS\G" | grep -A 20 "LATEST DETECTED DEADLOCK"
Solution:
# Implement batching in recycling procedures
mysql -u asteriskuser -p'PASSWORD' asterisk << EOF
-- Update in smaller chunks to reduce lock time
SET @batch_size = 100;
UPDATE vicidial_list vl
SET status = 'RECYCLE'
WHERE campaign_id = 'TESTCAMP'
AND status = 'NOANSWER'
AND lead_id IN (
SELECT lead_id FROM (
SELECT lead_id FROM vicidial_list vl2
WHERE campaign_id = 'TESTCAMP' AND status = 'NOANSWER'
LIMIT @batch_size
) tmp
);
EOF
# Add index for faster recycling queries
mysql -u asteriskuser -p'PASSWORD' asterisk -e \
"ALTER TABLE vicidial_list ADD INDEX idx_recycle (campaign_id, status, last_local_call_time);"
Summary
ViciDial lead recycling is a sophisticated system requiring careful planning and ongoing optimization. The key takeaways:
Understand Status Flow: Different statuses recycle at different intervals; configure per
vicidial_closer_log.Database-Driven Configuration: Recycling behavior lives in
vicidial_list,vicidial_log, andvicidial_campaigntables—customize via SQL.Time-Zone Awareness: Use
gmt_offset_nowandlast_local_call_timeto avoid off-hours contact.Prevent Lead Fatigue: Set attempt limits, monitor call frequency, and archive over-contacted leads.
Monitor Performance: Track recycle-to-sale conversion rates, queue depths, and recycling effectiveness.
Automate Maintenance: Use cron jobs and stored procedures to archive old leads, optimize tables, and maintain database health.
Test Before Deploying: Always back up your database and test recycling changes in a staging environment first.
Document Custom Logic: If implementing AGI scripts or complex procedures, maintain clear documentation for future maintenance.
Effective lead recycling balances outreach frequency with lead quality, agent productivity, and customer satisfaction. Use the SQL queries and scripts provided here as templates, adjusting parameters for your specific campaign requirements.
Top comments (0)