Call lists for automated calls

Use call lists for automated outbound calls.
View as Markdown

Voximplant’s Call Lists feature allows you to build even the most intricate scenarios with call campaigns in a matter of minutes. You can create a CSV table containing a call list that can be processed by a VoxEngine scenario.

In this article, we present a scenario where we call through a CSV table containing a customer list and provide each customer with their appointment time.

Prerequisites

  1. Create an application.
  2. Create a scenario.
  3. Create a routing rule and attach it to the scenario.
  4. Prepare a call list CSV table with phone numbers and additional information.

CSV table setup

CSV table for call lists contains phone numbers to be called and any additional information to be passed into a VoxEngine scenario. Here’s an example of a call list CSV table format:

first_name;last_name;phone_number;appointment_date
Jon;Snow;+16501234567;November 15th
Daenerys;Targaryen;+16501234568;November 16th

The CSV file contains the names of the streets, phone numbers, and appointment dates. The first string describes the parameter names.

If needed, you can add time intervals when the call list processing starts and ends every day, UTC+0 24-h format: HH:mm:ss.

Jon;Snow;+16501234567;November 15th;17:00:00;22:00:00
Daenerys;Targaryen;+16501234568;November 16th;10:00:00;15:00:00

Task priorities

You can specify priorities for the call list entries.

Changing priorities work only for incomplete tasks. If the priority is set, first a call list processes the prioritized tasks, then the task according to the call list strategy (first calling attempts or repeating calling attempts are prioritized). Tasks with the same priority are sorted by IDs.

To set the priorities, add the task_priority column to the call list CSV which should contain a numeric value of the record’s priority. 0 is the highest priority.

If a priority is not set for a specific record, the call list engine defaults to 50. If the field contains a non-numeric value, the following error returns: The 'task_priority' parameter is invalid.

Scenario setup

In the scenario, require the CallList module and create variables for all the fields from the CSV table. The scenario processes each table row at a time. In this example, we use the AI module to detect voicemail. Then, we use the speech synthesis feature to announce the appointment time.

Please take a look at the scenario example to understand how it works.

Call list example scenario
require(Modules.CallList); // Enable CallList module
require(Modules.AI);
// The storage for the scenario data
const storage = {
callerId: '+1234567890', // rented or verified phone number
playbackCounter: 0
};
const handleCallConnected = (e) => {
setTimeout(() => {
e.call.say(
`Hello ${storage.firstName}! Your appointment scheduled for ${storage.appointmentDate}`,
{ voice: VoiceList.Amazon.en_US_Joanna }
);
}, 500);
e.call.addEventListener(CallEvents.PlaybackFinished, handlePlaybackFinished);
};
const handleCallDisconnected = async (e) => {
try {
// Tell CallList processor about successful call result
await CallList.reportResultAsync({
result: true,
duration: e.duration
});
VoxEngine.terminate();
} catch (error) {
Logger.write(`'CallList.reportResultAsync' error:`);
Logger.write(error);
}
};
const handleCallFailed = async (e) => {
try {
/**
* Depending on the request options it either try to launch the scenario again after some time
* or write the result (failed call) into result_data column of the CSV file with results
*/
await CallList.reportErrorAsync({
result: false,
msg: 'Failed',
code: e.code
});
VoxEngine.terminate();
} catch (error) {
Logger.write(`'CallListAsync.reportError' error:`);
Logger.write(error);
}
};
const handlePlaybackFinished = (e) => {
e.call.removeEventListener(CallEvents.PlaybackFinished, handlePlaybackFinished);
storage.playbackCounter++;
// If the message was played 4 times - hangup
if (storage.playbackCounter === 4) {
e.call.hangup();
} else {
// Play one more time
setTimeout(() => {
e.call.say(`Your appointment scheduled for ${storage.appointmentDate}`, {
voice: VoiceList.Amazon.en_US_Joanna
});
e.call.addEventListener(CallEvents.PlaybackFinished, handlePlaybackFinished);
}, 2000);
}
};
const handleDetectionComplete = async (result) => {
Logger.write(`Machine answer detection is complete.`);
if (result.resultClass === AMD.ResultClass.VOICEMAIL) {
Logger.write(`Voicemail detected with a ${result.confidence}% confidence.`);
try {
await CallList.reportErrorAsync('Voicemail');
VoxEngine.terminate();
} catch (error) {
Logger.write(`'CallList.reportErrorAsync' error:`);
Logger.write(error);
}
} else {
Logger.write('Voicemail not detected.');
}
};
const handleDetectionError = (error) => {
Logger.write(`Detection failed with an error:`);
Logger.write(error);
};
VoxEngine.addEventListener(AppEvents.Started, () => {
const customData = VoxEngine.customData(); // data from CSV string in JSON format
const { first_name, last_name, phone_number, appointment_date } = JSON.parse(customData);
storage.firstName = first_name;
storage.lastName = last_name;
storage.phoneNumber = phone_number;
storage.appointmentDate = appointment_date;
Logger.write(`Calling ${storage.firstName} ${storage.lastName} on ${storage.phoneNumber}`);
// Create an AMD instance
const amdParameters = {
model: AMD.Model.RU
};
const amd = AMD.create(amdParameters);
// Add amd event listeners
amd.addEventListener(AMD.Events.DetectionComplete, handleDetectionComplete);
amd.addEventListener(AMD.Events.DetectionError, handleDetectionError);
// Make a call
const callPSTNParameters = {
amd
};
const call = VoxEngine.callPSTN(storage.phoneNumber, storage.callerId, callPSTNParameters);
// Add call event listeners
call.addEventListener(CallEvents.Connected, handleCallConnected);
call.addEventListener(CallEvents.Failed, handleCallFailed);
call.addEventListener(CallEvents.Disconnected, handleCallDisconnected);
});

Launching the call list

To initiate a call list, you must send a Management API request. For more information about our call lists management API, refer to our API reference.

Please note

You cannot start a call list if your balance is below 1 USD. Once you top up your account, the lists launch automatically.

Let us take a look at the createCallList request parameters:

  • account_name — your Voximplant account name
  • api_key — your Voximplant API key
  • rule_id — the application rule id with the assigned scenario
  • priority — dialing priority, if you have more than one call list
  • max_simultaneous — maximum number of simultaneously processed records from a CSV file
  • num_attempts — dialing attempts number, a new attempt appears in case of CallList.reportError
  • name — call list name, useful to differentiate different call lists from each other
  • file_content — CSV file to be sent in the request body
  • interval_seconds — time interval for the next attempt in seconds
  • encoding — CSV file encoding, if different from UTF-8
  • delimiter — CSV file columns delimiter, default is ”;”

The getCallListDetails request result looks the following:

"appointment_date";"last_name";"phone_number";"first_name";"__end_execution_time";"__start_execution_time";"result_data";"last_attempt";"attmepts_left";"status_id";status
November 15th;Jon;16501234567;Snow;;;"{""result"":true,""duration"":27}";"2021-05-24 19:21:39";1;2;Processed
November 16th;Daenerys;16501234568;Targaryen;;;"{""result"":true,""duration"":18}";"2021-05-24 19:22:10";1;2;Processed

Processing user input

There are two ways to receive user feedback during a call: through voice and by pressing buttons. To process voice feedback, you can utilize our built-in voice recognition. For button feedback, you need to handle the ToneReceived event.

Handle DTMF tones

In our code example, let us prompt the customer to rate the service quality on a scale of 1 to 5 and then handle the corresponding event.

Handle user feedback
// The storage for the scenario dataconst storage = {
playbackCounter: 0,
};
const handleCallConnected = (e) => {
setTimeout(() => {
e.call.say(
`Hello ${storage.firstName}! Your appointment scheduled for ${storage.appointmentDate}`,
{ voice: VoiceList.Amazon.en_US_Joanna }
);
}, 500);
e.call.addEventListener(CallEvents.PlaybackFinished, handlePlaybackFinished);
e.call.addEventListener(CallEvents.ToneReceived, handleToneReceived);
};
const handleToneReceived = (e) => {
e.call.removeEventListener(CallEvents.PlaybackFinished, handlePlaybackFinished);
e.call.stopPlayback();
storage.rating = e.tone;
e.call.say('Thank you for your answer!', { voice: VoiceList.Amazon.en_US_Joanna });
e.call.addEventListener(CallEvents.PlaybackFinished, (e) => e.call.hangup());
}
const handleCallDisconnected = async (e) => {
try {
// Tell CallList processor about successful call result await CallList.reportResultAsync(
{
result: true,
duration: e.duration,
rating: storage.rating }
);
VoxEngine.terminate();
} catch (error) {
Logger.write(`'CallList.reportResultAsync' error:`);
Logger.write(error);
}
};
const handlePlaybackFinished = (e) => {
e.call.removeEventListener(CallEvents.PlaybackFinished, handlePlaybackFinished);
storage.playbackCounter++;
// If the message was played 4 times - hangup if (storage.playbackCounter === 4) {
e.call.hangup();
} else {
setTimeout(() => {
e.call.say('Please rate the customer service quality from 1 to 5.', { voice: VoiceList.Amazon.en_US_Joanna});
e.call.addEventListener(CallEvents.PlaybackFinished, handlePlaybackFinished);
}, 2000);
}
};

Handle user speech

Use Voximplant’s voice recognition feature to handle the user’s speech.

In our code example, require the ASR module and create a speech recognition instance via the createASR method. Then use the sendMediaTo method to send the customer’s input to the instance.

You receive the result in the ASREvents.Result event. You can process the result as you need.

Please take a look at the code example to understand how it works:

Survey with speech recognition
require(Modules.CallList); // Enable CallList module
require(Modules.AI);
require(Modules.ASR);
// The storage for the scenario data
const storage = {
callerId: '+1234567890', // rented or verified phone number
playbackCounter: 0
};
const handlePlaybackFinished = (e) => e.call.sendMediaTo(storage.asr);
const handleToneReceived = (e) => {
e.call.removeEventListener(CallEvents.PlaybackFinished, handlePlaybackFinished);
e.call.stopPlayback();
storage.rating = e.tone;
e.call.say('Thank you for your answer!', { voice: VoiceList.Amazon.en_US_Joanna });
e.call.addEventListener(CallEvents.PlaybackFinished, (e) => e.call.hangup());
};
const handleCallConnected = (e) => {
e.call.handleTones(true); // <-- enable input processing
setTimeout(() => {
e.call.say(
`Hello ${storage.firstName}! Thank you for visiting our store, ` +
`please rate the customer service quality from 1 to 5.`,
{ voice: VoiceList.Amazon.en_US_Joanna }
);
}, 1000);
e.call.addEventListener(CallEvents.ToneReceived, handleToneReceived);
e.call.addEventListener(CallEvents.PlaybackFinished, handlePlaybackFinished);
};
const handleCallDisconnected = async (e) => {
try {
// Tell CallList processor about successful call result
await CallList.reportResultAsync({
result: true,
duration: e.duration,
rating: storage.rating
});
VoxEngine.terminate();
} catch (error) {
Logger.write(`'CallList.reportResultAsync' error:`);
Logger.write(error);
}
};
const handleCallFailed = async (e) => {
try {
/**
* Tell CallList processor that we could not get a call connected.
* Depending on the request options it either try to launch the scenario again after some time
* or write the result (failed call) into result_data column of the CSV file with results
*/
await CallList.reportErrorAsync({
result: false,
msg: 'Failed',
code: e.code
});
VoxEngine.terminate();
} catch (error) {
Logger.write(`'CallListAsync.reportError' error:`);
Logger.write(error);
}
};
const handleDetectionComplete = async (result) => {
Logger.write(`Machine answer detection is complete.`);
if (result.resultClass === AMD.ResultClass.VOICEMAIL) {
Logger.write(`Voicemail detected with a ${result.confidence}% confidence.`);
try {
await CallList.reportErrorAsync('Voicemail');
VoxEngine.terminate();
} catch (error) {
Logger.write(`'CallListAsync.reportError' error:`);
Logger.write(error);
}
} else {
Logger.write('Voicemail not detected.');
}
};
const handleDetectionError = (error) => {
Logger.write(`Detection failed with an error:`);
Logger.write(error);
};
const handleAsrResult = (e) => {
if (e.confidence > 50) {
storage.call.removeEventListener(CallEvents.PlaybackFinished, handlePlaybackFinished);
storage.call.stopPlayback();
storage.call.say(
`You gave us ${e.text}, we appreciate it, thank you again.`,
VoiceList.Amazon.en_US_Joanna
);
storage.rating = e.text;
storage.call.addEventListener(CallEvents.PlaybackFinished, () => {
storage.asr.stop();
storage.call.hangup();
});
} else {
storage.call.say(
'Could not recognize your answer, say it again.',
VoiceList.Amazon.en_US_Joanna
);
storage.call.addEventListener(CallEvents.PlaybackFinished, handlePlaybackFinished);
}
};
const handleAsrSpeechCaptured = () => {
storage.call.stopMediaTo(storage.asr);
};
// AppEvents.Started dispatched for each CSV record
VoxEngine.addEventListener(AppEvents.Started, () => {
const customData = VoxEngine.customData(); // data from CSV string in JSON format
const { first_name, last_name, phone_number, appointment_date } = JSON.parse(customData);
storage.firstName = first_name;
storage.lastName = last_name;
storage.phoneNumber = phone_number;
storage.appointmentDate = appointment_date;
Logger.write(`Calling ${storage.firstName} ${storage.lastName} on ${storage.phoneNumber}`);
// Create an ASR instance
storage.asr = VoxEngine.createASR({
profile: ASRProfileList.Google.en_US,
phraseHints: ['One', 'Two', 'Three', 'Four', 'Five']
});
// Add asr event listeners
storage.asr.addEventListener(ASREvents.Result, handleAsrResult);
storage.asr.addEventListener(ASREvents.SpeechCaptured, handleAsrSpeechCaptured);
// Create an AMD instance
const amdParameters = {
model: AMD.Model.RU
};
const amd = AMD.create(amdParameters);
// Add amd event listeners
amd.addEventListener(AMD.Events.DetectionComplete, handleDetectionComplete);
amd.addEventListener(AMD.Events.DetectionError, handleDetectionError);
// Make a call
const callPSTNParameters = {
amd
};
storage.call = VoxEngine.callPSTN(storage.phoneNumber, storage.callerId, callPSTNParameters);
// Add call event listeners
storage.call.addEventListener(CallEvents.Connected, handleCallConnected);
storage.call.addEventListener(CallEvents.Failed, handleCallFailed);
storage.call.addEventListener(CallEvents.Disconnected, handleCallDisconnected);
});

The result is the following:

"last_name";"phone_number";"first_name";"__end_execution_time";"__start_execution_time";"result_data";"last_attempt";"attmepts_left";"status_id";status
Jon;16501234567;Snow;;;"{""result"":true,""duration"":27,""rating"":""four""}";"2021-05-24 20:17:13";1;2;Processed
Snow;"November 15th";"+16501234567";Jon;;;"{""result"":true,""duration"":14,""rating"":""four""}";"2021-07-15 20:20:38";0;0;2;Processed;
Targaryen;"November 16th";"+16501234568";Daenerys;;;"{""result"":true,""duration"":27,""rating"":""five""}";"2021-07-15 20:21:37";0;0;2;Processed;

You can also make HTTP requests from the scenario to pass the data to your backend in real-time. You can save the survey results to the customData parameter and then use it to get statistics, through the call_session_history_custom_data parameter of the GetCallHistory method.

Frequently asked questions

Call lists do not work if your account balance is less than $1. After you replenish the balance, call lists resume automatically.

To delete a call list, pause it or wait until completion because you cannot delete a call list that is in progress.