Course 1 / Lecture 8

How to build an autodialer in C# with Ozeki VoIP SIP SDK

Download: auto-dialer.zip

This tutorial explains how to build an autodialer in C# with Ozeki VoIP SIP SDK that reads phone numbers and messages from a CSV file, dials them automatically, and plays each message as synthesized speech once the call is answered. You will learn how to cap the number of calls placed at the same time, so the autodialer respects your network provider's concurrent call limit instead of dialing every number at once. The example builds on the previous "Parallel call management" tutorial, adding a queue that throttles outgoing calls to a configurable maximum.

Building an autodialer poster image

Why do we need an autodialer?

Dialing a large list of phone numbers by hand is slow and doesn't scale past a handful of calls, and manually respecting a provider's concurrent call limit while doing it is error-prone. An autodialer automates both problems at once: it works through a list of numbers and messages on its own, and it caps how many calls run at the same time so the account never exceeds what the network allows. This is the foundation behind outbound notification systems, appointment reminders, and IVR campaigns that need to reach many people without a human dialing each one.

Why do we need an autodialer
Why do we need an autodialer

What is an autodialer?

An autodialer is software that automatically places outbound calls to a list of phone numbers, without a person manually dialing each one. In this example, the autodialer reads phone numbers and messages from a CSV file, places calls up to a configurable concurrency limit, and plays each message as synthesized speech once the call is answered, using the Ozeki VoIP SIP SDK's TextToSpeech media handler.

What is an autodialer
What is an autodialer

How does it work?

The Autodialer works through the list of CallInfo objects on a background thread. If fewer calls are running than the configured limit, it starts the next one immediately; otherwise it waits on an AutoResetEvent until a running call finishes. Each completed call signals that event, which releases the wait and lets exactly one queued call start, so the number of simultaneous calls never exceeds the limit you set.

How the autodialer's concurrent call limit works
How does it work

How to build and run your solution

Download the example project, extract the archive, and open the solution in Visual Studio. Locate the example CSV file to see how phone numbers and messages are formatted and where the file lives in the project, then start the application.

How to test the solution

Enter your SIP account details and wait for registration to succeed. Set the maximum number of simultaneous calls, then press Enter to use the default example CSV file, or provide the path to your own. Confirm that only one call runs at a time, and that the next number is dialed as soon as the current call ends.

Key source code

The core of this example is Autodialer.Start() together with StartCallHandler(). The background loop starts the next queued call immediately if the concurrency limit hasn't been reached, or waits on an AutoResetEvent until a running call signals completion. This is what guarantees the number of simultaneous calls never exceeds the configured maximum.

Autodialer Start and StartCallHandler methods showing the concurrency throttling logic
Key source code

Capture SIP traffic with Wireshark

Start a Wireshark capture on the network interface used by the softphone, then run the same test as above with the concurrency limit set to 1. Apply a sip.Method == "INVITE" display filter to isolate the outgoing INVITEs, and confirm they appear one after another, each addressed to the next number in the CSV file, only once the previous call has ended.

What is a SIP INVITE PDU for an autodialer call

This is the INVITE PDU the autodialer sends for the first number in the CSV file, identified by its To header addressing extension 1001 and its own unique Call-ID. As the Autodialer works through the rest of the list, each subsequent call gets a fresh INVITE with a different destination and Call-ID, one at a time, since the concurrency limit in this test is set to 1.

SIP INVITE PDU for the autodialer's call to the first number in the CSV file
Figure 1. SIP INVITE PDU for the autodialer's call to the first number in the CSV file

Debug SIP events in Asterisk

Connect to the Asterisk console and enable SIP debug output, then run the autodialer against the example CSV file. Watch each outgoing call reach Asterisk one after another, matching the phone number dialed to the corresponding row in the CSV file, and confirm the next number is only dialed once the current call has ended.

What knowledge would you need?

This example assumes you're already familiar with SIP registration, media handlers, making and accepting calls, and parallel call management, covered in the earlier tutorials linked below.

This example uses five classes

  • Softphone.cs — registers to a PBX and creates outgoing call objects.
  • Program.cs — asks for SIP details, the concurrency limit, and the CSV path, then reads the file.
  • CallInfo.cs — represents one CSV row as a phone number and message pair.
  • CallHandler.cs — dials one CallInfo's number and plays its message when answered.
  • Autodialer.cs — creates a CallHandler per CallInfo, throttled to the concurrency limit.

Softphone.cs

The autodialer's softphone registers to a PBX, reports the phone line's state, and creates call objects on request. Since it never needs to receive incoming calls, its SIP account can set registrationRequired to false — the autodialer can still dial out through the PBX without registering, as long as the PBX allows it.

public IPhoneCall CreateCall(string member)
{
    return _softphone.CreateCallObject(_phoneLine, member);
}

Program.cs and the CSV file

Program.cs collects the SIP account details, the maximum number of simultaneous calls, and the CSV file path, then reads the file and builds a list of CallInfo objects before starting the Autodialer. Each line in the CSV pairs a phone number with a message, separated by a comma or semicolon:

Why should be the amount of simultaneously made calls set?

Network providers usually restrict how many calls can be made simultaneously, so to avoid further complications, set this integer number to be less or equal to that amount.
For example: if you set the number to "30", there will be always 30 or less outgoing calls.

What is csv file, and how that will be used?

A comma-separated values (CSV) (also sometimes called character-separated values, because the separator character does not have to be a comma) file stores tabular data (numbers and text) in plain-text form.
An example csv file:

phonenumber1;message1
phonenumber2;message2
static void ParseCSVLineToObjectList(string line)
{
    string[] parse = line.Split(',', ';');
    _callInfo = new CallInfo(parse[0], parse[1]);
    _callList.Add(_callInfo);
}
 
static void StartAutodialer()
{
    _autodialer = new Autodialer(_mySoftphone, _callList, _maxConcurrentCall);
    _autodialer.Start();
}

What is CallInfo.cs used for?

Each CallInfo object represents a line within the csv file, which is being used as a complex value, as it stores a phone number and a message to be sent to that party. CallHandler objects will be created for all of the CallInfo objects, to manage the calls separately.

What is CallHandler.cs used for?

The softphone can handle multiple calls simultaneously, and each of those is being handled by a CallHandler instance, set by a CallInfo object. Since a CallInfo object stores a phone number, the call will be created to that number, and the message is being passed to the TextToSpeech() method which converts the text message to audio data, than being played into the call, when that is being answered.

How are calls being managed simultaneously?

Instances of the class are being created from information stored in CallInfo objects, and within the Start() method of the class, a call object is being created by the Softphone class's CreateCall() method to the CallInfo object's PhoneNumber value, and it also subscribes to the calls' events etc., then makes the call.

public void Start()
    {
        var call = _softphone.CreateCall(_callInfo.PhoneNumber);
        call.CallStateChanged += OutgoingCallStateChanged;
        mediaSender.AttachToCall(call);
        call.Start();
    }

What happens during the outgoing calls?

Since the application is being notified when a call's state is being changed, tasks can be done during those changes:

  • when the call is being Answered, the CallInfo object's Message value is being played into the call with the help of the TextToSpeech() method.
  • the call can be ended by several reasons: the destination is busy or could not be reached, the call is completed etc. In these cases, the Completed event is being invoked:
private void OutgoingCallStateChanged(object sender, CallStateChangedArgs e)
    {
        if (e.State == CallState.Answered)
        {
            TextToSpeech(_callInfo.Message);
        }
        else if (e.State.IsCallEnded())
        {
            var handler = Completed;
            if (handler != null)
                handler(this, EventArgs.Empty);
        }
    }

How does the TextToSpeech function work?

Ozeki VoIP SIP SDK provides media handler, called TextToSpeech for the purpose to convert text to audio data. The TextToSpeech() method uses this media handler as an AudioHandler to send the CallInfo object's Message value into the call, through a PhoneCallAudioSender object:

void TextToSpeech(string text)
    {
        var textToSpeech = new TextToSpeech();
        connector.Connect(textToSpeech, mediaSender);
        textToSpeech.AddAndStartText(text);
    }

Autodialer.cs

The Autodialer creates a CallHandler for each CallInfo and starts it, but never runs more calls at once than the configured limit. See the Key source code section above for the full throttling logic.

When a CallHandler completes, it's removed from the active list, the running count is decreased, and the Autodialer is notified so it can start the next queued call.

Conclusion

This tutorial covered building an autodialer that reads a CSV of phone numbers and messages, places calls up to a configurable concurrency limit, and plays each message with text-to-speech once a call is answered. It also verified the throttling behavior by inspecting concurrent Call-IDs and call hand-off timing in Wireshark.

If you have any questions or need assistance, please contact us at info@voip-sip-sdk.com

Related Pages


More information