From Request Tracker Wiki
Jump to navigation Jump to search


This RT Scrip is a modified version of AddWatchersOnCorrespond - AddWatchersOnCorrespond simply adds all recipients as watchers.

This Scrip is the same, except it is more restrictive with the requestors added:

  • AddWatchersOnCorrespondDomains will add the person making the correspondence as a Watcher to the ticket if they are not already a Watcher.

If the transaction originated from an email message, the scrip will scan the email headers and add other recipients to the ticket as Watchers, only if:

  • They are not already a Watcher, and:
  • Their email address has the same domain (or from a subdomain) as another watcher already on the ticket.
  • e.g. New To: and Cc: recipients added by the customer will only be added as watchers if they are from the same domain as a requestor already on the ticket.
  • (Third parties not already on the ticket will not be added.)
  • We prefer to add as Requestors rather than Cc to simplify ticket updates. (All "Replies to Requestors" always go to everyone on the ticket.) If you do not want this behaviour, change the line: my $type = 'Requestor'; to my $type = 'Cc';
  • This accommodates the majority of our use cases:
  • Internal staff like to follow-up via email, but may not be aware that their replies will not be seen by someone the customer added as a Cc:
  • After this executes, We also use RT-Extension-NonWatcherRecipients to add a warning into the Admins email notification of any recipients on the email that are not on the ticket.
  • In our RT setup, we have a group named Staff which contains all admins for our site. If the user this Scrip is going to add as a Watcher to the ticket is also a member of the Staff group, then the Scrip will add them as an AdminCC Watcher instead of a CC or Requestor Watcher. It should be fairly easy for others who do not need this feature to remove or modify this Scrip accordingly.
  • NOTE: If you remove a watcher from a ticket, and a customer replies again with them in a Cc: header, this scrip will probably add them back again. To avoid this, keep them as a watcher, but remove them as an email recipient (Under People -> Modify who receives mail for ticket). They will not receive further email from the ticket. (This is known as a "Squelched" watcher in RT.)


  • 2022-12-07: (RobL) Created. Update to always populate Real Name field. See this forum post.
  • 2023-05-03: (RobL) BUGFIX: add CreatorEmailAddr as lower case. Check for bounce addresses lower case.


  1. Add never_auth list: Exclude certain domains where it is not safe to assume "same domain = same organisation" e.g.,, hotmail etc, and where multiple organisations use the same domain but are not related. (e.g., due to mergers/acquisitions, two organisations use the same domain and brand, but are not related, or where a customer for our service also happens to be a supplier for our other customers, so their domain will appear frequently in unrelated tickets.)
  2. Allow for equivalent domains: Ability to specify for example, is the same as
  3. Use RT groups to relate users from customers: Create a group for each customer, and add all users for that customer to the group. Then check all email addresses/domains of all existing users in the group. (e.g., is already a Requestor, and Cc:'s If is already a user in RT and is a member of the same customer group as then add them as a watcher.

Description: AddWatchersOnCorrespondDomains

Condition: On Correspond

Action: User Defined

Template: Global template: Blank

Stage: Normal

Custom condition:

Custom action preparation code: return 1;

Custom action cleanup code:

# Scrip to automatically add Cc: from incoming emails to tickets, if the following conditions apply:
# 1. Sender is sending from a domain that is already a watcher on the ticket (Requestor/Cc/AdminCc)
# 2. New Cc is from the same domain, or a subdomain.
# This Scrip is based on AddWatchersOnCorrespond
# But does not add just any cc:
# Example: From: Fred Bloggs <>
#          Cc:   Mary Jane <>
# - If (or any or * address is already a watcher on the ticket,
#   then will be automatically added as a Cc watcher.
# Example: From: Fred Bloggs <>
#          Cc:   Random Helpdesk <>
# - will NOT be added as a Cc: to the ticket if "" is not already
#   a watcher on the ticket.

my %People;

# Get some info:
my $scrip = 'Scrip:AddWatchersOnCorrespondDomains';
my $Transaction = $self->TransactionObj;
my $Queue = $self->TicketObj->QueueObj;
my $Ticket = $self->TicketObj;
my $Id = $self->TicketObj->id;

# Load a list of all domains of people on the ticket already:
my @TicketDomains = GetTicketDomains();

# Extract a list of people associated with this transaction:
#  - including the transaction creator, and if it is an email, the sender and recipients of that email

my $CreatorEmailAddr = lc $self->TransactionObj->CreatorObj->EmailAddress;
my $CreatorRealName  = $self->TransactionObj->CreatorObj->RealName;
$People{$CreatorEmailAddr}{RealName} = GetFullName($CreatorEmailAddr,$CreatorRealName);

foreach my $h (qw(From To Cc)) {
    my $header = $Transaction->Attachments->First->GetHeader($h);
    my @addr = Mail::Address->parse($header);
    foreach my $addrobj (@addr) {
        my $addr = lc $RT::Nobody->UserObj->CanonicalizeEmailAddress($addrobj->address);
        # Ignore the specific addresses for this queue:
        next if lc $Queue->CorrespondAddress eq $addr;
        next if lc $Queue->CommentAddress eq $addr;
        # Ignore any email address that looks like one for ANY of our queues:
        next if RT::EmailParser->IsRTAddress($addr);
        my $fullname = GetFullName($addr,$addrobj->phrase);
        $People{$addr}{RealName} = $fullname;
        $RT::Logger->debug("$scrip: Ticket #$Id correspondence contains header - $h: $addr $fullname");

# Lookup the 'experts' (staff) group to use below:
my $Experts = RT::Group->new($self->CurrentUser);

# Now check if each user is already watching the ticket or queue:

foreach my $addr (sort keys %People) {

    next if ($addr =~ /^(postmaster|root|mailer-daemon)\@/);

    my $User = RT::User->new( $RT::SystemUser );
        RealName     => $People{$addr}{RealName},
        EmailAddress => $addr,
        Comments     => "Autocreated by $scrip",
    my $Name = $User->Name;
    my $Principal = $User->PrincipalId;
    if ( not ($Queue->IsWatcher(Type => 'Cc', PrincipalId => $Principal) or
            $Queue->IsWatcher(Type => 'AdminCc', PrincipalId => $Principal) or
            $Ticket->IsWatcher(Type => 'Cc', PrincipalId => $Principal) or
            $Ticket->IsWatcher(Type => 'AdminCc', PrincipalId => $Principal) or
            $Ticket->IsWatcher(Type => 'Requestor', PrincipalId => $Principal) or
            $Ticket->IsOwner($User) )) {
     # check if the new cc: person's domain matches a domain already on the ticket.
     # If so, add them as a new watcher:
     if (is_address_authdomain($addr,@TicketDomains)) {
        # If the user is a member of the experts group, then add them as an AdminCc, otherwise as a Cc:
        # my $type = 'Cc';
        my $type = 'Requestor';
        $type = 'AdminCc' if $Experts->HasMember($User->PrincipalObj);
        # Add the new watcher now and check for errors:
        my ($ret, $msg) = $Ticket->AddWatcher(Type  => $type, PrincipalId => $Principal);
        if ($ret) {
            $RT::Logger->info("$scrip: New $type watcher added to ticket #$Id: $addr (#$Principal)");
        } else {
            $RT::Logger->error("$scrip: Failed to add new $type watcher to ticket #$Id: $addr (#$Principal) - $msg");


sub GetFullName {

    # Get a nicely formatted name for RT RealName Field:

    # If fullname is blank, make something up from the
    # local_part of the email address: "fred.bloggs@..." -> "Fred Bloggs".
    # Do not allow fullname to contain "@".

    my ($addr,$fullname) = @_;
    my ($local_part,$domain) = split('@', $addr);

     if (($fullname eq '') || ($fullname =~ /\@/)) {
      $fullname = $local_part;
      $fullname =~ s/[\._-]/ /g;
      $fullname =~ s/(\w+)/\u$1/g;

     $fullname =~ s/^[\"\']|[\"\']$//g; # strip leading/trailing " or '
     $fullname =~ s/^\s+|\s+$//g; # strip leading/trailing spaces

    return $fullname;

sub is_address_authdomain {

        # Is address in an authorised domain?
        # Also allow subdomain of existing domain

        my $addr = shift;
        my @domains = @_;
        $addr = lc($addr);

        my ($undef,$domain) = split('@',$addr);
        my $is_auth = 0;

        foreach my $authdomain (@domains) {

           # if ($domain eq $authdomain)      { $is_auth=1; last; };
           if ($domain =~ /(^|(\.?))$authdomain$/) { $is_auth=1; last; };
           if ($authdomain =~ /(^|(\.?))$domain$/) { $is_auth=1; last; };




sub GetTicketDomains {

    # my $self = shift;
    # Get list of existing email addresses from the ticket, and push all the domains.
    # We'll check this later. Any cc: by an existing requestor will be allowed from 
    # the same domain or a subdomain.

    # For each Role, get RoleAddresses: "," 
    # Return a deduped domain list.

    my %allticketdomains;

     foreach my $role (qw(Requestor Cc AdminCc)) {

        my $roleaddresses = lc($self->TicketObj->RoleAddresses($role));
               foreach my $a (split(', ', $roleaddresses)) {
                 my (undef,$domain) = split('@',$a);
                 next if ($allticketdomains{$domain});
                 $allticketdomains{$domain} = $domain;

      my @ticketdomains = sort keys %allticketdomains;

      return (@ticketdomains);


return 1;

# vim:ft=perl: