> For a complete documentation index, fetch https://docs.voximplant.ai/llms.txt

# Call lists for automated calls

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](/platform/voxengine/applications).
2. Create [a scenario](/platform/voxengine/scenarios).
3. Create [a routing rule](/platform/voxengine/routing-rules) 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](/platform/voxengine/scenarios). Here’s an example of a call list CSV table format:

```json
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.

```json
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](https://voximplant.com/docs/references/voxengine/calllist) 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](https://voximplant.com/docs/guides/calls/voicemail-detection). Then, we use the [speech synthesis](https://voximplant.com/docs/guides/speech/tts) feature to announce the appointment time.

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

```javascript title="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](https://voximplant.com/docs/references/httpapi/calllists#createcalllist).

<Warning title="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.
</Warning>

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](https://voximplant.com/docs/references/httpapi/calllists#getcalllistdetails) request result looks the following:

```json
"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](https://voximplant.com/docs/guides/speech/asr). For button feedback, you need to handle the [ToneReceived](https://voximplant.com/docs/references/voxengine/callevents#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.

```javascript title="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](https://voximplant.com/docs/guides/speech/asr) feature to handle the user's speech.

In our code example, require the [ASR module](https://voximplant.com/docs/references/voxengine/asr) and create a speech recognition instance via the [createASR](https://voximplant.com/docs/references/voxengine/voxengine/createasr) method. Then use the [sendMediaTo](https://voximplant.com/docs/references/voxengine/call#sendmediato) method to send the customer's input to the instance.

You receive the result in the [ASREvents.Result](https://voximplant.com/docs/references/voxengine/asrevents#result) event. You can process the result as you need.

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

```javascript title="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:

```json
"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](https://voximplant.com/docs/references/voxengine/voxengine/customdata) parameter and then use it to get statistics, through the `call_session_history_custom_data` parameter of the [GetCallHistory](https://voximplant.com/docs/references/httpapi/history#getcallhistory) method.

## Frequently asked questions

<AccordionGroup>
  <Accordion title="My call list processing suddenly stopped, why?">
    Call lists do not work if your account balance is less than \$1. After you replenish the balance, call lists resume automatically.
  </Accordion>

  <Accordion title="I cannot delete my call list, why?">
    To delete a call list, pause it or wait until completion because you cannot delete a call list that is in progress.
  </Accordion>
</AccordionGroup>