aboutsummaryrefslogtreecommitdiffstats
path: root/doc/queues-with-callback-members.txt
blob: c58c30a28338915a8966e8bf092fc163444b6fb9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
Setting up Call Queues -- A Tutorial

Pardon, but the dialplan in this tutorial will be expressed
in AEL, the new Asterisk Extension Language. If you are 
not used to its syntax, we hope you will find it to some
degree intuitive. If not, there are documents explaining
its syntax and constructs.


======  Configuring Call Queues

First of all, set up call queues in queue.conf

Here is an example:

   =========== queues.conf ===========
   | ; Cool Digium Queues            |
   | [general]                       |
   | persistentmembers = yes         |
   |                                 |
   | ; General sales queue           |
   | [sales-general]                 |
   | music=default                   |
   | context=sales                   |
   | strategy=ringall                |
   | joinempty=strict                |
   | leavewhenempty=strict           |
   |                                 |
   | ; Customer service queue        |
   | [customerservice]               |
   | music=default                   |
   | context=customerservice         |
   | strategy=ringall                |
   | joinempty=strict                |
   | leavewhenempty=strict           |
   |                                 |
   | ; Support dispatch queue        |
   | [support-dispatch]              |
   | music=default                   |
   | context=dispatch                |
   | strategy=ringall                |
   | joinempty=strict                |
   | leavewhenempty=strict           |
   ===================================

In the above, we have defined 3 separate calling queues: 
sales-general, customerservice, and support-dispatch.

Please note that the sales-general queue specifies a
context of "sales", and that customerservice specifies the
context of "customerservice", and the support-dispatch
queue specifies the context "dispatch". These three
contexts must be defined somewhere in your dialplan.
We will show them after the main menu below.

<verbage explaining options above>
In the [general] section, specifying the persistentmembers=yes,
will cause the agent lists to be stored in astdb, and 
recalled on startup.

The strategy=ringall will cause all agents to be dialed
together, the first to answer is then assigned the incoming
call. 

"joinempty" set to "strict" will keep incoming callers from
being placed in queues where there are no agents to take calls.
The Queue() application will return, and the dial plan can 
determine what to do next.

If there are calls queued, and the last agent logs out, the
remaining incoming callers will immediately be removed from
the queue, and the Queue() call will return, IF the "leavewhenempty" is
set to "strict".


=====================================
|  Routing incoming Calls to Queues |
=====================================


Then in extensions.ael, you can do these things:

================ The Main Menu 

At Digium, incoming callers are sent to the "mainmenu" context, where they
are greeted, and directed to the numbers they choose...

context mainmenu {

	includes {
		digium;
		queues-loginout;
	}

        0 => goto dispatch|s|1;
        2 => goto sales|s|1;
        3 => goto customerservice|s|1;
        4 => goto dispatch|s|1;

        s => {
                Ringing();
                Wait(1);
                Set(attempts=0);
                Answer();
                Wait(1);
                Background(digium/ThankYouForCallingDigium);
                Background(digium/YourOpenSourceTelecommunicationsSupplier);
                WaitExten(0.3);
        repeat:
                Set(attempts=$[${attempts} + 1]);
                Background(digium/IfYouKnowYourPartysExtensionYouMayDialItAtAnyTime);
                WaitExten(0.1);
                Background(digium/Otherwise);
                WaitExten(0.1);
                Background(digium/ForSalesPleasePress2);
                WaitExten(0.2);
                Background(digium/ForCustomerServicePleasePress3);
                WaitExten(0.2);
                Background(digium/ForAllOtherDepartmentsPleasePress4);
                WaitExten(0.2);
                Background(digium/ToSpeakWithAnOperatorPleasePress0AtAnyTime);
                if( ${attempts} < 2 ) {
                        WaitExten(0.3);
                        Background(digium/ToHearTheseOptionsRepeatedPleaseHold);
                }
                WaitExten(5);
                if( ${attempts} < 2 ) goto repeat;
                Background(digium/YouHaveMadeNoSelection);
                Background(digium/ThisCallWillBeEnded);
                Background(goodbye);
                Hangup();
        }
}


============= The Contexts referenced from the queues.conf file



context sales {

        0 => goto dispatch|s|1;
        8 => Voicemail(${SALESVM});

        s => {
                Ringing();
                Wait(2);
                Background(digium/ThankYouForContactingTheDigiumSalesDepartment);
                WaitExten(0.3);
                Background(digium/PleaseHoldAndYourCallWillBeAnsweredByOurNextAvailableSalesRepresentative);
                WaitExten(0.3);
                Background(digium/AtAnyTimeYouMayPress0ToSpeakWithAnOperatorOr8ToLeaveAMessage);
                Set(CALLERID(name)=Sales);
                Queue(sales-general|t);
                Set(CALLERID(name)=EmptySalQ);
                goto dispatch|s|1;
                Playback(goodbye);
                Hangup();
        }
}

Please note that there is only one attempt to queue a call in the sales queue. All sales agents that
are logged in will be rung.


context customerservice {

        0 => {
                SetCIDName(CSVTrans);
                goto dispatch|s|1;
        }
        8 => Voicemail(${CUSTSERVVM});

        s => {
                Ringing();
                Wait(2);
                Background(digium/ThankYouForCallingDigiumCustomerService);
                WaitExten(0.3);
        notracking:
                Background(digium/PleaseWaitForTheNextAvailableCustomerServiceRepresentative);
                WaitExten(0.3);
                Background(digium/AtAnyTimeYouMayPress0ToSpeakWithAnOperatorOr8ToLeaveAMessage);
                Set(CALLERID(name)=Cust Svc);
                Set(QUEUE_MAX_PENALTY=10);
                Queue(customerservice|t);
                Set(QUEUE_MAX_PENALTY=0);
                Queue(customerservice|t);
                Set(CALLERID(name)=EmptyCSVQ);
                goto dispatch|s|1;
                Background(digium/NoCustomerServiceRepresentativesAreAvailableAtThisTime);
                Background(digium/PleaseLeaveAMessageInTheCustomerServiceVoiceMailBox);
                Voicemail(${CUSTSERVVM});
                Playback(goodbye);
                Hangup();
        }
}

Note that calls coming into customerservice will first be try to queue
calls to those agents with a QUEUE_MAX_PENALTY of 10, and if none are available,
then all agents are rung.


context dispatch
{

        s => {
                Ringing();
                Wait(2);
                Background(digium/ThankYouForCallingDigium);
                WaitExten(0.3);
                Background(digium/YourCallWillBeAnsweredByOurNextAvailableOperator);
                Background(digium/PleaseHold);
                Set(QUEUE_MAX_PENALTY=10);
                Queue(dispatch|t);
                Set(QUEUE_MAX_PENALTY=20);
                Queue(dispatch|t);
                Set(QUEUE_MAX_PENALTY=0);
                Queue(dispatch|t);
                Background(digium/NoOneIsAvailableToTakeYourCall);
                Background(digium/PleaseLeaveAMessageInOurGeneralVoiceMailBox);
                Voicemail(${DISPATCHVM});
                Playback(goodbye);
                Hangup();
        }
}

And in the dispatch context, first agents of priority 10 are tried, then
20, and if none are available, all agents are tried. 

Notice that a common pattern is followed in each of the three queue contexts:

First, you set QUEUE_MAX_PENALTY to a value, then you call
Queue(<queue-name>,option,... (see the documentation for the Queue application));

In the above, note that the "t" option is specified, and this allows the 
agent picking up the incoming call the luxury of transferring the call to 
other parties.

The purpose of specifying the QUEUE_MAX_PENALTY is to develop a set of priorities
amongst agents. By the above usage, agents with lower number priorities will 
be given the calls first, and then, if no-one picks up the call, the QUEUE_MAX_PENALTY
will be incremented, and the queue tried again. Hopefully, along the line, someone
will pick up the call, and the Queue application will end with a hangup.

The final attempt to queue in most of our examples sets the QUEUE_MAX_PENALTY
to zero, which means to try all available agents.


=========================================
|      Assigning agents to Queues       |
=========================================

In this example dialplan, we want to be able to add and remove agents to 
handle incoming calls, as they feel they are available. As they log in,
they are added to the queue's agent list, and as they log out, they are
removed. If no agents are available, the queue command will terminate, and
it is the duty of the dialplan to do something appropriate, be it sending
the incoming caller to voicemail, or trying the queue again with a higher 
QUEUE_MAX_PENALTY.

Because a single agent can make themselves available to more than one queue,
the process of joining multiple queues can be handled automatically by the
dialplan.


================= Agents Log In and Out 


context queues-loginout
{
        6092 => {
                        Answer();
                        Read(AGENT_NUMBER,agent-enternum);
                        VMAuthenticate(${AGENT_NUMBER}@default,s);
                        Set(queue-announce-success=1);
                        goto queues-manip,I${AGENT_NUMBER},1;
                }

        6093 => {
                        Answer();
                        Read(AGENT_NUMBER,agent-enternum);
                        Set(queue-announce-success=1);
                        goto queues-manip,O${AGENT_NUMBER},1;
                }
}


In the above contexts, the agents dial 6092 to log into their queues,
and they dial 6093 to log out of their queues. The agent is prompted
for their agent number, and if they are logging in, their passcode, 
and then they are transferred to the proper extension in the 
queues-manip context.  The queues-manip context does all the 
actual work:


context queues-manip {

        // Raquel Squelch
        _[IO]6121 => {
                &queue-addremove(dispatch,10);
                &queue-success();
        }

        // Brittanica Spears
        _[IO]6165 => {
                &queue-addremove(dispatch,20);
                &queue-success();
        }

        // Rock Hudson
        _[IO]6170 => {
                &queue-addremove(sales-general,10);
                &queue-addremove(customerservice,20);
                &queue-addremove(dispatch,30);
                &queue-success();
        }

        // Saline Dye-on
        _[IO]6070 => {
                &queue-addremove(sales-general,20);
                &queue-addremove(customerservice,30);
                &queue-addremove(dispatch,30);
                &queue-success();
        }
}

In the above extensions, note that the queue-addremove macro is used
to actually add or remove the agent from the applicable queue,
with the applicable priority level. Note that agents with a 
priority level of 10 will be called before agents with levels
of 20 or 30.

In the above example, Raquel will be dialed first in the dispatch
queue, if she has logged in. If she is not, then the second call of
Queue() with priority of 20 will dial Brittanica if she is present,
otherwise the third call of Queue() with MAX_PENALTY of 0 will 
dial Rock and Saline simultaneously.

Also note that Rock will be among the first to be called in the sales-general 
queue, and among the last in the dispatch queue. As you can see in
main menu, the callerID is set in the main menu so they can tell 
which queue incoming calls are coming from.

The call to queue-success() gives some feedback to the agent
as they log in and out, that the process has completed.

macro queue-success()
{
        if( ${queue-announce-success} > 0 )
        {
                switch(${MACRO_EXTEN:0:1})
                {
                case I:
                        Playback(agent-loginok);
                        Hangup();
						break;
                case O:
                        Playback(agent-loggedoff);
                        Hangup();
						break;
                }
        }
}


The queue-addremove macro is defined in this manner:

macro queue-addremove(queuename,penalty)
{
        switch(${MACRO_EXTEN:0:1})
        {
        case I:  // Login
                AddQueueMember(${queuename},Local/${MACRO_EXTEN:1}@agents,${penalty});
				break;
        case O:  // Logout
                RemoveQueueMember(${queuename},Local/${MACRO_EXTEN:1}@agents);
				break;
        case P:  // Pause
                PauseQueueMember(${queuename},Local/${MACRO_EXTEN:1}@agents);
				break;
        case U:  // Unpause
                UnpauseQueueMember(${queuename},Local/${MACRO_EXTEN:1}@agents);
				break;
        default: // Invalid
                Playback(invalid);
				break;
        }
}

Basically, it uses the first character of the MACRO_EXTEN variable, to determine the
proper actions to take. In the above dial plan code, only the cases I or O are used,
which correspond to the Login and Logout actions.


=======================================================
|    Controlling The Way Queues Call the Agents       |
=======================================================

Notice in the above, that the commands to manipulate agents in queues have
"@agents" in their arguments. This is a reference to the agents context:

context agents
{
	// General sales queue
	8010 =>
	{
		Set(QUEUE_MAX_PENALTY=10);
		Queue(sales-general|t);
		Set(QUEUE_MAX_PENALTY=0);
		Queue(sales-general|t);
		Set(CALLERID(name)=EmptySalQ);
		goto dispatch|s|1;
	}
	// Customer Service queue
	8011 =>
	{
		Set(QUEUE_MAX_PENALTY=10);
		Queue(customerservice|t);
		Set(QUEUE_MAX_PENALTY=0);
		Queue(customerservice|t);
		Set(CALLERID(name)=EMptyCSVQ);
		goto dispatch|s|1;
	}
	8013 =>
	{
		Dial(iax2/sweatshop/9456@from-ecstacy);

		Set(CALLERID(name)=EmptySupQ);
		Set(QUEUE_MAX_PENALTY=10);
		Queue(support-dispatch,t);
		Set(QUEUE_MAX_PENALTY=20);
		Queue(support-dispatch,t);
		Set(QUEUE_MAX_PENALTY=0); // means no max
		Queue(support-dispatch,t);
		goto dispatch|s|1;
	}
	6121 => &callagent(${RAQUEL});
	6165 => &callagent(${SPEARS});
	6170 => &callagent(${ROCK});
	6070 => &callagent(${SALINE});
}

In the above, the variables ${RAQUEL}, etc stand for
actual devices to ring that person's
phone (like Zap/37).

The 8010, 8011, and 8013 extensions are purely for transferring
incoming callers to queues. For instance, a customer service 
agent might want to transfer the caller to talk to sales. The 
agent only has to transfer to extension 8010, in this case.

Here is the callagent macro, note that if a person in the
queue is called, but does not answer, then they are automatically
removed from the queue.

macro callagent(device)
{
	if( ${GROUP_COUNT(${MACRO_EXTEN}@agents)}=0 )
	{
		Set(OUTBOUND_GROUP=${MACRO_EXTEN}@agents);
		Dial(${device}|300|t);
		switch(${DIALSTATUS})
		{
		case BUSY:
			Busy();
			break;
		case NOANSWER:
			Set(queue-announce-success=0);
			goto queues-manip|O${MACRO_EXTEN}|1;
		default:
			Hangup();
			break;
		}
	}
	else
	{
		Busy();
	}
}

In the callagent macro above, the ${MACRO_EXTEN} will
be 6121, or 6165, etc, which is the extension of the agent.

The use of the GROUP_COUNT, and OUTBOUND_GROUP follow this line
of thinking. Incoming calls can be queued to ring all agents in the
current priority. If some of those agents are already talking, they
would get bothersome call-waiting tones. To avoid this inconvenience,
when an agent gets a call, the OUTBOUND_GROUP assigns that 
conversation to the group specified, for instance 6171@agents.
The ${GROUP_COUNT()} variable on a subsequent call should return
"1" for that group. If GROUP_COUNT returns 1, then the busy() 
is returned without actually trying to dial the agent.

================ Pre Acknowledgement Message

If you would like to have a pre acknowledge message with option to reject the message
you can use the following dialplan Macro as a base with the 'M' dial argument.

[macro-screen]
exten=>s,1,Wait(.25)
exten=>s,2,Read(ACCEPT|screen-callee-options|1)
exten=>s,3,Gotoif($[${ACCEPT} = 1] ?50)
exten=>s,4,Gotoif($[${ACCEPT} = 2] ?30)
exten=>s,5,Gotoif($[${ACCEPT} = 3] ?40)
exten=>s,6,Gotoif($[${ACCEPT} = 4] ?30:30)
exten=>s,30,Set(MACRO_RESULT=CONTINUE)
exten=>s,40,Read(TEXTEN|custom/screen-exten|)
exten=>s,41,Gotoif($[${LEN(${TEXTEN})} = 3]?42:45)
exten=>s,42,Set(MACRO_RESULT=GOTO:from-internal^${TEXTEN}^1)
exten=>s,45,Gotoif($[${TEXTEN} = 0] ?46:4)
exten=>s,46,Set(MACRO_RESULT=CONTINUE)
exten=>s,50,Playback(after-the-tone)
exten=>s,51,Playback(connected)
exten=>s,52,Playback(beep)

================ Caveats 

In the above examples, some of the possible error checking has been omitted,
to reduce clutter and make the examples clearer.