RT::Extension::SLA - Service Level Agreements for RT


RT extension to implement automated due dates using service levels.


On upgrade you shouldn't run 'make initdb'.

If you were using 0.02 or older version of this extension with RT 3.8.1 then you have to uninstall that manually. List of files you can find in the MANIFEST.


perl Makefile.PL
make install
make initdb (for the first time only)


Service level agreements of tickets is controlled by an SLA custom field (CF). This field is created during make initdb step (above) and applied globally. This CF MUST be of select one value type. Values of the CF define the service levels.

It's possible to define different set of levels for different queues. You can create several CFs with the same name and different set of values. But if you move tickets between queues a lot then it's going to be a problem and it's preferred to use ONE SLA custom field.

There is no WebUI in the current version. Almost everything is controlled in the RT's config using option %RT::ServiceAgreements and %RT::ServiceBusinessHours. For example:

    Set( %ServiceAgreements,
        Default => '4h',
        QueueDefault => {
            'Incident' => '2h',
        Levels => {
            '2h' => { Resolve => { RealMinutes => 60*2 } },
            '4h' => { Resolve => { RealMinutes => 60*4 } },

In this example Incident is the name of the queue, and 2h is the name of the SLA which will be applied to this queue by default.

Each service level can be described using several options: StartImmediately, Resolve, Response, KeepInLoop, OutOfHours and ServiceBusinessHours.

StartImmediately (boolean, false)

By default when a ticket is created Starts date is set to first business minute after time of creation. In other words if a ticket is created during business hours then Starts will be equal to Created time, otherwise Starts will be beginning of the next business day.

However, if you provide 24/7 support then you most probably would be interested in Starts to be always equal to Created time. In this case you can set option StartImmediately to a true value.


    '24/7' => {
        StartImmediately => 1,
        Response => { RealMinutes => 30 },
    'standard' => {
        StartImmediately => 0, # can be ommited as it's default
        Response => { BusinessMinutes => 2*60 },

Resolve and Response (interval, no defaults)

These two options define deadlines for resolve of a ticket and reply to customer(requestors) questions accordingly.

You can define them using real time, business or both. Read more about the latter below.

The Due date field is used to store calculated deadlines.


Defines deadline when a ticket should be resolved. This option is quite simple and straightforward when used without "Response".


    # 8 business hours
    'simple' => { Resolve => 60*8 },
    # one real week
    'hard' => { Resolve => { RealMinutes => 60*24*7 } },


In many companies providing support service(s) resolve time of a ticket is less important than time of response to requestors from stuff members.

You can use Response option to define such deadlines. When you're using this option Due time "flips" when requestors and non-requestors reply to a ticket. We set Due date when a ticket is created, unset when non-requestor replies... until ticket is closed when ticket's Due date is also unset.

NOTE that behaviour changes when Resolve and Response options are combined, read below.

As response deadlines are calculated using requestors' activity so several rules applies to make things sane:

  • If requestor(s) reply multiple times and are ignored then the deadline is calculated using the oldest requestors' correspondence.

  • If a ticket has no requestor(s) then it has no response deadline.

  • If a ticket is created by non-requestor then due date is left unset.

  • If owner of a ticket is its requestor then his actions are treated as non-requestors'.

Using both Resolve and Response in the same level

Resolve and Response can be combined. In such case due date is set according to the earliest of two deadlines and never is dropped to 'not set'.

If a ticket met its Resolve deadline then due date stops "flipping", is freezed and the ticket becomes overdue. Before that moment when non-requestor replies to a ticket, due date is changed to Resolve deadline instead of 'Not Set', as well this happens when a ticket is closed. So all the time due date is defined.


    'standard delivery' => {
        Response => { RealMinutes => 60*1  }, # one hour
        Resolve  => { RealMinutes => 60*24 }, # 24 real hours

A client orders goods and due date of the order is set to the next one hour, you have this hour to process the order and write a reply. As soon as goods are delivered you resolve tickets and usually meet Resolve deadline, but if you don't resolve or user replies then most probably there are problems with delivery of the goods. And if after a week you keep replying to the client and always meeting one hour response deadline that doesn't mean the ticket is not over due. Due date was frozen 24 hours after creation of the order.

Using business and real time in one option

It's quite rare situation when people need it, but we've decided that business is applied first and then real time when deadline described using both types of time. For example:

    'delivery' => {
        Resolve => { BusinessMinutes => 0, RealMinutes => 60*8 },
    'fast delivery' {
        StartImmediately => 1,
        Resolve => { RealMinutes => 60*8 },

For delivery requests which come into the system during business hours these levels define the same deadlines, otherwise the first level set deadline to 8 real hours starting from the next business day, when tickets with the second level should be resolved in the next 8 hours after creation.

Keep in loop (interval, no defaults)

If response deadline is used then Due date is changed to repsonse deadline or to "Not Set" when staff replies to a ticket. In some cases you want to keep requestors in loop and keed them up to date every few hours. KeepInLoop option can be used to achieve this.

    'incident' => {
        Response   => { RealMinutes => 60*1  }, # one hour
        KeepInLoop => { RealMinutes => 60*2 }, # two hours
        Resolve    => { RealMinutes => 60*24 }, # 24 real hours

In the above example Due is set to one hour after creation, reply of a non-requestor moves Due date two hours forward, requestors' replies move Due date to one hour and resolve deadine is 24 hours.

OutOfHours (struct, no default)

Out of hours modifier. Adds more real or business minutes to resolve and/or reply options if event happens out of business hours, read also </"Configuring business hours"> below.


    'level x' => {
        OutOfHours => { Resolve => { RealMinutes => +60*24 } },
        Resolve    => { RealMinutes => 60*24 },

If a request comes into the system during night then supporters have two hours, otherwise only one.

    'level x' => {
        OutOfHours => { Response => { BusinessMinutes => +60*2 } },
        Resolve    => { BusinessMinutes => 60 },

Supporters have two additional hours in the morning to deal with bunch of requests that came into the system during the last night.

Configuring business hours

In the config you can set one or more work schedules. Use the following format:

    Set( %ServiceBusinessHours,
        'Default' => {
            ... description ...
        'Support' => {
            ... description ...
        'Sales' => {
            ... description ...

Read more about how to describe a schedule in Business::Hours.

Defining different business hours for service levels

Each level supports BusinessHours option to specify your own business hours.

    'level x' => {
        BusinessHours => 'work just in Monday',
        Resolve    => { BusinessMinutes => 60 },

then %RT::ServiceBusinessHours should have the corresponding definition:

    Set( %ServiceBusinessHours,
        'work just in Monday' => {
            1 => { Name => 'Monday', Start => '9:00', End => '18:00' },

Default Business Hours setting is in $RT::ServiceBusinessHours{'Default'}.

Defining service levels per queue

In the config you can set per queue defaults, using:

    Set( %ServiceAgreements,
        Default => 'global default level of service',
        QueueDefault => {
            'queue name' => 'default value for this queue',

Access control

You can totally hide SLA custom field from users and use per queue defaults, just revoke SeeCustomField and ModifyCustomField.

If you want people to see the current service level ticket is assigned to then grant SeeCustomField right.

You may want to allow customers or managers to escalate thier tickets. Just grant them ModifyCustomField right.


    * [implemented, TODO: tests for options in the config] default SLA for queues

    * [implemented, TODO: tests] add support for multiple b-hours definitions,
      this could be very helpfull when you have 24/7 mixed with 8/5 and/or
      something like 8/5+4/2 for different tickets(by requestor, queue or
      something else). So people would be able to handle tickets in the right
      order using Due dates.

    * [not implemented] WebUI



Actions are subclasses of RT::Action::SLA class that is subclass of RT::Extension::SLA and RT::Action::Generic classes.

Conditions are subclasses of RT::Condition::SLA class that is subclass of RT::Extension::SLA and RT::Condition::Generic classes.

RT::Extension::SLA is a base class for all classes in the extension, it provides access to config, generates Business::Hours objects, and other things useful for whole extension. As this class is the base for all actions and conditions then we MUST avoid adding methods which overload methods in 'RT::{Condition,Action}::Generic' RT's modules.


If you run make initdb more than once you will create multiple SLA CFs. You can remove these via RT's Configuration->Global menu, (both Custom Fields and Scrips).


Ruslan Zakirov <>


This extension is Copyright (C) 2007-2009 Best Practical Solutions, LLC.

It is freely redistributable under the terms of version 2 of the GNU GPL.