<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://rt-wiki.bestpractical.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Robl</id>
	<title>Request Tracker Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://rt-wiki.bestpractical.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Robl"/>
	<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/wiki/Special:Contributions/Robl"/>
	<updated>2026-08-21T19:01:23Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.41.1</generator>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=RestrictAttachments&amp;diff=27253</id>
		<title>RestrictAttachments</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=RestrictAttachments&amp;diff=27253"/>
		<updated>2025-12-18T12:26:06Z</updated>

		<summary type="html">&lt;p&gt;Robl: Add DisableAttachments option&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Restricting Attachments in RT =&lt;br /&gt;
&lt;br /&gt;
== Existing Attachment Restrictions ==&lt;br /&gt;
&lt;br /&gt;
RT has existing config options to set a maximum file size for attachments, and if files exceeding this size limit should be truncated or dropped. &lt;br /&gt;
&lt;br /&gt;
See config options:&lt;br /&gt;
&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#MaxAttachmentSize MaxAttachmentSize]&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#TruncateLongAttachments TruncateLongAttachments]&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#DropLongAttachments DropLongAttachments]&lt;br /&gt;
&lt;br /&gt;
== Restricting attachment types ==&lt;br /&gt;
&lt;br /&gt;
RT (as of 6.0.2) does not currently have a way to restrict attachments only to specific types, and there is no easy way to disable all attachments.&lt;br /&gt;
&lt;br /&gt;
There are two methods to customise RT to restrict file attachments, depending on where you need to restrict:&lt;br /&gt;
&lt;br /&gt;
# Restrict which file types can be uploaded via the web interface (When users upload attachments)&lt;br /&gt;
# Add a back-end overlay which restricts which attachment types will be stored globally, including via email, or any transaction involving attachments.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1. Restricting via RT Web Interface ==&lt;br /&gt;
&lt;br /&gt;
RT uses the JavaScript library [https://docs.dropzone.dev/ Dropzone] in the web UI to handle file uploads.&lt;br /&gt;
&lt;br /&gt;
Dropzone has an option &#039;&#039;&#039;acceptedFiles&#039;&#039;&#039; to limit the file type and/or extensions accepted.&lt;br /&gt;
&lt;br /&gt;
This checks the file&#039;s MIME type or extension against this list. This is a &#039;&#039;&#039;comma separated list&#039;&#039;&#039; of MIME types or file extensions, e.g., The following would allow all image files, any file with MIME type application/pdf, and any file with extension .csv. All other file types will be rejected:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
acceptedFiles: image/*,application/pdf,.csv&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing in RT (6.0.2 and possibly older) ===&lt;br /&gt;
&lt;br /&gt;
* Install local modified version of [https://gist.github.com/listerr/b274f2ae0529ffeb337e5a1c0c6ce017 AddAttachments] Mason template. From your RT directory, e.g. (/opt/rt6)&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # cd /opt/rt6&lt;br /&gt;
 # mkdir -p local/html/Ticket/Elements&lt;br /&gt;
 # cd local/html/Ticket/Elements&lt;br /&gt;
 # wget https://gist.githubusercontent.com/listerr/b274f2ae0529ffeb337e5a1c0c6ce017/raw/3721f96ae4d9ff36777c4e7fb0cf6a9a616ffbb3/AddAttachments&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add the following new RT config option to your &#039;&#039;&#039;RT_SiteConfig.d&#039;&#039;&#039; files, e.g.: &amp;lt;code&amp;gt;/opt/rt6/etc/RT_SiteConfig.d/20-RT_AttachmentRestrict.pm&amp;lt;/code&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 Set($AcceptedFiles, &#039;image/*,application/pdf,.csv&#039;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Clear the Mason cache&#039;&#039;&#039; and restart everything. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE: This will vary depending on your installation of RT and OS/Distro&#039;&#039;&#039;, e.g. if you installed RT from source or using a package manager. Mine (on nginx and systemd) looks something like this:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # service nginx stop &lt;br /&gt;
 # systemctl stop rt-server.socket &lt;br /&gt;
 # systemctl stop rt-server.service &lt;br /&gt;
 # rm -rf /opt/rt6/var/mason_data/obj &lt;br /&gt;
 # systemctl start rt-server.socket&lt;br /&gt;
 # systemctl start rt-server.service&lt;br /&gt;
 # service nginx start&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One-line version:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# service nginx stop &amp;amp;&amp;amp; systemctl stop rt-server.socket &amp;amp;&amp;amp; systemctl stop rt-server.service &amp;amp;&amp;amp; rm -rf /opt/rt6/var/mason_data/obj &amp;amp;&amp;amp; systemctl start rt-server.socket &amp;amp;&amp;amp; systemctl start rt-server.service &amp;amp;&amp;amp; service nginx start&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTES&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* This method will not restrict attachments added outside the web interface, or not using Dropzone.&lt;br /&gt;
&lt;br /&gt;
* RT has a config option, &amp;lt;code&amp;gt;$PreferDropzone&amp;lt;/code&amp;gt; which is enabled by default. However, users can change this option in their preferences to disable Dropzone. &#039;&#039;&#039;This modified AddAttachments ignores the user preference setting&#039;&#039;&#039; to prevent users bypassing attachment restrictions by disabling Dropzone.&lt;br /&gt;
&lt;br /&gt;
* You may need to check this template when upgrading to new releases/updates of RT. The installed version is in &amp;lt;code&amp;gt;share/html/Ticket/Elements/AddAttachments&amp;lt;/code&amp;gt; by default.&lt;br /&gt;
&lt;br /&gt;
* This may be a little inflexible because it applies globally for all attachment uploads. It would need further modifications, for example, to apply only to some queues but not others, or to allow different restrictions depending on user or queue.&lt;br /&gt;
&lt;br /&gt;
== 2. Restricting via RT backend ==&lt;br /&gt;
&lt;br /&gt;
Alternative method is to restrict attachments when they are processed by the RT backend. This will restrict all attachments received via email and the web interface.&lt;br /&gt;
&lt;br /&gt;
This overrides the method &amp;lt;code&amp;gt;RT::Record::_EncodeLOB&amp;lt;/code&amp;gt; to reject attachments using the same method as the existing &amp;lt;code&amp;gt;$MaxAttachmentSize&amp;lt;/code&amp;gt; option.&lt;br /&gt;
&lt;br /&gt;
=== Implementing in RT (6.0.2 and possibly older) ===&lt;br /&gt;
&lt;br /&gt;
* Install local modified version of [https://gist.github.com/listerr/4e1d697884528a30b2bea2b6e531199f Record_Local.pm]. From your RT directory, e.g. (/opt/rt6)&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # cd /opt/rt6&lt;br /&gt;
 # mkdir -p local/lib/RT&lt;br /&gt;
 # cd local/lib/RT&lt;br /&gt;
 # wget https://gist.githubusercontent.com/listerr/4e1d697884528a30b2bea2b6e531199f/raw/d7c1f5fc74c3ef0c54c6b4d4d42110f962ee34cc/Record_Local.pm&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add the following new RT config option(s) to your &#039;&#039;&#039;RT_SiteConfig.d&#039;&#039;&#039; files, e.g.: &amp;lt;code&amp;gt;/opt/rt6/etc/RT_SiteConfig.d/20-RT_AttachmentRestrict.pm&amp;lt;/code&amp;gt;. You can use one or both of these options. MIME type is usually more flexible than listing lots of different file extensions.&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# Regexp to restrict by MIME Type:&lt;br /&gt;
Set($AttachmentTypeAllowRegexp,qr{^(application/pdf|image/jpeg|image/pjpeg)$});&lt;br /&gt;
 &lt;br /&gt;
# Regexp to restrict on filename/extension:&lt;br /&gt;
Set($AttachmentNameAllowRegexp,qr{\.(doc|png)$});&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Restart RT.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTES&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* This method will still allow files to be uploaded via the Web interface - attachments are only rejected once RT tries to add them to a ticket. This means a correspondence will still be sent, but without the file attachment. A warning will appear on the ticket to show the attachment was rejected.&lt;br /&gt;
&lt;br /&gt;
* Deleting file attachments involves changing the message structure (e.g. unpacking the entire message, removing the unwanted part(s) and creating a new message with the correct MIME headers etc.)  This is quite a complex process. For this reason, RT does not remove the attachment, but instead replaces it with a .txt part with the message &amp;lt;code&amp;gt;attachment type .... dropped - file type not permitted&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 3. Disable file attachments in web frontend ==&lt;br /&gt;
&lt;br /&gt;
To completely disable file attachments in the web frontend: &lt;br /&gt;
&lt;br /&gt;
* Follow the instructions above for &#039;&#039;&#039;Restricting via RT Web Interface&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
* Add the following new RT config option to your &#039;&#039;&#039;RT_SiteConfig.d&#039;&#039;&#039; files, e.g.: &amp;lt;code&amp;gt;/opt/rt6/etc/RT_SiteConfig.d/20-RT_AttachmentRestrict.pm&amp;lt;/code&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 Set($DisableAttachments, 1);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Clear the Mason cache&#039;&#039;&#039; and restart everything. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Notes/TODO ==&lt;br /&gt;
&lt;br /&gt;
* Possibly package this as an Extension if it proves useful.&lt;br /&gt;
* More examples of regexp matches&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=RestrictAttachments&amp;diff=27252</id>
		<title>RestrictAttachments</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=RestrictAttachments&amp;diff=27252"/>
		<updated>2025-12-18T11:58:56Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Implementing in RT (6.0.2 and possibly older) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Restricting Attachments in RT =&lt;br /&gt;
&lt;br /&gt;
== Existing Attachment Restrictions ==&lt;br /&gt;
&lt;br /&gt;
RT has existing config options to set a maximum file size for attachments, and if files exceeding this size limit should be truncated or dropped. &lt;br /&gt;
&lt;br /&gt;
See config options:&lt;br /&gt;
&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#MaxAttachmentSize MaxAttachmentSize]&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#TruncateLongAttachments TruncateLongAttachments]&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#DropLongAttachments DropLongAttachments]&lt;br /&gt;
&lt;br /&gt;
== Restricting attachment types ==&lt;br /&gt;
&lt;br /&gt;
RT (as of 6.0.2) does not currently have a way to restrict attachments only to specific types, and there is no easy way to disable all attachments.&lt;br /&gt;
&lt;br /&gt;
There are two methods to customise RT to restrict file attachments, depending on where you need to restrict:&lt;br /&gt;
&lt;br /&gt;
# Restrict which file types can be uploaded via the web interface (When users upload attachments)&lt;br /&gt;
# Add a back-end overlay which restricts which attachment types will be stored globally, including via email, or any transaction involving attachments.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1. Restricting via RT Web Interface ==&lt;br /&gt;
&lt;br /&gt;
RT uses the JavaScript library [https://docs.dropzone.dev/ Dropzone] in the web UI to handle file uploads.&lt;br /&gt;
&lt;br /&gt;
Dropzone has an option &#039;&#039;&#039;acceptedFiles&#039;&#039;&#039; to limit the file type and/or extensions accepted.&lt;br /&gt;
&lt;br /&gt;
This checks the file&#039;s MIME type or extension against this list. This is a &#039;&#039;&#039;comma separated list&#039;&#039;&#039; of MIME types or file extensions, e.g., The following would allow all image files, any file with MIME type application/pdf, and any file with extension .csv. All other file types will be rejected:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
acceptedFiles: image/*,application/pdf,.csv&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing in RT (6.0.2 and possibly older) ===&lt;br /&gt;
&lt;br /&gt;
* Install local modified version of [https://gist.github.com/listerr/4445b14adea8f801970ce7daf7d9613e AddAttachments] Mason template. From your RT directory, e.g. (/opt/rt6)&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # cd /opt/rt6&lt;br /&gt;
 # mkdir -p local/html/Ticket/Elements&lt;br /&gt;
 # cd local/html/Ticket/Elements&lt;br /&gt;
 # wget https://gist.githubusercontent.com/listerr/4445b14adea8f801970ce7daf7d9613e/raw/83b93f6f3d410432b5d354a487278bea34b1e6d5/AddAttachments&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add the following new RT config option to your &#039;&#039;&#039;RT_SiteConfig.d&#039;&#039;&#039; files, e.g.: &amp;lt;code&amp;gt;/opt/rt6/etc/RT_SiteConfig.d/20-RT_AttachmentRestrict.pm&amp;lt;/code&amp;gt; :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 Set($AcceptedFiles, &#039;image/*,application/pdf,.csv&#039;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Clear the Mason cache&#039;&#039;&#039; and restart everything. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE: This will vary depending on your installation of RT and OS/Distro&#039;&#039;&#039;, e.g. if you installed RT from source or using a package manager. Mine (on nginx and systemd) looks something like this:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # service nginx stop &lt;br /&gt;
 # systemctl stop rt-server.socket &lt;br /&gt;
 # systemctl stop rt-server.service &lt;br /&gt;
 # rm -rf /opt/rt6/var/mason_data/obj &lt;br /&gt;
 # systemctl start rt-server.socket&lt;br /&gt;
 # systemctl start rt-server.service&lt;br /&gt;
 # service nginx start&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One-line version:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# service nginx stop &amp;amp;&amp;amp; systemctl stop rt-server.socket &amp;amp;&amp;amp; systemctl stop rt-server.service &amp;amp;&amp;amp; rm -rf /opt/rt6/var/mason_data/obj &amp;amp;&amp;amp; systemctl start rt-server.socket &amp;amp;&amp;amp; systemctl start rt-server.service &amp;amp;&amp;amp; service nginx start&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTES&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* This method will not restrict attachments added outside the web interface, or not using Dropzone.&lt;br /&gt;
&lt;br /&gt;
* RT has a config option, &amp;lt;code&amp;gt;$PreferDropzone&amp;lt;/code&amp;gt; which is enabled by default. However, users can change this option in their preferences to disable Dropzone. &#039;&#039;&#039;This modified AddAttachments ignores the user preference setting&#039;&#039;&#039; to prevent users bypassing attachment restrictions by disabling Dropzone.&lt;br /&gt;
&lt;br /&gt;
* You may need to check this template when upgrading to new releases/updates of RT. The installed version is in &amp;lt;code&amp;gt;share/html/Ticket/Elements/AddAttachments&amp;lt;/code&amp;gt; by default.&lt;br /&gt;
&lt;br /&gt;
* This may be a little inflexible because it applies globally for all attachment uploads. It would need further modifications, for example, to apply only to some queues but not others, or to allow different restrictions depending on user or queue.&lt;br /&gt;
&lt;br /&gt;
== 2. Restricting via RT backend ==&lt;br /&gt;
&lt;br /&gt;
Alternative method is to restrict attachments when they are processed by the RT backend. This will restrict all attachments received via email and the web interface.&lt;br /&gt;
&lt;br /&gt;
This overrides the method &amp;lt;code&amp;gt;RT::Record::_EncodeLOB&amp;lt;/code&amp;gt; to reject attachments using the same method as the existing &amp;lt;code&amp;gt;$MaxAttachmentSize&amp;lt;/code&amp;gt; option.&lt;br /&gt;
&lt;br /&gt;
=== Implementing in RT (6.0.2 and possibly older) ===&lt;br /&gt;
&lt;br /&gt;
* Install local modified version of [https://gist.github.com/listerr/4e1d697884528a30b2bea2b6e531199f Record_Local.pm]. From your RT directory, e.g. (/opt/rt6)&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # cd /opt/rt6&lt;br /&gt;
 # mkdir -p local/lib/RT&lt;br /&gt;
 # cd local/lib/RT&lt;br /&gt;
 # wget https://gist.githubusercontent.com/listerr/4e1d697884528a30b2bea2b6e531199f/raw/d7c1f5fc74c3ef0c54c6b4d4d42110f962ee34cc/Record_Local.pm&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add the following new RT config option(s) to your &#039;&#039;&#039;RT_SiteConfig.d&#039;&#039;&#039; files, e.g.: &amp;lt;code&amp;gt;/opt/rt6/etc/RT_SiteConfig.d/20-RT_AttachmentRestrict.pm&amp;lt;/code&amp;gt;. You can use one or both of these options. MIME type is usually more flexible than listing lots of different file extensions.&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# Regexp to restrict by MIME Type:&lt;br /&gt;
Set($AttachmentTypeAllowRegexp,qr{^(application/pdf|image/jpeg|image/pjpeg)$});&lt;br /&gt;
 &lt;br /&gt;
# Regexp to restrict on filename/extension:&lt;br /&gt;
Set($AttachmentNameAllowRegexp,qr{\.(doc|png)$});&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Restart RT.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTES&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* This method will still allow files to be uploaded via the Web interface - attachments are only rejected once RT tries to add them to a ticket. This means a correspondence will still be sent, but without the file attachment. A warning will appear on the ticket to show the attachment was rejected.&lt;br /&gt;
&lt;br /&gt;
* Deleting file attachments involves changing the message structure (e.g. unpacking the entire message, removing the unwanted part(s) and creating a new message with the correct MIME headers etc.)  This is quite a complex process. For this reason, RT does not remove the attachment, but instead replaces it with a .txt part with the message &amp;lt;code&amp;gt;attachment type .... dropped - file type not permitted&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Notes/TODO ==&lt;br /&gt;
&lt;br /&gt;
* Possibly package this as an Extension if it proves useful.&lt;br /&gt;
* More examples of regexp matches&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=Contributions&amp;diff=27251</id>
		<title>Contributions</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=Contributions&amp;diff=27251"/>
		<updated>2025-12-18T11:51:48Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Callbacks */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Contributions to RT =&lt;br /&gt;
&lt;br /&gt;
Contains info about software that third parties have contributed to RT. If you know of a contribution or enhancement that is not in this list, please add to the best section possible below, in alphabetical order. Also include notes about the RT versions tested.&lt;br /&gt;
&lt;br /&gt;
You may also want to check the [[Documentation]] page because many articles there describe how to add features that RT does not have by default.&lt;br /&gt;
&lt;br /&gt;
Packages that have their own installer have been moved to [[Extensions]].&lt;br /&gt;
&lt;br /&gt;
See Also the outdated [[Patches]], whose content should be moved to the appropriate subsections below.&lt;br /&gt;
&lt;br /&gt;
== Coping with Spam ==&lt;br /&gt;
&lt;br /&gt;
The techniques for dealing with spam span the divisions below and have been collected in a single place for easy review. Please see [[SpamFiltering]], limiting additions to tricks that differ significantly from the existing material for conciseness.&lt;br /&gt;
&lt;br /&gt;
== ScripConditions ==&lt;br /&gt;
&lt;br /&gt;
Custom [[Condition]]s. Doesn&#039;t matter if it&#039;s module or text to fill into &amp;quot;user defined condition&amp;quot; block in the WebUI, all live here.&lt;br /&gt;
&lt;br /&gt;
*Please*, start wiki page names with &amp;quot;On&amp;quot; prefix if you don&#039;t want to add condition into [[CustomConditionSnippets]].&lt;br /&gt;
&lt;br /&gt;
* [[CustomConditionSnippets]] - &#039;&#039;&#039;very big&#039;&#039;&#039; and organized list of simple conditions&lt;br /&gt;
&lt;br /&gt;
* [[AnyTransactionSource]];&lt;br /&gt;
* [[AnyReminderTransaction]] - a scrip condition which triggers on all reminder transactions;&lt;br /&gt;
* [[MuteResolve]] - let resolver choose not to send email on resolve;&lt;br /&gt;
* [[MuteResolve Redux]] - alternate version of MuteResolve that uses a status code and custom field to avoid some problems;&lt;br /&gt;
* [[NotResolved]] - a scrip condition to detect all ticket that aren&#039;t marked resolved;&lt;br /&gt;
* [[On Correspond Notify AdminCcs if Not Owned|OnCorrespondNotifyAdminCcsNotOwned]] - Notifies AdminCc only if ticket is unowned&lt;br /&gt;
* [[OnCreateAutoReplyException]] - a scrip condition that will send [[AutoReply]] emails to users except for those in the execption list;&lt;br /&gt;
* [[OnCreateCheckCF]] - check presence of a mandatory cf on ticket creation;&lt;br /&gt;
* [[OnCreateFromEmail]] - scrip condition that will send [[AutoReply]] emails to users only when a ticket is opened via email;&lt;br /&gt;
* [[OnCreationOfApprovalTicket]] - a scrip condition that checks if you are creating a new approval ticket&lt;br /&gt;
* [[OnCreatePageOffHours]] - scrip condition that will send email via the [[SendEmailAction]] scrip to and external account. This scrip has an additional condition where it is checking if the request is coming from a specific user or system.&lt;br /&gt;
* [[OnCreateSetUserDetails]] - parse vCards for user information.&lt;br /&gt;
* [[OnCustomFieldValueChange]] - this condition matches when [[CustomField]] value is changed;&lt;br /&gt;
* [[OnMaxPriority]] - check if ticket hits maximal priority (used with priority escalation)&lt;br /&gt;
* [[OnMerge]] - a scrip condition that triggers on ticket merges;&lt;br /&gt;
* [[OnResolveOnce]] - a scrip condition that matches when a ticket is resolved but only if the ticket was not resolved before;&lt;br /&gt;
* [[OnStatusChange]] - using RT&#039;s condition &#039;On Resolve&#039; to trigger other conditions&lt;br /&gt;
* [[OnStealEnhanced]] - small enhancement to the [[OnSteal]] condition in &amp;quot;RT Essentials&amp;quot;.&lt;br /&gt;
* [[OnTimeEstimated]] - when the time Time Estimated fields is set&lt;br /&gt;
* [[OnToOrCC]] - when a request is sent to a particular mailbox&lt;br /&gt;
* [[OnWatcherChange]] - a scrip condition to detect when a ticket watcher is added or deleted.&lt;br /&gt;
* [[OnWebCorrespond]] - determines if a reply is from an incoming email message or from the web interface.&lt;br /&gt;
* [[ReplyBasedUponContent]] - a scrip to reply to an email based upon its content&lt;br /&gt;
* [[ReplyToResolved]] - a scrip condition to detect all ticket that are marked resolved;&lt;br /&gt;
* [[ShowDashboardTabs]] - add dashboards to the personal quickbar&lt;br /&gt;
* [[TicketIDMatches]].pm - a condition which triggers only one ticket by id;&lt;br /&gt;
* [[UntouchedInHours]] - scrip condition that checks if a ticket&#039;s LastUpdate is more than the specified number of hours;&lt;br /&gt;
* [[OnCorrespondOpenUnlessResolved]] - condition to stop RT re-opening resolved tickets when the user replies to them by email;&lt;br /&gt;
* [[NoReplyAddress]] - Use a &amp;quot;no reply&amp;quot; RT email address that posts comments as a Reply/Correspondence (so they can see it in web UI), but does not send email to Requestors&lt;br /&gt;
* [[OnSpam]] - a condition that detects a certain number of ticket creation by the same user on the same day, to block spam or email loops&lt;br /&gt;
&lt;br /&gt;
== ScripActions ==&lt;br /&gt;
&lt;br /&gt;
Nice custom [[ScripAction]]s that makes your life easier.&lt;br /&gt;
&lt;br /&gt;
* [[AddAdminCc]] - Add an [[AdminCc]] to tickets for a specific queue, or for queue change.&lt;br /&gt;
* [[AddAdminCcAndChangeQueue]] - Add an [[AdminCc]] to a ticket, and move the ticket to a queue.&lt;br /&gt;
* [[AddSquelchedCc]] - Add group members as Cc to give them access to tickets but without email notifications.&lt;br /&gt;
* [[AddRefersToOnEqualCustomField]] - Create [[RefersTo]] links to tickets with same custom field value&lt;br /&gt;
* [[AddRequestor]] - Allow some accounts to view a ticket without adding by hand some requestors.&lt;br /&gt;
* [[AddWatcherPerTicket]] - Add a watcher to a specific ticket (e.g. when certain conditions are met, such as high urgency)&lt;br /&gt;
* [[AddWatchersOnCorrespond]] - Add Actor &amp;amp;amp; other Cc&#039;d people to ticket on any Correspondence (complement to using [[ParseNewMessageForTicketCcs]] to add people on ticket creation).&lt;br /&gt;
* [[AddWatchersOnCorrespondDomains]] - Modified version of [[AddWatchersOnCorrespond]]. Only adds watchers if they are from the same domain as a watcher already on the ticket.&lt;br /&gt;
* [[LoopIn]] - Similar to [[AddWatchersOnCorrespond]], but with some additional security and rules to prevent random people from getting added to existing tickets.&lt;br /&gt;
* [[AutomaticCustomFieldValue]] - set CF value by requestor&#039;s email address.&lt;br /&gt;
* [[AutoCcOwner]] - Add the owner as an [[AdminCc]]&lt;br /&gt;
* [[AutoCcLastOwner]] - When the owner is changed, automatically add the previous owner to the Cc list.&lt;br /&gt;
* [[AutoChangeQueue]] - Change queue if a specific group member take a ticket&lt;br /&gt;
* [[AutoCloseOnNagiosRecoveryMessages]] - Automatically merges and closes a ticket based on the creation of another ticket, in this case, of a nagios generated RECOVERY e-mail&lt;br /&gt;
* [[AutoSetOwner]] - how to automatically set owner on resolution&lt;br /&gt;
* [[AutoSetOwnerIfAdminCc]] - Automatically set ticket owner to an [[AdminCc]]&lt;br /&gt;
* [[AutoSetOwnerFromCC]] - how to automatically set owner from Cc&lt;br /&gt;
* [[BounceMerge]] - Merge a Mail Bounce into the original Ticket&lt;br /&gt;
* [[CcManagers]] - Add the manager subgroup of the ticket creator as Cc, useful when managing departments as groups (as [[Rights|rights]] suggests).&lt;br /&gt;
* [[CopyContentToCF]] - When called &#039;On Create&#039; will copy &amp;lt;tt&amp;gt;$Transaction-&amp;gt;Content&amp;lt;/tt&amp;gt; to the custom field &amp;lt;tt&amp;gt;Problem&amp;lt;/tt&amp;gt;.&lt;br /&gt;
* [[CreatePriorityBasedOnCustomFieldValues]] - Automatically set the Priority based on Ticket Urgency and Impact.&lt;br /&gt;
* [[DefaultCustomFieldValue]] - set default CF value.&lt;br /&gt;
* [[DivideTicketIntoSubtasks]] - auto-creates new tickets for each subtask in a bulleted list&lt;br /&gt;
* [[DueDateinBusinessHours]] - scrip action that sets short-term ticket due dates to coincide with business hours&lt;br /&gt;
* [[EscalateTicketOnAction]] - increment the priority of a ticket whenever a certain action is taken&lt;br /&gt;
* [[ExtractCustomFieldValues]] - set [[CustomField]] with arbitrary data extracted from a ticket using a simple template&lt;br /&gt;
* [[ForkIntoNewTicket]] - a scrip action to copy response/comment ticket to another ticket&lt;br /&gt;
* [[JumpToFrontPageOnTicketResolve]] - Admonish me if you wish, but on resolve of a ticket this scrip will redirect your browser to a new URL of your choosing.&lt;br /&gt;
* [[NotifyNonRecipients]] - notify recipients unless they were already cc&#039;d on the mail&lt;br /&gt;
* [[OnCreateSetDeptHeadCc]] - On create in the case that [[CustomField]].Department = &#039;Foo&#039; then add group &#039;Head Foo&#039; as a Cc&lt;br /&gt;
* [[OnCreateAddGroupCc]] - On create from Requestor email that matches a regex, add members of arbitrary group to CC list while making sure not to add anyone already associated with the ticket&lt;br /&gt;
* [[OnOwnershipSquelchMailtoQueueWatchers]] - When a ticket&#039;s owner changes from &amp;quot;Nobody&amp;quot; to a regular user, stop sending mail to people who are just Queue Watchers.&lt;br /&gt;
* [[OnQueueChangeFixReminders]] - Tickets lose reminders when they are moved between queues. This scrip fixes that&lt;br /&gt;
* [[OnQueueChangeResetPriorityAndDueDate ]] - Reset priorities and due date when moving a ticket to another queue.&lt;br /&gt;
* [[OpenTicketOnAllMemberResolve]]&lt;br /&gt;
* [[OpenDependantsOnResolve]]&lt;br /&gt;
* [[RemoteControlLimeSurvey2]] - new Version of the old [[RemoteControlLimeSurvey]] - Scrip action to trigger [[LimeSurvey]] to add a token to a given survey.&lt;br /&gt;
* [[ResolveTicket]]&lt;br /&gt;
* [[SendEmailAction]] - sends an alert to someone not specified in the ticket&lt;br /&gt;
* [[SendHTMLEmail]] - modification of RT::Action::[[SendEmail]] for sending mails with Content-Type: text/html&lt;br /&gt;
* [[SendNagiosAlert]] - Send an alert to Nagios if a new or open ticket exists in any defined queues.&lt;br /&gt;
* [[SendAlarmPointEvent]] - Send an event to alarm point to invoke SMS/Voice/E-mail alerts&lt;br /&gt;
* [[SetActiveOnCustomerReply]] - detects if a ticket is set to a certain status and changes that status if someone other than the ticket owner replies.&lt;br /&gt;
* [[SetCorresponderAsCC]] - Add anyone who correspondes on a ticket to CC - for the lazy users.&lt;br /&gt;
* [[SetOwnerAndQueueBySubject]] - Set queue and owner when the subject matches a regex&lt;br /&gt;
* [[SetTicketPropertiesViaMail]] - scrip action that allow you to set status, owner and etc via email&lt;br /&gt;
* [[SetTimeWorkedAutomatically]] - scrip action that updates automatically the Time Worked field on the Ticket&lt;br /&gt;
* [[SpamScore2Priority]] - Expose message spam score as priority for review&lt;br /&gt;
&lt;br /&gt;
== Template parts ==&lt;br /&gt;
&lt;br /&gt;
Code that you can put into your mail [[Template]]s, [[Template]] page has also some code snippets.&lt;br /&gt;
&lt;br /&gt;
* [[AddAttachmentLinksToMail]] - adds links on file attachments that ticket has&lt;br /&gt;
* [[AddCustomFieldsValuesToMail]] - puts all [[CustomField]]s values into mail&lt;br /&gt;
* [[AddCustomFieldstoTemplates]] - extracting just one or more [[CustomField]]s, without recalling the entire set&lt;br /&gt;
* [[AddTicketHistoryToMail]] - complex template that adds ticket&#039;s history&lt;br /&gt;
* [[AddQueueNameToMailHeaders]] - add the relevent queue name to mail sent&lt;br /&gt;
* [[AddLastCommentToMail]]&lt;br /&gt;
* [[AddRichTextEditorToCustomField]] - add CKEditor to a [[CustomField]]s textarea values&lt;br /&gt;
* [[AutoreplyOrCorrespondence]] - if creator is not requestor use Corresondence instead of Autoreply&lt;br /&gt;
* [[EmailGroup]] - email an RT [[Group]]&lt;br /&gt;
* [[ForkTemplate]] - send a range of customized responses without hard-coding variants.&lt;br /&gt;
* [[ForwardFirstMessage]] - re-send the first message (i.e. ticket creation message)&lt;br /&gt;
* [[MailingListIntegration]] - Scrip + Template to optionally subscribe requestors to a listserv.&lt;br /&gt;
* [[MultipleOutgoingEmailAddresses]]&lt;br /&gt;
* [[UseActorAsSender]]&lt;br /&gt;
* [[X-Priority]] - Maps RT priority field to email priority header.&lt;br /&gt;
&lt;br /&gt;
== Callbacks ==&lt;br /&gt;
&lt;br /&gt;
Callbacks are an easy way to [[CleanlyCustomizeRT]]&lt;br /&gt;
&lt;br /&gt;
* [[RestrictAttachments]] - Various ways to restrict attachments by type or filename. &lt;br /&gt;
* [[CloningQueues]] - Add user functionality to clone existing queues including templates, scrips, privileges and custom fields during queue creation&lt;br /&gt;
* [[CreateChildTicket]] - Add a button to the Ticket display to create a child ticket in another queue&lt;br /&gt;
* [[HideTransactions]] - hide messages from a history view&lt;br /&gt;
* [[ModifyQuery]] -Change default simple search behavior to in/ex-clude closed tickets, etc.&lt;br /&gt;
* [[MakeClicky:Fedex]] - Make a link to Fedex tracking website whenever phrase looks like a tracking number&lt;br /&gt;
* [[QuickResolveandQuickReject]] - Create two actions in Display page which allow you to reject or resolve the ticket without no comments.&lt;br /&gt;
* [[MailtoLinksFromTransactions]] - Create mailto-inks at the top of transactions&lt;br /&gt;
* [[TwoColumnTicketLayout]] - Display a ticket&#039;s history and metadata side by side&lt;br /&gt;
* [[AutoRequestorTicketSearch]] - Automatically search for requestor&#039;s last updated tickets on creation page&lt;br /&gt;
* [[NewTicketsAlert]] - Display a messagebox on My RT listing new tickets and add new ticket count to page title&lt;br /&gt;
&lt;br /&gt;
== Extensions ==&lt;br /&gt;
&lt;br /&gt;
Patches, [[Overlays]], Mason components, configuration tools and so on. These extensions are unlikely to become RT core package.&lt;br /&gt;
&lt;br /&gt;
Packages that have their own installer have been moved to [[Extensions]] - Below are code bits too small/experimental/etc. to warrant a standalone package:&lt;br /&gt;
&lt;br /&gt;
* [[AutomaticImageResize]] -- automatically scale images that are displayed inline in your ticket history&lt;br /&gt;
* [[AutoRedirectToSelfService]] - automatically redirect your users to Self Service if they don&#039;t have &amp;quot;own ticket&amp;quot; permissions;&lt;br /&gt;
* [[BasicVsAdvancedInterface]] - allow privileged users to choose between the [[SelfService]] and RT at a Glance interfaces&lt;br /&gt;
* [[BetterPerformanceWithFullText]] - tweak to improve full text search in Postgres (and a note about Oracle).&lt;br /&gt;
* [[CalendarWidget]] - add a the dynarch.com jscalendar widget to pick dates&lt;br /&gt;
* [[CannedReplies]] - provides drop-down list of templates that can be included in a ticket reply&lt;br /&gt;
* [[ColorizedLinks]] - colorizing list of Ticket Links depends on it&#039;s status&lt;br /&gt;
* [[ConvertMultiSelectToCheckboxes]] - converts the multiselect customfield into a checkbox interface&lt;br /&gt;
* [[CreateGroupAndAddMembers]] - an overylay that grants [[AdminGroupMembership]] when a user creates a group&lt;br /&gt;
* [[CustomFieldRightsWithoutSeeQueue]] - for a custom ticket creation form that includes the custom fields and you don&#039;t want to turn on the [[SeeQueue]] right;&lt;br /&gt;
* [[DisplayCustomFieldsInTicketSearch]];&lt;br /&gt;
* [[DisplayCustomFieldsInUserPrefs]] - add user-based custom fields to User/Prefs.html&lt;br /&gt;
* [[DisplayCustomFieldsOnTicketUpdatePage]] - Make a ticket&#039;s custom fields visible when updating or resolving a ticket.&lt;br /&gt;
* [[EditCustomFieldsOnUpdate]] - edit custom fields on update, reply, comment...;&lt;br /&gt;
* [[ForwardWithMessage]] - forward a transaction or ticket WITH a message for the recipient&lt;br /&gt;
* [[GroupMembershipCheck]] - snippet that can be included in a custom form if you want to limit the display of some things to a specific group;&lt;br /&gt;
* [[HideTransactions]] - hide messages from a history view&lt;br /&gt;
* [[HomePageSavedSearches]] - display lists of saved searches on the RT home page;&lt;br /&gt;
* [[HTML5Charts]] - eye candy with jqplot;&lt;br /&gt;
* [[ImportCustomFieldValues]] - Fills custom field data from external source;&lt;br /&gt;
* [[LdapSummary]] - Several authentication and user creation techniques&lt;br /&gt;
* [[MaintenanceMode]] - a quick and dirty way to shut down your site temporarily&lt;br /&gt;
* [[MandatorySubject]] - make a ticket&#039;s Subject mandatory using [[JavaScript]]&lt;br /&gt;
* [[MoreAboutPrivilegedUsers]] - show the More About box for privileged users&lt;br /&gt;
* [[MoveRTName]] - Move the $rtname to the end of a subject line&lt;br /&gt;
* [[MultipleSubjectTokens]] - Change subject token dependend on queue name&lt;br /&gt;
* [[PasswordReset]] - show password reset on login&lt;br /&gt;
* [[PersistentSessions]] - Making users&#039; sessions persistent&lt;br /&gt;
* [[PopUpAlert]] - Send Reply instead of Comment&lt;br /&gt;
* [[QuickTicket]] - quickly create a ticket on homepage with custom fields and status&lt;br /&gt;
* [[Extension - Queue Change On Update]] - This is a plugin which adds a callback to RT with the result that you add a Queue change dropdown box to the ticket update page (comment/reply page). Very handy for proper ticket transport between Queue&#039;s, especially whena Queue represents a department.&lt;br /&gt;
* [[Rich Text Custom Fields]] - convert wiki text custom fields into rich text custom fields&lt;br /&gt;
* [[ResolveSendsReply]] - change the &amp;quot;Resolve&amp;quot; link to reply instead of comment by default&lt;br /&gt;
* [[SelectRequestor]] - allow user to select requestor from drop down lists instead of typing email address;&lt;br /&gt;
* [[SelectDefaultQueue]] - Using a user-based custom field, cause all queue name drop down lists to autopick that queue&lt;br /&gt;
* [[SendEmail]] - lets template send an e-mail without adding RT ticket info to subject line&lt;br /&gt;
* [[ShortcutPopupMenuScript]] - Javascript based popup menu of useful actions for ticket list&lt;br /&gt;
* [[SideBySideTicketScreen]] - ticket update screen that shows ticket history &amp;quot;side-by-side&amp;quot; with ticket details.&lt;br /&gt;
* [[SignatureToTheTop]] - Insert user&#039;s signature to the top of the message, not to the bottom as default&lt;br /&gt;
* [[SimpleSearchExcludeResolved]] - exclude resolved and rejected tickets from simple search results&lt;br /&gt;
* [[ShowStatusInColor]] - show status (or priority) in Color in Search screens;&lt;br /&gt;
* [[ShowPerQueueInstructions]]&lt;br /&gt;
* [[SpawnChildTicket]] - spawn a child ticket in a given queue list;&lt;br /&gt;
* [[SpatialRT]] - Plot tickets on a map. More of a recipe than a full solution but could be expanded.&lt;br /&gt;
* [http://wiki.bestpractical.com/view/Spreadsheet+RequestorDetails Spreadsheet+RequestorDetails] - Creates a Spreadsheet link which includes some requestor details if the user has the rights to see them (Global ACL [[AdminUsers]])&lt;br /&gt;
* [[SpreadsheetDisplayedFields]] - download just the displayed search result fields into a spreadsheet;&lt;br /&gt;
* [[StockAnswers]] - insert predefined templates into replies - now with a template editor;&lt;br /&gt;
* [[SuppressOutgoingMail]] - optionally turn off outgoing mail&lt;br /&gt;
* [[TextBasedPriorities]] - use &amp;lt;code&amp;gt;Normal, High, Emergency...&amp;lt;/code&amp;gt; for priority value instead of numbers;&lt;br /&gt;
* [[TicketsPerQueue]] - Display X unowned tickets per queue a user has rights to&lt;br /&gt;
* [[TimeWorked]] - Display a report with total time worked per queue/per user&lt;br /&gt;
* [[TimeWorkedReport]] - Display a report with total time worked per user per ticket for one or more queues.&lt;br /&gt;
* [[TimelineStartDue]] - Modify Timeline package to display tickets using the Start and Due date&lt;br /&gt;
* [[UpdateTimeLeft]] - update Time Left -field from &amp;lt;code&amp;gt;Update.html&amp;lt;/code&amp;gt; (/reply, comment/)&lt;br /&gt;
* [[ViewMyRequests]] - mod to [[SelfService]] that allows requestors to see ticket summaries, and details of their own tickets;&lt;br /&gt;
* [[WhoHasRightsToWhat]] - a mason component that makes it easier to understand your complex rights setup.&lt;br /&gt;
* [[WatcherSummary]] - a mason component that gives you an overview of tickets where a user is listed as a watcher.&lt;br /&gt;
* [[LockLessSessionsMySQL]] - a new version of Sessions which works in no-locking mode&lt;br /&gt;
&lt;br /&gt;
== External utils ==&lt;br /&gt;
&lt;br /&gt;
Various standalone utilities.&lt;br /&gt;
&lt;br /&gt;
* [[backupRT]] - Run a quick backup of RT files (Debian)&lt;br /&gt;
* [[backupRTDB]] - RT Database Backup Script (Debian)&lt;br /&gt;
* [http://nextup.cz/bestpractical-rt-widget/ BestPracticalRT Mac OSX Widget] - simple configurable OSX widget for quick posting tickets&lt;br /&gt;
* [http://search.cpan.org/dist/Bot-BasicBot-Pluggable-Module-RT Bot::BasicBot::Pluggable::Module::RT] - an IRC Bot module that allows full querying of RT tickets from an IRC session. It can do as much as [http://search.cpan.org/dist/RT-Client-REST/ RT::Client::REST] can.&lt;br /&gt;
* [[CleanupSessions]] - Clean up old database sessions&lt;br /&gt;
* [[CloseAll]] - Close all TT in a queue&lt;br /&gt;
* [[ConvertLegacyToRt]] - Converts a Legacy Flat File to RT Tickets.&lt;br /&gt;
* [[delete-transaction]] - [http://download.bestpractical.com/pub/rt/contrib/3.0/Other/Censorware/delete-transaction Original version] was old, poured out errors and didn;t work well on 3.8. This one is fixed.&lt;br /&gt;
* [[DenormalizedViewsForReporting]] - Several views to allow SQL reporting outside of RT.&lt;br /&gt;
* [https://github.com/botsie/dirt Dirt] - A web application that provides kanban boards, scrum taskboards, graphical and tabluar reports on top of RT. &lt;br /&gt;
* Email reminders&lt;br /&gt;
** [[DueDateRemindersByEmail]] - A script (to be run daily) that sends email notifications for expired tickets to owners and Queues/Tickets [[AdminCC]]&lt;br /&gt;
** [http://www.cs.kent.ac.uk/people/staff/tdb/rt3/ rt-remind] - Stick this in your crontab to send out reminders about open tickets.&lt;br /&gt;
** [[rtReminderMails]] - Cronscript that sends mails about reminders that are due in the next two days to the ticket and reminder owners.&lt;br /&gt;
** [[rtUnifiedreminder]] - All the other reminder scripts are based on [[StartDate]], [[DueDate]] or Priority but not all organizations make use of those fields. Also, all the other scripts only send plaintext email to the ticket owner. This script sends one HTML mail (so you can click the tickets and links to RT searches) that lists all tickets that seem to be getting too old without being touched. &amp;quot;Too old&amp;quot; is based on [[LastUpdated]] field, with the amount of time configurable for New, Open and Stalled tickets.&lt;br /&gt;
** [[rt-askForFeedback]] - This Script bases on the above &amp;quot;[[DueDateRemindersByEmail]]&amp;quot; and got modified in the way, that it sends Mails to customers other than to administrators and Ticket-Owner. You will be able to &amp;quot;remind&amp;quot; customer to get back to you with a reply if the ticket is in &amp;quot;stalled&amp;quot;-State. If there is no response within a time of &amp;quot;x&amp;quot;, you can autoclose the ticket.&lt;br /&gt;
* [http://www.bestpractical.com/pub/rt/contrib/3.0/Other/F2Wcvs-to-rt-3.0 F2Wcvs-to-rt] - Tool to help converting from the [http://f2w.sourceforge.net/ F2W] helpdesk system to RT&lt;br /&gt;
* [http://mit.edu/alexmv/Public/graph-mason-deps graph-mason-deps] uses [http://www.graphviz.org/ GraphViz] to create a graph of which components call each other&lt;br /&gt;
* [[html2mime]] - small perl script used to create a text/plain part from 100% html messages&lt;br /&gt;
* [http://pthbb.org/software/manual/mailfilter mailfilter] - spam checking and more&lt;br /&gt;
* [[Mbox2Rt]] - import a unix-style mailbox into RT&lt;br /&gt;
* [[ProcmailRecipes]] - procmail recipes used for email filtering&lt;br /&gt;
* [http://www.dmo.ca/projects/hacks/RT/RT.bm RT.bm] is a plugin for [http://www.mozilla.org/projects/mozbot/ mozbot] that allows some minimal querying of RT tickets from an IRC session.&lt;br /&gt;
* [http://www.bestpractical.com/pub/rt/contrib/3.0/Other/rtadduser rt-adduser] ([http://www.bestpractical.com/pub/rt/contrib/3.0/Other/rtadduser.README docs]) - command line tool to add RT users.&lt;br /&gt;
* [[Rt-auth-user|rt-auth-user]] - perl script for authenticating a user against RT (both local and external sources through [[ExternalAuth]] )&lt;br /&gt;
* [http://mimosaid.007sites.com/rt-batch-add-users.txt rt-batch-add-users] - command line tool to add a batch of RT users based on data of a csv file.&lt;br /&gt;
* [rt-batch-stats [[RT3BatchStats]]] - Command Line or batch statistics.&lt;br /&gt;
* [[RtBounceHandler]] - scan bounce email for ticket details, then post essense of bounce info to that ticket.&lt;br /&gt;
* [http://www.cpan.org/authors/id/A/AH/AHARRISON/scripts/rt-class-map-1.3.pl rt-class-map-1.3-pl] - Show methods available to specific RT objects.&lt;br /&gt;
* [http://www.bestpractical.com/pub/rt/contrib/3.0/rt-cvsgate.txt rt-cvsgate] ([http://www.bestpractical.com/pub/rt/contrib/3.0/rt-cvsgate.README docs]) - cvs integration for request tracker.&lt;br /&gt;
* [http://www.cs.kent.ac.uk/people/staff/tdb/rt3/ rt-escalate] ([[ConfigureEscalation]]) - stick this in your crontab to escalate priority on tickets automatically&lt;br /&gt;
* [[RTLogins]] - simple php script that creates a login report (&amp;quot;Who&#039;s using RT?&amp;quot;)&lt;br /&gt;
* [http://wiki.bestpractical.com/view/rt_logins_email2ldap rt_logins_email2ldap] - script to convert email usernames to LDAP/Active Directory usernames&lt;br /&gt;
* [[rt-google-charts]] - produce google charts for queue statistics&lt;br /&gt;
* [http://www.bestpractical.com/pub/rt/contrib/3.0/Conversion/rt3-on-pg-to-mysql rt-on-pg-to-mysql] - Convert your rt database from postgres to mysql.&lt;br /&gt;
* [http://shellscripts.org/project/rtqueues rt-queues] Shellscript called from procmail to sort E-Mails to the correct queue. Parses incoming mails and based on addresses in To: and CC: fields automatically sorts mails to the correct queue. This makes changing your MTA configuration for every new queue obsolete.&lt;br /&gt;
* [[RtTalkToSelf]] - a mail filter script that allows a single RT instance to have one ticket as the &amp;quot;external requestor&amp;quot; of another.&lt;br /&gt;
* [[scan-and-set]] - sample perl script to all the text attachments of open tickets for a text string and set a custom field with the result.&lt;br /&gt;
* [http://www.jeconley.com/pub/rt/statdump statdump/statcron] - script and cronjob to generate RT management reports. The original URL is defunct, but a patched version of the scripts are still in [http://www.nabble.com/Patched-statdump-statcron-scripts-td2264154.html listarchives]. An updated version that fixed [[AverageTickets]] calcs and CURDATE selection used to be available at http://www.lei.net.au/stats.tgz.&lt;br /&gt;
* [[CountTickets]] - a BASH script to count tickets in a [[MySQL]] db.&lt;br /&gt;
* [[ShredderControl]] - a BASH script to shred tickets using [[RTx]]-Shredder.&lt;br /&gt;
* [[MigrateBugzillaToRT]] - Migrate a Bugzilla instance cleanly to RT&lt;br /&gt;
* [[IntegrateSphinx]] - How to integrate the Sphinx full-text search engine into RT&lt;br /&gt;
* [[SendingCommentsDirectlyToATicketWithExim4]] - How to configure exim4 to send comments to tickets based on ticket ID and custom field values&lt;br /&gt;
* [[rt-clonequeue]] - a perl script used to create a new queue using an existing one as a template (copying its custom fields, permissions, templates and scrips but not its tickets).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Browser Tools ===&lt;br /&gt;
&lt;br /&gt;
* [[AutomaticTextareaAutosave]] - Firefox + Windows only&lt;br /&gt;
* Integrated Browser Search - Add a custom search engine for RT system to your modern browser with [[OpenSearchPluginForRT]]. See also [[SearchRTFromFirefox]].&lt;br /&gt;
* [[RTKeyboardShortcut]] - Firefox + Greasmonky script (can be added to an installation)&lt;br /&gt;
&lt;br /&gt;
== Database Queries ==&lt;br /&gt;
&lt;br /&gt;
* [[QueryResolvedByUser]]&lt;br /&gt;
&lt;br /&gt;
== Other ==&lt;br /&gt;
&lt;br /&gt;
* [http://www.cs.mu.oz.au/systems/rt.html Department of Computer Science and Software Engineering, The University of Melbourne&#039;s RT page]&lt;br /&gt;
* [http://page.mi.fu-berlin.de/~pape/rt3screenshots/ Dirk Pape&#039;s RT page] (Fixed &amp;quot;Fork&amp;quot; tarball link, I hope it is the right Version.)&lt;br /&gt;
* [http://web.mit.edu/tooltime/ MIT IS&amp;amp;amp;T RT page]&lt;br /&gt;
* [http://www.usit.uio.no/it/rt/modifications/ University of Oslo&#039;s RT page]&lt;br /&gt;
* [http://www.cs.kent.ac.uk/people/staff/tdb/rt3/ University of Kent&#039;s RT page]&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=RestrictAttachments&amp;diff=27250</id>
		<title>RestrictAttachments</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=RestrictAttachments&amp;diff=27250"/>
		<updated>2025-12-18T11:49:18Z</updated>

		<summary type="html">&lt;p&gt;Robl: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Restricting Attachments in RT =&lt;br /&gt;
&lt;br /&gt;
== Existing Attachment Restrictions ==&lt;br /&gt;
&lt;br /&gt;
RT has existing config options to set a maximum file size for attachments, and if files exceeding this size limit should be truncated or dropped. &lt;br /&gt;
&lt;br /&gt;
See config options:&lt;br /&gt;
&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#MaxAttachmentSize MaxAttachmentSize]&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#TruncateLongAttachments TruncateLongAttachments]&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#DropLongAttachments DropLongAttachments]&lt;br /&gt;
&lt;br /&gt;
== Restricting attachment types ==&lt;br /&gt;
&lt;br /&gt;
RT (as of 6.0.2) does not currently have a way to restrict attachments only to specific types, and there is no easy way to disable all attachments.&lt;br /&gt;
&lt;br /&gt;
There are two methods to customise RT to restrict file attachments, depending on where you need to restrict:&lt;br /&gt;
&lt;br /&gt;
# Restrict which file types can be uploaded via the web interface (When users upload attachments)&lt;br /&gt;
# Add a back-end overlay which restricts which attachment types will be stored globally, including via email, or any transaction involving attachments.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1. Restricting via RT Web Interface ==&lt;br /&gt;
&lt;br /&gt;
RT uses the JavaScript library [https://docs.dropzone.dev/ Dropzone] in the web UI to handle file uploads.&lt;br /&gt;
&lt;br /&gt;
Dropzone has an option &#039;&#039;&#039;acceptedFiles&#039;&#039;&#039; to limit the file type and/or extensions accepted.&lt;br /&gt;
&lt;br /&gt;
This checks the file&#039;s MIME type or extension against this list. This is a &#039;&#039;&#039;comma separated list&#039;&#039;&#039; of MIME types or file extensions, e.g., The following would allow all image files, any file with MIME type application/pdf, and any file with extension .csv. All other file types will be rejected:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
acceptedFiles: image/*,application/pdf,.csv&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing in RT (6.0.2 and possibly older) ===&lt;br /&gt;
&lt;br /&gt;
* Install local modified version of [https://gist.github.com/listerr/4445b14adea8f801970ce7daf7d9613e AddAttachments] Mason template. From your RT directory, e.g. (/opt/rt6)&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # cd /opt/rt6&lt;br /&gt;
 # mkdir -p local/html/Ticket/Elements&lt;br /&gt;
 # cd local/html/Ticket/Elements&lt;br /&gt;
 # wget https://gist.githubusercontent.com/listerr/4445b14adea8f801970ce7daf7d9613e/raw/83b93f6f3d410432b5d354a487278bea34b1e6d5/AddAttachments&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add the following new RT config option to your &#039;&#039;&#039;RT_SiteConfig.d&#039;&#039;&#039; files, e.g.: /opt/rt6/etc/RT_SiteConfig.d/20-RT_AttachmentRestrict.pm :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 Set($AcceptedFiles, &#039;image/*,application/pdf,.csv&#039;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Clear the Mason cache&#039;&#039;&#039; and restart everything. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE: This will vary depending on your installation of RT and OS/Distro&#039;&#039;&#039;, e.g. if you installed RT from source or using a package manager. Mine (on nginx and systemd) looks something like this:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # service nginx stop &lt;br /&gt;
 # systemctl stop rt-server.socket &lt;br /&gt;
 # systemctl stop rt-server.service &lt;br /&gt;
 # rm -rf /opt/rt6/var/mason_data/obj &lt;br /&gt;
 # systemctl start rt-server.socket&lt;br /&gt;
 # systemctl start rt-server.service&lt;br /&gt;
 # service nginx start&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One-line version:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# service nginx stop &amp;amp;&amp;amp; systemctl stop rt-server.socket &amp;amp;&amp;amp; systemctl stop rt-server.service &amp;amp;&amp;amp; rm -rf /opt/rt6/var/mason_data/obj &amp;amp;&amp;amp; systemctl start rt-server.socket &amp;amp;&amp;amp; systemctl start rt-server.service &amp;amp;&amp;amp; service nginx start&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTES&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* This method will not restrict attachments added outside the web interface, or not using Dropzone.&lt;br /&gt;
&lt;br /&gt;
* RT has a config option, &amp;lt;code&amp;gt;$PreferDropzone&amp;lt;/code&amp;gt; which is enabled by default. However, users can change this option in their preferences to disable Dropzone. &#039;&#039;&#039;This modified AddAttachments ignores the user preference setting&#039;&#039;&#039; to prevent users bypassing attachment restrictions by disabling Dropzone.&lt;br /&gt;
&lt;br /&gt;
* You may need to check this template when upgrading to new releases/updates of RT. The installed version is in &amp;lt;code&amp;gt;share/html/Ticket/Elements/AddAttachments&amp;lt;/code&amp;gt; by default.&lt;br /&gt;
&lt;br /&gt;
* This may be a little inflexible because it applies globally for all attachment uploads. It would need further modifications, for example, to apply only to some queues but not others, or to allow different restrictions depending on user or queue.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 2. Restricting via RT backend ==&lt;br /&gt;
&lt;br /&gt;
Alternative method is to restrict attachments when they are processed by the RT backend. This will restrict all attachments received via email and the web interface.&lt;br /&gt;
&lt;br /&gt;
This overrides the method &amp;lt;code&amp;gt;RT::Record::_EncodeLOB&amp;lt;/code&amp;gt; to reject attachments using the same method as the existing &amp;lt;code&amp;gt;$MaxAttachmentSize&amp;lt;/code&amp;gt; option.&lt;br /&gt;
&lt;br /&gt;
=== Implementing in RT (6.0.2 and possibly older) ===&lt;br /&gt;
&lt;br /&gt;
* Install local modified version of [https://gist.github.com/listerr/4e1d697884528a30b2bea2b6e531199f Record_Local.pm]. From your RT directory, e.g. (/opt/rt6)&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # cd /opt/rt6&lt;br /&gt;
 # mkdir -p local/lib/RT&lt;br /&gt;
 # cd local/lib/RT&lt;br /&gt;
 # wget https://gist.githubusercontent.com/listerr/4e1d697884528a30b2bea2b6e531199f/raw/d7c1f5fc74c3ef0c54c6b4d4d42110f962ee34cc/Record_Local.pm&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add the following new RT config option(s) to your &#039;&#039;&#039;RT_SiteConfig.d&#039;&#039;&#039; files, e.g.: &amp;lt;code&amp;gt;/opt/rt6/etc/RT_SiteConfig.d/20-RT_AttachmentRestrict.pm&amp;lt;/code&amp;gt;. You can use one or both of these options. MIME type is usually more flexible than listing lots of different file extensions.&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# Regexp to restrict by MIME Type:&lt;br /&gt;
Set($AttachmentTypeAllowRegexp,qr{^(application/pdf|image/jpeg|image/pjpeg)$});&lt;br /&gt;
 &lt;br /&gt;
# Regexp to restrict on filename/extension:&lt;br /&gt;
Set($AttachmentNameAllowRegexp,qr{\.(doc|png)$});&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Restart RT.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTES&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* This method will still allow files to be uploaded via the Web interface - attachments are only rejected once RT tries to add them to a ticket. This means a correspondence will still be sent, but without the file attachment. A warning will appear on the ticket to show the attachment was rejected.&lt;br /&gt;
&lt;br /&gt;
* Deleting file attachments involves changing the message structure (e.g. unpacking the entire message, removing the unwanted part(s) and creating a new message with the correct MIME headers etc.)  This is quite a complex process. For this reason, RT does not remove the attachment, but instead replaces it with a .txt part with the message &amp;lt;code&amp;gt;attachment type .... dropped - file type not permitted&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Notes/TODO ==&lt;br /&gt;
&lt;br /&gt;
* Possibly package this as an Extension if it proves useful.&lt;br /&gt;
* More examples of regexp matches&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=RestrictAttachments&amp;diff=27249</id>
		<title>RestrictAttachments</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=RestrictAttachments&amp;diff=27249"/>
		<updated>2025-12-18T11:47:32Z</updated>

		<summary type="html">&lt;p&gt;Robl: add Record_Local.pm method&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Restricting Attachments in RT =&lt;br /&gt;
&lt;br /&gt;
== Existing Attachment Restrictions ==&lt;br /&gt;
&lt;br /&gt;
RT has existing config options to set a maximum file size for attachments, and if files exceeding this size limit should be truncated or dropped. &lt;br /&gt;
&lt;br /&gt;
See config options:&lt;br /&gt;
&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#MaxAttachmentSize MaxAttachmentSize]&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#TruncateLongAttachments TruncateLongAttachments]&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#DropLongAttachments DropLongAttachments]&lt;br /&gt;
&lt;br /&gt;
== Restricting attachment types ==&lt;br /&gt;
&lt;br /&gt;
RT (as of 6.0.2) does not currently have a way to restrict attachments only to specific types, and there is no easy way to disable all attachments.&lt;br /&gt;
&lt;br /&gt;
There are two methods to customise RT to restrict file attachments, depending on where you need to restrict:&lt;br /&gt;
&lt;br /&gt;
# Restrict which file types can be uploaded via the web interface (When users upload attachments)&lt;br /&gt;
# Add a back-end overlay which restricts which attachment types will be stored globally, including via email, or any transaction involving attachments.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1. Restricting via RT Web Interface ==&lt;br /&gt;
&lt;br /&gt;
RT uses the JavaScript library [https://docs.dropzone.dev/ Dropzone] in the web UI to handle file uploads.&lt;br /&gt;
&lt;br /&gt;
Dropzone has an option &#039;&#039;&#039;acceptedFiles&#039;&#039;&#039; to limit the file type and/or extensions accepted.&lt;br /&gt;
&lt;br /&gt;
This checks the file&#039;s MIME type or extension against this list. This is a &#039;&#039;&#039;comma separated list&#039;&#039;&#039; of MIME types or file extensions, e.g., The following would allow all image files, any file with MIME type application/pdf, and any file with extension .csv. All other file types will be rejected:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
acceptedFiles: image/*,application/pdf,.csv&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing in RT (6.0.2 and possibly older) ===&lt;br /&gt;
&lt;br /&gt;
* Install local modified version of [https://gist.github.com/listerr/4445b14adea8f801970ce7daf7d9613e AddAttachments] Mason template. From your RT directory, e.g. (/opt/rt6)&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # cd /opt/rt6&lt;br /&gt;
 # mkdir -p local/html/Ticket/Elements&lt;br /&gt;
 # cd local/html/Ticket/Elements&lt;br /&gt;
 # wget https://gist.githubusercontent.com/listerr/4445b14adea8f801970ce7daf7d9613e/raw/83b93f6f3d410432b5d354a487278bea34b1e6d5/AddAttachments&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add the following new RT config option to your &#039;&#039;&#039;RT_SiteConfig.d&#039;&#039;&#039; files, e.g.: /opt/rt6/etc/RT_SiteConfig.d/20-RT_AttachmentRestrict.pm :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 Set($AcceptedFiles, &#039;image/*,application/pdf,.csv&#039;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Clear the Mason cache&#039;&#039;&#039; and restart everything. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE: This will vary depending on your installation of RT and OS/Distro&#039;&#039;&#039;, e.g. if you installed RT from source or using a package manager. Mine (on nginx and systemd) looks something like this:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # service nginx stop &lt;br /&gt;
 # systemctl stop rt-server.socket &lt;br /&gt;
 # systemctl stop rt-server.service &lt;br /&gt;
 # rm -rf /opt/rt6/var/mason_data/obj &lt;br /&gt;
 # systemctl start rt-server.socket&lt;br /&gt;
 # systemctl start rt-server.service&lt;br /&gt;
 # service nginx start&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One-line version:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# service nginx stop &amp;amp;&amp;amp; systemctl stop rt-server.socket &amp;amp;&amp;amp; systemctl stop rt-server.service &amp;amp;&amp;amp; rm -rf /opt/rt6/var/mason_data/obj &amp;amp;&amp;amp; systemctl start rt-server.socket &amp;amp;&amp;amp; systemctl start rt-server.service &amp;amp;&amp;amp; service nginx start&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTES&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* This method will not restrict attachments added outside the web interface, or not using Dropzone.&lt;br /&gt;
&lt;br /&gt;
* RT has a config option, &amp;lt;code&amp;gt;$PreferDropzone&amp;lt;/code&amp;gt; which is enabled by default. However, users can change this option in their preferences to disable Dropzone. &#039;&#039;&#039;This modified AddAttachments ignores the user preference setting&#039;&#039;&#039; to prevent users bypassing attachment restrictions by disabling Dropzone.&lt;br /&gt;
&lt;br /&gt;
* You may need to check this template when upgrading to new releases/updates of RT. The installed version is in &amp;lt;code&amp;gt;share/html/Ticket/Elements/AddAttachments&amp;lt;/code&amp;gt; by default.&lt;br /&gt;
&lt;br /&gt;
* This may be a little inflexible because it applies globally for all attachment uploads. It would need further modifications, for example, to apply only to some queues but not others, or to allow different restrictions depending on user or queue.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 2. Restricting via RT backend ==&lt;br /&gt;
&lt;br /&gt;
Alternative method is to restrict attachments when they are processed by the RT backend. This will restrict all attachments received via email and the web interface.&lt;br /&gt;
&lt;br /&gt;
This overrides the method &amp;lt;code&amp;gt;RT::Record::_EncodeLOB&amp;lt;/code&amp;gt; to reject attachments using the same method as the existing &amp;lt;code&amp;gt;$MaxAttachmentSize&amp;lt;/code&amp;gt; option.&lt;br /&gt;
&lt;br /&gt;
=== Implementing in RT (6.0.2 and possibly older) ===&lt;br /&gt;
&lt;br /&gt;
* Install local modified version of [https://gist.github.com/listerr/4e1d697884528a30b2bea2b6e531199f Record_Local.pm]. From your RT directory, e.g. (/opt/rt6)&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # cd /opt/rt6&lt;br /&gt;
 # mkdir -p local/lib/RT&lt;br /&gt;
 # cd local/lib/RT&lt;br /&gt;
 # wget https://gist.githubusercontent.com/listerr/4e1d697884528a30b2bea2b6e531199f/raw/d7c1f5fc74c3ef0c54c6b4d4d42110f962ee34cc/Record_Local.pm&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add the following new RT config option(s) to your &#039;&#039;&#039;RT_SiteConfig.d&#039;&#039;&#039; files, e.g.: &amp;lt;code&amp;gt;/opt/rt6/etc/RT_SiteConfig.d/20-RT_AttachmentRestrict.pm&amp;lt;/code&amp;gt;. You can use one or both of these options. MIME type is usually more flexible than listing lots of different file extensions.&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# Regexp to restrict by MIME Type:&lt;br /&gt;
Set($AttachmentTypeAllowRegexp,qr{^(application/pdf|image/jpeg|image/pjpeg)$});&lt;br /&gt;
 &lt;br /&gt;
# Regexp to restrict on filename/extension:&lt;br /&gt;
Set($AttachmentNameAllowRegexp,qr{\.(doc|png)$});&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Restart RT.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTES&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* This method will still allow files to be uploaded via the Web interface - attachments are only rejected once RT tries to add them to a ticket. &lt;br /&gt;
&lt;br /&gt;
* Deleting file attachments involves changing the message structure (e.g. unpacking the entire message, removing the unwanted part(s) and creating a new message with the correct MIME headers etc.)  This is quite a complex process. For this reason, RT does not remove the attachment, but instead replaces it with a .txt part with the message &amp;lt;code&amp;gt;attachment type .... dropped - file type not permitted&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Notes/TODO ==&lt;br /&gt;
&lt;br /&gt;
* Possibly package this as an Extension if it proves useful.&lt;br /&gt;
* More examples of regexp matches&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=RestrictAttachments&amp;diff=27248</id>
		<title>RestrictAttachments</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=RestrictAttachments&amp;diff=27248"/>
		<updated>2025-12-17T19:26:15Z</updated>

		<summary type="html">&lt;p&gt;Robl: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Restricting Attachments in RT =&lt;br /&gt;
&lt;br /&gt;
== Existing Attachment Restrictions ==&lt;br /&gt;
&lt;br /&gt;
RT has existing config options to set a maximum file size for attachments, and if files exceeding this size limit should be truncated or dropped. &lt;br /&gt;
&lt;br /&gt;
See config options:&lt;br /&gt;
&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#MaxAttachmentSize MaxAttachmentSize]&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#TruncateLongAttachments TruncateLongAttachments]&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#DropLongAttachments DropLongAttachments]&lt;br /&gt;
&lt;br /&gt;
== Restricting attachment types ==&lt;br /&gt;
&lt;br /&gt;
RT (as of 6.0.2) does not currently have a way to restrict attachments only to specific types, and there is no easy way to disable all attachments.&lt;br /&gt;
&lt;br /&gt;
There are two methods to customise RT to restrict file attachments, depending on where you need to restrict:&lt;br /&gt;
&lt;br /&gt;
# Restrict which file types can be uploaded via the web interface (When users upload attachments)&lt;br /&gt;
# Add a back-end overlay which restricts which attachment types will be stored globally, including via email, or any transaction involving attachments.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1. Restricting via RT Web Interface ==&lt;br /&gt;
&lt;br /&gt;
RT uses the JavaScript library [https://docs.dropzone.dev/ Dropzone] in the web UI to handle file uploads.&lt;br /&gt;
&lt;br /&gt;
Dropzone has an option &#039;&#039;&#039;acceptedFiles&#039;&#039;&#039; to limit the file type and/or extensions accepted.&lt;br /&gt;
&lt;br /&gt;
This checks the file&#039;s MIME type or extension against this list. This is a &#039;&#039;&#039;comma separated list&#039;&#039;&#039; of MIME types or file extensions, e.g., The following would allow all image files, any file with MIME type application/pdf, and any file with extension .csv. All other file types will be rejected:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
acceptedFiles: image/*,application/pdf,.csv&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing in RT (6.x and possibly older) ===&lt;br /&gt;
&lt;br /&gt;
* Install local modified version of [https://gist.github.com/listerr/4445b14adea8f801970ce7daf7d9613e AddAttachments] Mason template. From your RT directory, e.g. (/opt/rt6)&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # cd /opt/rt6&lt;br /&gt;
 # mkdir -p local/html/Ticket/Elements&lt;br /&gt;
 # cd local/html/Ticket/Elements&lt;br /&gt;
 # wget https://gist.githubusercontent.com/listerr/4445b14adea8f801970ce7daf7d9613e/raw/83b93f6f3d410432b5d354a487278bea34b1e6d5/AddAttachments&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add the following new RT config option to your &#039;&#039;&#039;RT_SiteConfig.d&#039;&#039;&#039; files, e.g.: /opt/rt6/etc/RT_SiteConfig.d/20-RT_AttachmentRestrict.pm :&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 Set($AcceptedFiles, &#039;image/*,application/pdf,.csv&#039;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Clear the Mason cache&#039;&#039;&#039; and restart everything. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE: This will vary depending on your installation of RT and OS/Distro&#039;&#039;&#039;, e.g. if you installed RT from source or using a package manager. Mine (on nginx and systemd) looks something like this:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # service nginx stop &lt;br /&gt;
 # systemctl stop rt-server.socket &lt;br /&gt;
 # systemctl stop rt-server.service &lt;br /&gt;
 # rm -rf /opt/rt6/var/mason_data/obj &lt;br /&gt;
 # systemctl start rt-server.socket&lt;br /&gt;
 # systemctl start rt-server.service&lt;br /&gt;
 # service nginx start&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One-line version:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# service nginx stop &amp;amp;&amp;amp; systemctl stop rt-server.socket &amp;amp;&amp;amp; systemctl stop rt-server.service &amp;amp;&amp;amp; rm -rf /opt/rt6/var/mason_data/obj &amp;amp;&amp;amp; systemctl start rt-server.socket &amp;amp;&amp;amp; systemctl start rt-server.service &amp;amp;&amp;amp; service nginx start&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTES&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* RT has a config option, &#039;&#039;&#039;$PreferDropzone&#039;&#039;&#039; which is enabled by default. However, users can change this option in their preferences to disable Dropzone. &#039;&#039;&#039;This modified AddAttachments ignores the user preference setting&#039;&#039;&#039; to prevent users bypassing attachment restrictions by disabling Dropzone.&lt;br /&gt;
&lt;br /&gt;
* You may need to check this template when there upgrading to new releases/updates of RT. The installed version is in &#039;&#039;&#039;share/html/Ticket/Elements/AddAttachments&#039;&#039;&#039; by default.&lt;br /&gt;
&lt;br /&gt;
* This may be a little inflexible because it applies globally for all attachment uploads. It would need further modifications, for example, to apply only to some queues but not others, or to allow different restrictions depending on user or queue.&lt;br /&gt;
&lt;br /&gt;
== 2. Restricting via RT back-end ==&lt;br /&gt;
&lt;br /&gt;
Alternative method in the RT back-end. This will restrict all attachments received via email and the web interface.&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=RestrictAttachments&amp;diff=27247</id>
		<title>RestrictAttachments</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=RestrictAttachments&amp;diff=27247"/>
		<updated>2025-12-17T19:14:31Z</updated>

		<summary type="html">&lt;p&gt;Robl: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Restricting Attachments in RT =&lt;br /&gt;
&lt;br /&gt;
== Existing Attachment Restrictions ==&lt;br /&gt;
&lt;br /&gt;
RT has existing config options to set a maximum file size for attachments, and if files exceeding this size limit should be truncated or dropped. &lt;br /&gt;
&lt;br /&gt;
See config options:&lt;br /&gt;
&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#MaxAttachmentSize MaxAttachmentSize]&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#TruncateLongAttachments TruncateLongAttachments]&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#DropLongAttachments DropLongAttachments]&lt;br /&gt;
&lt;br /&gt;
== Restricting attachment types ==&lt;br /&gt;
&lt;br /&gt;
RT (as of 6.0.2) does not currently have a way to restrict attachments only to specific types, and there is no easy way to disable all attachments.&lt;br /&gt;
&lt;br /&gt;
There are two methods to customise RT to restrict file attachments, depending on where you need to restrict:&lt;br /&gt;
&lt;br /&gt;
# Restrict which file types can be uploaded via the web interface (When users upload attachments)&lt;br /&gt;
# Add a back-end overlay which restricts which attachment types will be stored globally, including via email, or any transaction involving attachments.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1. Restricting via RT Web Interface ==&lt;br /&gt;
&lt;br /&gt;
RT uses the JavaScript library [https://docs.dropzone.dev/ Dropzone] in the web UI to handle file uploads.&lt;br /&gt;
&lt;br /&gt;
Dropzone has an option &#039;&#039;&#039;acceptedFiles&#039;&#039;&#039; to limit the file type and/or extensions accepted.&lt;br /&gt;
&lt;br /&gt;
This checks the file&#039;s MIME type or extension against this list. This is a &#039;&#039;&#039;comma separated list&#039;&#039;&#039; of MIME types or file extensions, e.g., The following would allow all image files, any file with MIME type application/pdf, and any file with extension .csv. All other file types will be rejected:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
acceptedFiles: image/*,application/pdf,.csv&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing in RT (6.x and possibly older) ===&lt;br /&gt;
&lt;br /&gt;
* Install local modified version of [https://gist.github.com/listerr/4445b14adea8f801970ce7daf7d9613e AddAttachments] Mason template. From your RT directory, e.g. (/opt/rt6)&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # cd /opt/rt6&lt;br /&gt;
 # mkdir -p local/html/Ticket/Elements&lt;br /&gt;
 # cd local/html/Ticket/Elements&lt;br /&gt;
 # wget https://gist.githubusercontent.com/listerr/4445b14adea8f801970ce7daf7d9613e/raw/83b93f6f3d410432b5d354a487278bea34b1e6d5/AddAttachments&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add a new RT config option to your &#039;&#039;&#039;RT_SiteConfig.d&#039;&#039;&#039; files, e.g.:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # cd /opt/rt6/etc/RT_SiteConfig.d&lt;br /&gt;
 # echo &#039;Set($AcceptedFiles, &#039;image/*,application/pdf,.csv&#039;);&#039; &amp;gt; 20-RT_AttachmentRestrict.pm&lt;br /&gt;
 &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Clear the Mason cache&#039;&#039;&#039; and restart everything. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE: This will vary depending on your installation of RT and OS/Distro&#039;&#039;&#039;, e.g. if you installed RT from source or using a package manager. Mine (on nginx and systemd) looks something like this:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # service nginx stop &lt;br /&gt;
 # systemctl stop rt-server.socket &lt;br /&gt;
 # systemctl stop rt-server.service &lt;br /&gt;
 # rm -rf /opt/rt6/var/mason_data/obj &lt;br /&gt;
 # systemctl start rt-server.socket&lt;br /&gt;
 # systemctl start rt-server.service&lt;br /&gt;
 # service nginx start&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One-line version:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# service nginx stop &amp;amp;&amp;amp; systemctl stop rt-server.socket &amp;amp;&amp;amp; systemctl stop rt-server.service &amp;amp;&amp;amp; rm -rf /opt/rt6/var/mason_data/obj &amp;amp;&amp;amp; systemctl start rt-server.socket &amp;amp;&amp;amp; systemctl start rt-server.service &amp;amp;&amp;amp; service nginx start&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTES&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* RT has a config option, &#039;&#039;&#039;$PreferDropzone&#039;&#039;&#039; which is enabled by default. However, users can change this option in their preferences to disable Dropzone. &#039;&#039;&#039;This modified AddAttachments ignores the user preference setting&#039;&#039;&#039; to prevent users bypassing attachment restrictions by disabling Dropzone.&lt;br /&gt;
&lt;br /&gt;
* You may need to check this template when there upgrading to new releases/updates of RT. The installed version is in &#039;&#039;&#039;share/html/Ticket/Elements/AddAttachments&#039;&#039;&#039; by default.&lt;br /&gt;
&lt;br /&gt;
* This may be a little inflexible because it applies globally for all attachment uploads. It would need further modifications, for example, to apply only to some queues but not others, or to allow different restrictions depending on user or queue.&lt;br /&gt;
&lt;br /&gt;
== 2. Restricting via RT back-end ==&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=RestrictAttachments&amp;diff=27246</id>
		<title>RestrictAttachments</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=RestrictAttachments&amp;diff=27246"/>
		<updated>2025-12-17T19:01:15Z</updated>

		<summary type="html">&lt;p&gt;Robl: add new page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Restricting Attachments in RT =&lt;br /&gt;
&lt;br /&gt;
== Existing Attachment Restrictions ==&lt;br /&gt;
&lt;br /&gt;
RT has existing config options to set a maximum file size for attachments, and if files exceeding this size limit should be truncated or dropped. &lt;br /&gt;
&lt;br /&gt;
See config options:&lt;br /&gt;
&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#MaxAttachmentSize MaxAttachmentSize]&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#TruncateLongAttachments TruncateLongAttachments]&lt;br /&gt;
* [https://docs.bestpractical.com/rt/6.0.2/RT_Config.html#DropLongAttachments DropLongAttachments]&lt;br /&gt;
&lt;br /&gt;
== Restricting attachment types ==&lt;br /&gt;
&lt;br /&gt;
RT (as of 6.0.2) does not currently have a way to restrict attachments only to specific types, and there is no easy way to disable all attachments.&lt;br /&gt;
&lt;br /&gt;
There are two methods to customise RT to restrict file attachments, depending on where you need to restrict:&lt;br /&gt;
&lt;br /&gt;
# Restrict which file types can be uploaded via the web interface (When users upload attachments)&lt;br /&gt;
# Add a back-end overlay which restricts which attachment types will be stored globally, including via email, or any transaction involving attachments.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 1. Restricting via RT Web Interface ==&lt;br /&gt;
&lt;br /&gt;
RT uses the JavaScript library [https://docs.dropzone.dev/ Dropzone] in the web UI to handle file uploads.&lt;br /&gt;
&lt;br /&gt;
Dropzone has an option &#039;&#039;&#039;acceptedFiles&#039;&#039;&#039; to limit the file type and/or extensions accepted.&lt;br /&gt;
&lt;br /&gt;
This checks the file&#039;s MIME type or extension against this list. This is a &#039;&#039;&#039;comma separated list&#039;&#039;&#039; of MIME types or file extensions, e.g., The following would allow all image files, any file with MIME type application/pdf, and any file with extension .csv. All other file types will be rejected:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
acceptedFiles: image/*,application/pdf,.csv&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Implementing in RT (6.x and possibly older) ===&lt;br /&gt;
&lt;br /&gt;
* Install local modified version of [https://gist.github.com/listerr/4445b14adea8f801970ce7daf7d9613e AddAttachments] Mason template. From your RT directory, e.g. (/opt/rt6)&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # cd /opt/rt6&lt;br /&gt;
 # mkdir -p local/html/Ticket/Elements&lt;br /&gt;
 # cd local/html/Ticket/Elements&lt;br /&gt;
 # wget https://gist.githubusercontent.com/listerr/4445b14adea8f801970ce7daf7d9613e/raw/83b93f6f3d410432b5d354a487278bea34b1e6d5/AddAttachments&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add a new RT config option to your &#039;&#039;&#039;RT_SiteConfig.d&#039;&#039;&#039; files, e.g.:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # cd /opt/rt6/etc/RT_SiteConfig.d&lt;br /&gt;
 # echo &#039;Set($AcceptedFiles, &#039;image/*,application/pdf,.csv&#039;);&#039; &amp;gt; 20-RT_AttachmentRestrict.pm&lt;br /&gt;
 &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Clear the Mason cache&#039;&#039;&#039; and restart everything. NOTE: This will vary depending on your installation of RT and OS/Distro, e.g. if you installed RT from source or using a package manager. Mine (on nginx and systemd) looks something like this:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
 # service nginx stop &lt;br /&gt;
 # systemctl stop rt-server.socket &lt;br /&gt;
 # systemctl stop rt-server.service &lt;br /&gt;
 # rm -rf /opt/rt6/var/mason_data/obj &lt;br /&gt;
 # systemctl start rt-server.socket&lt;br /&gt;
 # systemctl start rt-server.service&lt;br /&gt;
 # service nginx start&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
One-line version:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
# service nginx stop &amp;amp;&amp;amp; systemctl stop rt-server.socket &amp;amp;&amp;amp; systemctl stop rt-server.service &amp;amp;&amp;amp; rm -rf /opt/rt6/var/mason_data/obj &amp;amp;&amp;amp; systemctl start rt-server.socket &amp;amp;&amp;amp; systemctl start rt-server.service &amp;amp;&amp;amp; service nginx start&lt;br /&gt;
&amp;lt;/nowiki&amp;gt;&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=HideTransactions&amp;diff=27245</id>
		<title>HideTransactions</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=HideTransactions&amp;diff=27245"/>
		<updated>2025-12-17T17:36:56Z</updated>

		<summary type="html">&lt;p&gt;Robl: spelling&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Hiding transactions in tickets&#039; history =&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
You &#039;&#039;&#039;should be&#039;&#039;&#039; familiar with [[CustomizingWithCallbacks]] as all examples here are based on &#039;SkipTransaction&#039; callback in Ticket/Elements/ShowHistory Mason component. Create a Callback &#039;SkipTransaction&#039; in local/html/Callbacks/&amp;amp;lt;MyCallbacks&amp;amp;gt;/Ticket/Elements/ShowHistory/SkipTransaction and pick code below.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;For RT 4.2.0 and above &#039;&#039;&#039;Ticket/Elements/ShowHistory no longer exists.  You will need to hook Elements/ShowHistory&#039;s SkipTransaction instead.  Keep in mind that this means your transactions may apply to users or queues in addition to tickets and you should tread carefully.&lt;br /&gt;
&lt;br /&gt;
SkipTransaction callback allows your code to set $$skip variable if you want to skip the current transaction.&lt;br /&gt;
&lt;br /&gt;
== Minimal example ==&lt;br /&gt;
&lt;br /&gt;
This is minimal example as starting point that hides all transactions created by the system user.&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;%INIT&amp;gt;&lt;br /&gt;
    $$skip = 1 if $Transaction-&amp;gt;Creator == $RT::SystemUser-&amp;gt;id;&lt;br /&gt;
    &amp;lt;/%INIT&amp;gt;&lt;br /&gt;
    &amp;lt;%ARGS&amp;gt;&lt;br /&gt;
    $Transaction =&amp;gt; undef&lt;br /&gt;
    $skip =&amp;gt; undef&lt;br /&gt;
    &amp;lt;/%ARGS&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sane template for your custom code ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
    &amp;lt;%INIT&amp;gt;&lt;br /&gt;
    # do nothing if transaction is already flagged for skipping&lt;br /&gt;
    # yes, this is possible, you may have many callbacks that&lt;br /&gt;
    # skip using different conditions&lt;br /&gt;
    return if $$skip;&lt;br /&gt;
&lt;br /&gt;
    # do nothing if it&#039;s not system user&lt;br /&gt;
    return unless $Transaction-&amp;gt;Creator == $RT::SystemUser-&amp;gt;id;&lt;br /&gt;
&lt;br /&gt;
    # this is not good idea to skip some transaction types&lt;br /&gt;
    # even if those are created by system user&lt;br /&gt;
    return if $SHOW{ $Transaction-&amp;gt;Type };&lt;br /&gt;
&lt;br /&gt;
    ...&lt;br /&gt;
    here add your additional conditions&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;/%INIT&amp;gt;&lt;br /&gt;
    &amp;lt;%ONCE&amp;gt;&lt;br /&gt;
    my %SHOW = map { $_ =&amp;gt; 1} qw(&lt;br /&gt;
        Create Comment Correspond&lt;br /&gt;
        EmailRecord CommentEmailRecord&lt;br /&gt;
    );&lt;br /&gt;
    &amp;lt;/%ONCE&amp;gt;&lt;br /&gt;
    &amp;lt;%ARGS&amp;gt;&lt;br /&gt;
    $Transaction =&amp;gt; undef&lt;br /&gt;
    $skip =&amp;gt; undef&lt;br /&gt;
    &amp;lt;/%ARGS&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Additional code snippets ==&lt;br /&gt;
&lt;br /&gt;
=== Do not hide on History.html page ===&lt;br /&gt;
&lt;br /&gt;
If you&#039;d like to have an abbreviated history on the main ticket display, but would like the full history in the History tab display, then add the following:&lt;br /&gt;
&lt;br /&gt;
    ...&lt;br /&gt;
    # do nothing if we&#039;re on history page&lt;br /&gt;
    return if $r-&amp;gt;uri =~ /History\.html/;&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
== RT::Extension::HistoryFilter ==&lt;br /&gt;
&lt;br /&gt;
I created an extension where you can define which transaction types are shown on the ticket display page. The ticket history page shows always the full history. You can find it at github: [http://github.com/cloos/rt-extension-briefhistory http://github.com/cloos/rt-extension-historyfilter]&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
[[CustomizingWithCallbacks]]&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27239</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27239"/>
		<updated>2025-03-13T14:55:57Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Search Users by Partial Email Address */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== RT System Information ===&lt;br /&gt;
&lt;br /&gt;
* A good start to check it&#039;s working and authenticating correctly:&lt;br /&gt;
&lt;br /&gt;
 curl -s -X GET \&lt;br /&gt;
 -H &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
 -H &amp;quot;Accept: application/json&amp;quot; \&lt;br /&gt;
 &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/rt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Returns similar to:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;Version&amp;quot; : &amp;quot;5.0.7&amp;quot;,&lt;br /&gt;
   &amp;quot;Plugins&amp;quot; : [&lt;br /&gt;
      &amp;quot;RT::Extension::BooleanCustomField&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::CustomFieldsOnUpdate&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::NonWatcherRecipients&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::Tags&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Authen::OAuth2&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::CommandByMail&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::RepliesToResolved&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::TicketLocking&amp;quot;,&lt;br /&gt;
      &amp;quot;RTx::Calendar&amp;quot;&lt;br /&gt;
   ]&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
* Plugins list will only be present if the user has SuperUser privileges in RT.&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
* Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Search for group by name ===&lt;br /&gt;
&lt;br /&gt;
* This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Add User to Group ===&lt;br /&gt;
&lt;br /&gt;
 # Add a user to a group, where group_id is set in a variable, and user name is set in a variable rt_user: &lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/$rt_user/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above &#039;&#039;Search for group by name&#039;&#039; curl example to return various properties from the search for group &amp;quot;Staff&amp;quot;, e.g.:  &lt;br /&gt;
&lt;br /&gt;
 $ curl ... &amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
 71414&lt;br /&gt;
&lt;br /&gt;
* If you are doing anything serious, it is better to use a scripting language such as python or perl, which has support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
=== Get only user defined groups ===&lt;br /&gt;
&lt;br /&gt;
* The groups query returns all groups by default, including internal groups (e.g. role groups for tickets.) This can return many thousands groups, which is unhelpful. The output also doesn&#039;t contain the group&#039;s name or type. To return only User Defined groups (e.g actual groups users are added to in RT.):&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
* You can also add fields= parameter to the url to include otherwise hidden fields:&lt;br /&gt;
&lt;br /&gt;
 curl https://&amp;lt;your_server&amp;gt;/REST/2.0/groups?fields=Name,Description,Domain,CustomFields --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
=== Search for user by Email Address ===&lt;br /&gt;
&lt;br /&gt;
 curl https://&amp;lt;your_server&amp;gt;/REST/2.0/users \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;EmailAddress&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;newuser@example.com&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Returns:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;page&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;count&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;total&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;per_page&amp;quot; : 20,&lt;br /&gt;
   &amp;quot;items&amp;quot; : [&lt;br /&gt;
      {&lt;br /&gt;
         &amp;quot;id&amp;quot; : &amp;quot;newuser&amp;quot;,&lt;br /&gt;
         &amp;quot;type&amp;quot; : &amp;quot;user&amp;quot;,&lt;br /&gt;
         &amp;quot;_url&amp;quot; : &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/newuser&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;pages&amp;quot; : 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
* Oddly, the id returned here is the &#039;&#039;&#039;username&#039;&#039;&#039;, not the numeric id as might be expected. Users can be retrieved using either id or username, so it&#039;s not always necessary to get the user id. If you actually want the numeric id, add it to fields=:&lt;br /&gt;
&lt;br /&gt;
 curl https://&amp;lt;your_server&amp;gt;/REST/2.0/users?&#039;&#039;&#039;fields=id,Name&#039;&#039;&#039; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;EmailAddress&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;newuser@example.com&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Returns:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;items&amp;quot; : [&lt;br /&gt;
      {&lt;br /&gt;
         &amp;quot;type&amp;quot; : &amp;quot;user&amp;quot;,&lt;br /&gt;
         &#039;&#039;&#039;&amp;quot;id&amp;quot; : 118176,&#039;&#039;&#039;&lt;br /&gt;
         &amp;quot;_url&amp;quot; : &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/newuser&amp;quot;,&lt;br /&gt;
         &#039;&#039;&#039;&amp;quot;Name&amp;quot; : &amp;quot;newuser&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
      }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot; : 20,&lt;br /&gt;
   &amp;quot;total&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;pages&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;page&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;count&amp;quot; : 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
* Notice the value of id is now the numeric id but as username is not included by default it is gone. Include Name in fields parameter to get it back if this is a wanted field, or you need a consistent field which does not change between username and numeric id.&lt;br /&gt;
&lt;br /&gt;
=== Search Users by Partial Email Address  ===&lt;br /&gt;
&lt;br /&gt;
* Find all users with email address domain example.com.&lt;br /&gt;
* This example adds the Email address and a few more useful fields to the results:&lt;br /&gt;
&lt;br /&gt;
 curl https://&amp;lt;your_server&amp;gt;/REST/2.0/users&#039;&#039;&#039;?fields=id,Name,EmailAddress,RealName,Organization&#039;&#039;&#039;\&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_server&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;EmailAddress&amp;quot;,&lt;br /&gt;
              &amp;quot;operator&amp;quot;: &amp;quot;ENDSWITH&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;@example.com&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Returns:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;total&amp;quot; : 2,&lt;br /&gt;
   &amp;quot;per_page&amp;quot; : 20,&lt;br /&gt;
   &amp;quot;items&amp;quot; : [&lt;br /&gt;
      {&lt;br /&gt;
         &amp;quot;RealName&amp;quot; : &amp;quot;Freddy Farquar&amp;quot;,&lt;br /&gt;
         &amp;quot;Name&amp;quot; : &amp;quot;newuser&amp;quot;,&lt;br /&gt;
         &amp;quot;id&amp;quot; : 118176,&lt;br /&gt;
         &amp;quot;type&amp;quot; : &amp;quot;user&amp;quot;,&lt;br /&gt;
         &amp;quot;_url&amp;quot; : &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/newuser&amp;quot;,&lt;br /&gt;
         &amp;quot;EmailAddress&amp;quot; : &amp;quot;newuser@example.com&amp;quot;,&lt;br /&gt;
         &amp;quot;Organization&amp;quot; : &amp;quot;Acme Widgets Ltd&amp;quot;&lt;br /&gt;
      },&lt;br /&gt;
      {&lt;br /&gt;
         &amp;quot;RealName&amp;quot; : &amp;quot;John Doe&amp;quot;,&lt;br /&gt;
         &amp;quot;Name&amp;quot; : &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
         &amp;quot;EmailAddress&amp;quot; : &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
         &amp;quot;Organization&amp;quot; : &amp;quot;Acme Widgets Inc&amp;quot;,&lt;br /&gt;
         &amp;quot;type&amp;quot; : &amp;quot;user&amp;quot;,&lt;br /&gt;
         &amp;quot;id&amp;quot; : 118178,&lt;br /&gt;
         &amp;quot;_url&amp;quot; : &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/newuser2&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;page&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;count&amp;quot; : 2&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
* Note: Only enabled users are retuned by default. You can add &amp;quot;&amp;amp;find_disabled_rows=1&amp;quot; to the URL show disabled users, but there appears to be no way to include the &amp;quot;Disabled&amp;quot; field, or to search on it e.g. &amp;quot;find only disabled users&amp;quot;. &lt;br /&gt;
* It also doesn&#039;t seem possible to search for Privileged users?&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27238</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27238"/>
		<updated>2025-03-13T14:43:58Z</updated>

		<summary type="html">&lt;p&gt;Robl: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== RT System Information ===&lt;br /&gt;
&lt;br /&gt;
* A good start to check it&#039;s working and authenticating correctly:&lt;br /&gt;
&lt;br /&gt;
 curl -s -X GET \&lt;br /&gt;
 -H &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
 -H &amp;quot;Accept: application/json&amp;quot; \&lt;br /&gt;
 &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/rt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Returns similar to:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;Version&amp;quot; : &amp;quot;5.0.7&amp;quot;,&lt;br /&gt;
   &amp;quot;Plugins&amp;quot; : [&lt;br /&gt;
      &amp;quot;RT::Extension::BooleanCustomField&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::CustomFieldsOnUpdate&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::NonWatcherRecipients&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::Tags&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Authen::OAuth2&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::CommandByMail&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::RepliesToResolved&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::TicketLocking&amp;quot;,&lt;br /&gt;
      &amp;quot;RTx::Calendar&amp;quot;&lt;br /&gt;
   ]&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
* Plugins list will only be present if the user has SuperUser privileges in RT.&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
* Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Search for group by name ===&lt;br /&gt;
&lt;br /&gt;
* This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Add User to Group ===&lt;br /&gt;
&lt;br /&gt;
 # Add a user to a group, where group_id is set in a variable, and user name is set in a variable rt_user: &lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/$rt_user/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above &#039;&#039;Search for group by name&#039;&#039; curl example to return various properties from the search for group &amp;quot;Staff&amp;quot;, e.g.:  &lt;br /&gt;
&lt;br /&gt;
 $ curl ... &amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
 71414&lt;br /&gt;
&lt;br /&gt;
* If you are doing anything serious, it is better to use a scripting language such as python or perl, which has support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
=== Get only user defined groups ===&lt;br /&gt;
&lt;br /&gt;
* The groups query returns all groups by default, including internal groups (e.g. role groups for tickets.) This can return many thousands groups, which is unhelpful. The output also doesn&#039;t contain the group&#039;s name or type. To return only User Defined groups (e.g actual groups users are added to in RT.):&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
* You can also add fields= parameter to the url to include otherwise hidden fields:&lt;br /&gt;
&lt;br /&gt;
 curl https://&amp;lt;your_server&amp;gt;/REST/2.0/groups?fields=Name,Description,Domain,CustomFields --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
=== Search for user by Email Address ===&lt;br /&gt;
&lt;br /&gt;
 curl https://&amp;lt;your_server&amp;gt;/REST/2.0/users \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;EmailAddress&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;newuser@example.com&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Returns:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;page&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;count&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;total&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;per_page&amp;quot; : 20,&lt;br /&gt;
   &amp;quot;items&amp;quot; : [&lt;br /&gt;
      {&lt;br /&gt;
         &amp;quot;id&amp;quot; : &amp;quot;newuser&amp;quot;,&lt;br /&gt;
         &amp;quot;type&amp;quot; : &amp;quot;user&amp;quot;,&lt;br /&gt;
         &amp;quot;_url&amp;quot; : &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/newuser&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;pages&amp;quot; : 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
* Oddly, the id returned here is the &#039;&#039;&#039;username&#039;&#039;&#039;, not the numeric id as might be expected. Users can be retrieved using either id or username, so it&#039;s not always necessary to get the user id. If you actually want the numeric id, add it to fields=:&lt;br /&gt;
&lt;br /&gt;
 curl https://&amp;lt;your_server&amp;gt;/REST/2.0/users?&#039;&#039;&#039;fields=id,Name&#039;&#039;&#039; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;EmailAddress&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;newuser@example.com&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Returns:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;items&amp;quot; : [&lt;br /&gt;
      {&lt;br /&gt;
         &amp;quot;type&amp;quot; : &amp;quot;user&amp;quot;,&lt;br /&gt;
         &#039;&#039;&#039;&amp;quot;id&amp;quot; : 118176,&#039;&#039;&#039;&lt;br /&gt;
         &amp;quot;_url&amp;quot; : &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/newuser&amp;quot;,&lt;br /&gt;
         &#039;&#039;&#039;&amp;quot;Name&amp;quot; : &amp;quot;newuser&amp;quot;&#039;&#039;&#039;&lt;br /&gt;
      }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot; : 20,&lt;br /&gt;
   &amp;quot;total&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;pages&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;page&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;count&amp;quot; : 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
* Notice the value of id is now the numeric id but as username is not included by default it is gone. Include Name in fields parameter to get it back if this is a wanted field, or you need a consistent field which does not change between username and numeric id.&lt;br /&gt;
&lt;br /&gt;
=== Search Users by Partial Email Address  ===&lt;br /&gt;
&lt;br /&gt;
* This example adds the Email address, and a few more useful fields to the results:&lt;br /&gt;
&lt;br /&gt;
 curl https://&amp;lt;your_server&amp;gt;/REST/2.0/users&#039;&#039;&#039;?fields=id,Name,EmailAddress,RealName,Organization&#039;&#039;&#039;\&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_server&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;EmailAddress&amp;quot;,&lt;br /&gt;
              &amp;quot;operator&amp;quot;: &amp;quot;ENDSWITH&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;@example.com&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Returns:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;total&amp;quot; : 2,&lt;br /&gt;
   &amp;quot;per_page&amp;quot; : 20,&lt;br /&gt;
   &amp;quot;items&amp;quot; : [&lt;br /&gt;
      {&lt;br /&gt;
         &amp;quot;RealName&amp;quot; : &amp;quot;Freddy Farquar&amp;quot;,&lt;br /&gt;
         &amp;quot;Name&amp;quot; : &amp;quot;newuser&amp;quot;,&lt;br /&gt;
         &amp;quot;id&amp;quot; : 118176,&lt;br /&gt;
         &amp;quot;type&amp;quot; : &amp;quot;user&amp;quot;,&lt;br /&gt;
         &amp;quot;_url&amp;quot; : &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/newuser&amp;quot;,&lt;br /&gt;
         &amp;quot;EmailAddress&amp;quot; : &amp;quot;newuser@example.com&amp;quot;,&lt;br /&gt;
         &amp;quot;Organization&amp;quot; : &amp;quot;Acme Widgets Ltd&amp;quot;&lt;br /&gt;
      },&lt;br /&gt;
      {&lt;br /&gt;
         &amp;quot;RealName&amp;quot; : &amp;quot;John Doe&amp;quot;,&lt;br /&gt;
         &amp;quot;Name&amp;quot; : &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
         &amp;quot;EmailAddress&amp;quot; : &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
         &amp;quot;Organization&amp;quot; : &amp;quot;Acme Widgets Inc&amp;quot;,&lt;br /&gt;
         &amp;quot;type&amp;quot; : &amp;quot;user&amp;quot;,&lt;br /&gt;
         &amp;quot;id&amp;quot; : 118178,&lt;br /&gt;
         &amp;quot;_url&amp;quot; : &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/newuser2&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;page&amp;quot; : 1,&lt;br /&gt;
   &amp;quot;count&amp;quot; : 2&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
* Note: Only enabled users are retuned by default. You can add &amp;quot;&amp;amp;find_disabled_rows=1&amp;quot; to the URL show disabled users, but there appears to be no way to include the &amp;quot;Disabled&amp;quot; field, or to search on it e.g. &amp;quot;find only disabled users&amp;quot;. &lt;br /&gt;
* It also doesn&#039;t seem possible to search for Privileged users?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27237</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27237"/>
		<updated>2025-03-13T13:23:10Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* RT System Information */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== RT System Information ===&lt;br /&gt;
&lt;br /&gt;
* A good start to check it&#039;s working and authenticating correctly:&lt;br /&gt;
&lt;br /&gt;
 curl -s -X GET \&lt;br /&gt;
 -H &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
 -H &amp;quot;Accept: application/json&amp;quot; \&lt;br /&gt;
 &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/rt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Returns similar to:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;Version&amp;quot; : &amp;quot;5.0.7&amp;quot;,&lt;br /&gt;
   &amp;quot;Plugins&amp;quot; : [&lt;br /&gt;
      &amp;quot;RT::Extension::BooleanCustomField&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::CustomFieldsOnUpdate&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::NonWatcherRecipients&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::Tags&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Authen::OAuth2&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::CommandByMail&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::RepliesToResolved&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::TicketLocking&amp;quot;,&lt;br /&gt;
      &amp;quot;RTx::Calendar&amp;quot;&lt;br /&gt;
   ]&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
* Plugins list will only be present if the user has SuperUser privileges in RT.&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
* Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Search for group by name ===&lt;br /&gt;
&lt;br /&gt;
* This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Add User to Group ===&lt;br /&gt;
&lt;br /&gt;
 # Add a user to a group, where group_id is set in a variable, and user name is set in a variable rt_user: &lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/$rt_user/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above &#039;&#039;Search for group by name&#039;&#039; curl example to return various properties from the search for group &amp;quot;Staff&amp;quot;, e.g.:  &lt;br /&gt;
&lt;br /&gt;
 $ curl ... &amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
 71414&lt;br /&gt;
&lt;br /&gt;
* If you are doing anything serious, it is better to use a scripting language such as python or perl, which has support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
=== Get only user defined groups ===&lt;br /&gt;
&lt;br /&gt;
* The groups query returns all groups by default, including internal groups (e.g. role groups for tickets.) This can return many thousands groups, which is unhelpful. The output also doesn&#039;t contain the group&#039;s name or type. To return only User Defined groups (e.g actual groups users are added to in RT.):&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
* You can also add fields= parameter to the url to include otherwise hidden fields:&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups?fields=Name,Description,Domain,CustomFields --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27236</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27236"/>
		<updated>2025-03-13T13:19:45Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Examples */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== RT System Information ===&lt;br /&gt;
&lt;br /&gt;
* A good start to check it&#039;s working and authenticating correctly:&lt;br /&gt;
&lt;br /&gt;
 curl -s -X GET \&lt;br /&gt;
 -H &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
 -H &amp;quot;Accept: application/json&amp;quot; \&lt;br /&gt;
 &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/rt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Returns similar to:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;Version&amp;quot; : &amp;quot;5.0.7&amp;quot;,&lt;br /&gt;
   &amp;quot;Plugins&amp;quot; : [&lt;br /&gt;
      &amp;quot;RT::Extension::BooleanCustomField&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::CustomFieldsOnUpdate&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::NonWatcherRecipients&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::Tags&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Authen::OAuth2&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::CommandByMail&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::RepliesToResolved&amp;quot;,&lt;br /&gt;
      &amp;quot;RT::Extension::TicketLocking&amp;quot;,&lt;br /&gt;
      &amp;quot;RTx::Calendar&amp;quot;&lt;br /&gt;
   ]&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
* Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Search for group by name ===&lt;br /&gt;
&lt;br /&gt;
* This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Add User to Group ===&lt;br /&gt;
&lt;br /&gt;
 # Add a user to a group, where group_id is set in a variable, and user name is set in a variable rt_user: &lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/$rt_user/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above &#039;&#039;Search for group by name&#039;&#039; curl example to return various properties from the search for group &amp;quot;Staff&amp;quot;, e.g.:  &lt;br /&gt;
&lt;br /&gt;
 $ curl ... &amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
 71414&lt;br /&gt;
&lt;br /&gt;
* If you are doing anything serious, it is better to use a scripting language such as python or perl, which has support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
=== Get only user defined groups ===&lt;br /&gt;
&lt;br /&gt;
* The groups query returns all groups by default, including internal groups (e.g. role groups for tickets.) This can return many thousands groups, which is unhelpful. The output also doesn&#039;t contain the group&#039;s name or type. To return only User Defined groups (e.g actual groups users are added to in RT.):&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
* You can also add fields= parameter to the url to include otherwise hidden fields:&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups?fields=Name,Description,Domain,CustomFields --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27235</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27235"/>
		<updated>2025-03-13T12:54:13Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Search for group by name */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
* Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Search for group by name ===&lt;br /&gt;
&lt;br /&gt;
* This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Add User to Group ===&lt;br /&gt;
&lt;br /&gt;
 # Add a user to a group, where group_id is set in a variable, and user name is set in a variable rt_user: &lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/$rt_user/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above &#039;&#039;Search for group by name&#039;&#039; curl example to return various properties from the search for group &amp;quot;Staff&amp;quot;, e.g.:  &lt;br /&gt;
&lt;br /&gt;
 $ curl ... &amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
 71414&lt;br /&gt;
&lt;br /&gt;
* If you are doing anything serious, it is better to use a scripting language such as python or perl, which has support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
=== Get only user defined groups ===&lt;br /&gt;
&lt;br /&gt;
* The groups query returns all groups by default, including internal groups (e.g. role groups for tickets.) This can return many thousands groups, which is unhelpful. The output also doesn&#039;t contain the group&#039;s name or type. To return only User Defined groups (e.g actual groups users are added to in RT.):&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
* You can also add fields= parameter to the url to include otherwise hidden fields:&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups?fields=Name,Description,Domain,CustomFields --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27234</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27234"/>
		<updated>2025-03-13T12:52:14Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Get only user defined groups */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
* Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Search for group by name ===&lt;br /&gt;
&lt;br /&gt;
- This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Add User to Group ===&lt;br /&gt;
&lt;br /&gt;
 # Add a user to a group, where group_id is set in a variable, and user name is set in a variable rt_user: &lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/$rt_user/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above &#039;&#039;Search for group by name&#039;&#039; curl example to return various properties from the search for group &amp;quot;Staff&amp;quot;, e.g.:  &lt;br /&gt;
&lt;br /&gt;
 $ curl ... &amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
 71414&lt;br /&gt;
&lt;br /&gt;
* If you are doing anything serious, it is better to use a scripting language such as python or perl, which has support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
=== Get only user defined groups ===&lt;br /&gt;
&lt;br /&gt;
* The groups query returns all groups by default, including internal groups (e.g. role groups for tickets.) This can return many thousands groups, which is unhelpful. The output also doesn&#039;t contain the group&#039;s name or type. To return only User Defined groups (e.g actual groups users are added to in RT.):&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
* You can also add fields= parameter to the url to include otherwise hidden fields:&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups?fields=Name,Description,Domain,CustomFields --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27233</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27233"/>
		<updated>2025-03-13T12:51:32Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Get only user defined groups */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
* Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Search for group by name ===&lt;br /&gt;
&lt;br /&gt;
- This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Add User to Group ===&lt;br /&gt;
&lt;br /&gt;
 # Add a user to a group, where group_id is set in a variable, and user name is set in a variable rt_user: &lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/$rt_user/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above &#039;&#039;Search for group by name&#039;&#039; curl example to return various properties from the search for group &amp;quot;Staff&amp;quot;, e.g.:  &lt;br /&gt;
&lt;br /&gt;
 $ curl ... &amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
 71414&lt;br /&gt;
&lt;br /&gt;
* If you are doing anything serious, it is better to use a scripting language such as python or perl, which has support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
=== Get only user defined groups ===&lt;br /&gt;
&lt;br /&gt;
* The groups query returns all groups by default, including internal groups (e.g. role groups for tickets.) This can return many thousands groups, which is unhelpful. The output also doesn&#039;t contain the group&#039;s name or type. To return only User Defined groups (e.g actual groups users are added to in RT.)&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
* You can also add fields= parameter to the url to include otherwise hidden fields:&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups?fields=Name,Description,Domain,CustomFields --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27232</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27232"/>
		<updated>2025-03-13T12:51:09Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Add User to Group */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
* Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Search for group by name ===&lt;br /&gt;
&lt;br /&gt;
- This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Add User to Group ===&lt;br /&gt;
&lt;br /&gt;
 # Add a user to a group, where group_id is set in a variable, and user name is set in a variable rt_user: &lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/$rt_user/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above &#039;&#039;Search for group by name&#039;&#039; curl example to return various properties from the search for group &amp;quot;Staff&amp;quot;, e.g.:  &lt;br /&gt;
&lt;br /&gt;
 $ curl ... &amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
 71414&lt;br /&gt;
&lt;br /&gt;
* If you are doing anything serious, it is better to use a scripting language such as python or perl, which has support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
=== Get only user defined groups ===&lt;br /&gt;
&lt;br /&gt;
* The groups query returns all groups by default, including internal groups (e.g. role groups for tickets.) This can return many thousands groups, which is unhelpful. The output also doesn&#039;t contain the group&#039;s name or type. To return only User Defined groups (e.g actual groups users are added to in RT.)&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
You can also add fields= parameter to the url to include otherwise hidden fields:&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups?fields=Name,Description,Domain,CustomFields --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27231</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27231"/>
		<updated>2025-03-13T12:50:47Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Search for group by name */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
* Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Search for group by name ===&lt;br /&gt;
&lt;br /&gt;
- This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Add User to Group ===&lt;br /&gt;
&lt;br /&gt;
 # Add a user to a group, where group_id is set in a variable, and user name is set in a variable rt_user: &lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/$rt_user/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above &#039;&#039;Search for group by name&#039;&#039; curl example to return various properties from the search for group &amp;quot;Staff&amp;quot;, e.g.:  &lt;br /&gt;
&lt;br /&gt;
 $ curl ... &amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
 17474&lt;br /&gt;
&lt;br /&gt;
* If you are doing anything serious, it is better to use a scripting language such as python or perl, which has support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
=== Get only user defined groups ===&lt;br /&gt;
&lt;br /&gt;
* The groups query returns all groups by default, including internal groups (e.g. role groups for tickets.) This can return many thousands groups, which is unhelpful. The output also doesn&#039;t contain the group&#039;s name or type. To return only User Defined groups (e.g actual groups users are added to in RT.)&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
You can also add fields= parameter to the url to include otherwise hidden fields:&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups?fields=Name,Description,Domain,CustomFields --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27230</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27230"/>
		<updated>2025-03-13T12:50:32Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Create New User */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
* Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Search for group by name ===&lt;br /&gt;
&lt;br /&gt;
- This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
 curl --silent &amp;quot;&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Add User to Group ===&lt;br /&gt;
&lt;br /&gt;
 # Add a user to a group, where group_id is set in a variable, and user name is set in a variable rt_user: &lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/$rt_user/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above &#039;&#039;Search for group by name&#039;&#039; curl example to return various properties from the search for group &amp;quot;Staff&amp;quot;, e.g.:  &lt;br /&gt;
&lt;br /&gt;
 $ curl ... &amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
 17474&lt;br /&gt;
&lt;br /&gt;
* If you are doing anything serious, it is better to use a scripting language such as python or perl, which has support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
=== Get only user defined groups ===&lt;br /&gt;
&lt;br /&gt;
* The groups query returns all groups by default, including internal groups (e.g. role groups for tickets.) This can return many thousands groups, which is unhelpful. The output also doesn&#039;t contain the group&#039;s name or type. To return only User Defined groups (e.g actual groups users are added to in RT.)&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
You can also add fields= parameter to the url to include otherwise hidden fields:&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups?fields=Name,Description,Domain,CustomFields --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27229</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27229"/>
		<updated>2025-03-13T12:47:49Z</updated>

		<summary type="html">&lt;p&gt;Robl: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;$RT_URL/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
* Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Search for group by name ===&lt;br /&gt;
&lt;br /&gt;
- This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
 curl --silent &amp;quot;&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Add User to Group ===&lt;br /&gt;
&lt;br /&gt;
 # Add a user to a group, where group_id is set in a variable, and user name is set in a variable rt_user: &lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/$rt_user/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above &#039;&#039;Search for group by name&#039;&#039; curl example to return various properties from the search for group &amp;quot;Staff&amp;quot;, e.g.:  &lt;br /&gt;
&lt;br /&gt;
 $ curl ... &amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
 17474&lt;br /&gt;
&lt;br /&gt;
* If you are doing anything serious, it is better to use a scripting language such as python or perl, which has support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
=== Get only user defined groups ===&lt;br /&gt;
&lt;br /&gt;
* The groups query returns all groups by default, including internal groups (e.g. role groups for tickets.) This can return many thousands groups, which is unhelpful. The output also doesn&#039;t contain the group&#039;s name or type. To return only User Defined groups (e.g actual groups users are added to in RT.)&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
You can also add fields= parameter to the url to include otherwise hidden fields:&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups?fields=Name,Description,Domain,CustomFields --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27228</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27228"/>
		<updated>2025-03-13T12:47:10Z</updated>

		<summary type="html">&lt;p&gt;Robl: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;$RT_URL/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
 * Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Search for group by name ===&lt;br /&gt;
&lt;br /&gt;
- This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
 curl --silent &amp;quot;&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Add User to Group ===&lt;br /&gt;
&lt;br /&gt;
 # Add a user to a group, where group_id is set in a variable, and user name is set in a variable rt_user: &lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/$rt_user/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above &#039;&#039;Search for group by name&#039;&#039; curl example to return various properties from the search for group &amp;quot;Staff&amp;quot;, e.g.:  &lt;br /&gt;
&lt;br /&gt;
 $ curl ... &amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
 17474&lt;br /&gt;
&lt;br /&gt;
* If you are doing anything serious, it is better to use a scripting language such as python or perl, which has support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
=== Get only user defined groups ===&lt;br /&gt;
&lt;br /&gt;
* The groups query returns all groups by default, including internal groups (e.g. role groups for tickets.) This can return many thousands groups, which is unhelpful. The output also doesn&#039;t contain the group&#039;s name or type. To return only User Defined groups (e.g actual groups users are added to in RT.)&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
You can also add fields= parameter to the url to include otherwise hidden fields:&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups?fields=Name,Description,Domain,CustomFields --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27227</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27227"/>
		<updated>2025-03-13T12:44:05Z</updated>

		<summary type="html">&lt;p&gt;Robl: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;$RT_URL/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
- Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Search for group by name ===&lt;br /&gt;
&lt;br /&gt;
- This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
 curl --silent &amp;quot;&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Add User to Group ===&lt;br /&gt;
&lt;br /&gt;
 # Add a user to a group, where group_id is set in a variable, and user name is set in a variable rt_user: &lt;br /&gt;
 curl --silent &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/user/$rt_user/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above curl command to return various properties from the search for group &amp;quot;Staff&amp;quot;  &lt;br /&gt;
&lt;br /&gt;
 $ curl ... &amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
 17474&lt;br /&gt;
&lt;br /&gt;
 * If you are doing anything serious, it is better to use a scripting language such as python or perl, which has support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
=== Get only user defined groups ===&lt;br /&gt;
&lt;br /&gt;
* The groups query returns all groups by default, including internal groups (e.g. role groups for tickets.) This can return many thousands groups, which is unhelpful. The output also doesn&#039;t contain the group&#039;s name or type. To return only User Defined groups (e.g actual groups users are added to in RT.)&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
You can also add fields= parameter to the url to include otherwise hidden fields:&lt;br /&gt;
&lt;br /&gt;
 curl  https://&amp;lt;your_server&amp;gt;/REST/2.0/groups?fields=Name,Description,Domain,CustomFields --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Domain&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;UserDefined&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27226</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27226"/>
		<updated>2025-03-13T12:30:06Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Get group information */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;$RT_URL/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
- Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Get group information ===&lt;br /&gt;
&lt;br /&gt;
- This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
 curl --silent &amp;quot;&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
 # Add user to group, where group_id is set in a variable.&lt;br /&gt;
 curl --silent &amp;quot;$RT_URL/REST/2.0/user/$RT_USER/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above curl command to return various properties from the search for group &amp;quot;Staff&amp;quot;  &lt;br /&gt;
&lt;br /&gt;
 curl ...&amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
&lt;br /&gt;
If you are doing anything serious, it is better to use a scripting language such as python or perl, which has support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27225</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27225"/>
		<updated>2025-03-13T12:29:06Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Get group information */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;$RT_URL/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
- Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Get group information ===&lt;br /&gt;
&lt;br /&gt;
- This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
 curl --silent &amp;quot;&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
 # Add user to group, where group_id is set in a variable.&lt;br /&gt;
 curl --silent &amp;quot;$RT_URL/REST/2.0/user/$RT_USER/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above curl command to return various properties from the search for group &amp;quot;Staff&amp;quot;  &lt;br /&gt;
&lt;br /&gt;
 curl ...&amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
&lt;br /&gt;
If you are doing anything serious, it is better to use a scripting language such as python or perl, which has better support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27224</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27224"/>
		<updated>2025-03-13T12:28:45Z</updated>

		<summary type="html">&lt;p&gt;Robl: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
= RT REST2 (JSON) API =&lt;br /&gt;
&lt;br /&gt;
* RT has &#039;&#039;&#039;two&#039;&#039;&#039; REST APIs - the original [[REST]] v1.0 API and a newer, REST2 [https://en.wikipedia.org/wiki/JSON JSON] API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API uses [https://en.wikipedia.org/wiki/JSON JSON] for queries and responses, making it easier to integrate RT with other systems to read and update RT from scripts and applications on remote servers.&lt;br /&gt;
&lt;br /&gt;
== Releases ==&lt;br /&gt;
&lt;br /&gt;
* The REST2 API was originally provided as extension [https://github.com/bestpractical/rt-extension-rest2 RT::Extension::REST2] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;REST2 is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
== Authentication Tokens ==&lt;br /&gt;
&lt;br /&gt;
* It is recommended to use &#039;&#039;&#039;authentication tokens&#039;&#039;&#039; instead of usernames and passwords for API requests, especially when used in scripts and automation. Authentication should &#039;&#039;&#039;always&#039;&#039;&#039; be done over HTTPS/SSL for security. You should only serve up the /REST/2.0/ endpoint over SSL.&lt;br /&gt;
&lt;br /&gt;
* RT 4.4.x requires the extension [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Authentication Tokens is core in RT 5.0.0 and later&#039;&#039;&#039;, so you do not need the extension for those versions.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;If upgrading from RT 4.4.x to RT 5.0.x and later&#039;&#039;&#039;: A clean install is recommended anyway. If you were using [https://metacpan.org/pod/RT::Authen::Token RT::Authen::Token]. You will need to remove any configuration for RT::Authen::Token and [https://docs.bestpractical.com/rt/5.0.7/UPGRADING-5.0.html#Extensions-Integrated-into-RT-5 follow the migration steps] to migrate your existing tokens to RT5 core.&lt;br /&gt;
&lt;br /&gt;
== Documentation and Examples ==&lt;br /&gt;
&lt;br /&gt;
There are some differences between RT5 core REST2 and earlier RT::Extension::REST2. Refer to the correct documentation for the RT version you are using:&lt;br /&gt;
&lt;br /&gt;
* &amp;gt;= RT 5.0.0: [https://docs.bestpractical.com/rt/latest/RT/REST2.html Official REST2 Documentation] (latest)&lt;br /&gt;
* &amp;gt;= RT 4.4.0: [https://metacpan.org/pod/RT::Extension::REST2 RT::Extension::REST2 Documentation] &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Unfortunately the documentation lacks detailed explanation and examples&#039;&#039;&#039; in many places, especially for anything other than tickets (users, groups etc.). &lt;br /&gt;
&lt;br /&gt;
The purpose of this wiki page is to collect working examples, code snippets and other resources to help work with the API.&lt;br /&gt;
&lt;br /&gt;
== Other sources of help ==&lt;br /&gt;
&lt;br /&gt;
* Search the [https://forum.bestpractical.com/ RT forums] for hints.&lt;br /&gt;
* [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial posted] this very useful [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. The YAML file describes a lot of the endpoints and parameters. You can use something like [https://editor-next.swagger.io/ Swagger] or [https://openapi-generator.tech/ OpenAPI Generator] to generate documentation and client code from this spec. (See below for example)&lt;br /&gt;
* [https://github.com/bestpractical/rt/tree/stable/t/rest2 RT&#039;s test suite files for the REST2 API] is also a very useful reference, both for constructing REST2 API requests and the perl API. Locate the test file for the API operation you need, for example to work with users, look in [https://github.com/bestpractical/rt/blob/stable/t/rest2/users.t users.t] and maybe also [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-memberships.t user-memberships.t] and [https://github.com/bestpractical/rt/blob/stable/t/rest2/user-customfields.t user-customfields.t]. The tests usually create and update objects using the Perl API, then tries to read and manipulate those objects with the corresponding REST2 endpoints and tests for correct responses.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;If you can&#039;t find an answer:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ask in the [https://forum.bestpractical.com/ RT forums] for help.&lt;br /&gt;
* Try asking ChatGPT or AI tools, referring to the above OpenAPI spec and Documentation.&lt;br /&gt;
* Consider alternatives such as the PERL or REST1 APIs.&lt;br /&gt;
* Hire Best Practical to develop a solution. :)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Create New User ===&lt;br /&gt;
&lt;br /&gt;
 curl &amp;quot;$RT_URL/REST/2.0/user&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --no-buffer \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        {&lt;br /&gt;
            &amp;quot;Name&amp;quot;: &amp;quot;newuser2&amp;quot;,&lt;br /&gt;
            &amp;quot;Password&amp;quot;: &amp;quot;password123&amp;quot;,&lt;br /&gt;
            &amp;quot;EmailAddress&amp;quot;: &amp;quot;newuser2@example.com&amp;quot;,&lt;br /&gt;
            &amp;quot;RealName&amp;quot;: &amp;quot;New User&amp;quot;,&lt;br /&gt;
            &amp;quot;Privileged&amp;quot;: 1,&lt;br /&gt;
        }&#039;&lt;br /&gt;
&lt;br /&gt;
- Omitting Privileged will add an unprivileged user by default.&lt;br /&gt;
&lt;br /&gt;
=== Get group information ===&lt;br /&gt;
&lt;br /&gt;
- This example searches for a group &amp;quot;Staff&amp;quot; and returns the details:&lt;br /&gt;
&lt;br /&gt;
curl --silent &amp;quot;&amp;lt;your_server&amp;gt;/REST/2.0/groups&amp;quot; \&lt;br /&gt;
    --request POST \&lt;br /&gt;
    --header &amp;quot;Authorization: token &amp;lt;your_token&amp;gt;&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;&lt;br /&gt;
        [&lt;br /&gt;
            { &amp;quot;field&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
              &amp;quot;value&amp;quot;: &amp;quot;Staff&amp;quot;&lt;br /&gt;
             }&lt;br /&gt;
        ]&#039;&lt;br /&gt;
&lt;br /&gt;
Response:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;pages&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;items&amp;quot;: [&lt;br /&gt;
     {&lt;br /&gt;
       &amp;quot;_url&amp;quot;: &amp;quot;https://&amp;lt;your_server&amp;gt;/REST/2.0/group/71414&amp;quot;,&lt;br /&gt;
       &amp;quot;type&amp;quot;: &amp;quot;group&amp;quot;,&lt;br /&gt;
       &amp;quot;id&amp;quot;: &amp;quot;71414&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
   ],&lt;br /&gt;
   &amp;quot;per_page&amp;quot;: 20,&lt;br /&gt;
   &amp;quot;total&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;count&amp;quot;: 1,&lt;br /&gt;
   &amp;quot;page&amp;quot;: 1&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
 # Add user to group, where group_id is set in a variable.&lt;br /&gt;
 curl --silent &amp;quot;$RT_URL/REST/2.0/user/$RT_USER/groups&amp;quot; \&lt;br /&gt;
    --request PUT \&lt;br /&gt;
    --header &amp;quot;Authorization: token $RT_TOKEN&amp;quot; \&lt;br /&gt;
    --header &amp;quot;Content-Type: application/json&amp;quot; \&lt;br /&gt;
    --data-binary &#039;[&#039;$group_id&#039;]&#039;&lt;br /&gt;
&lt;br /&gt;
* Working with JSON data structures in bash is fiddly. You can use [https://jqlang.org/ jq] to get values from the JSON in a useable format. For example by piping the above curl command to return various properties from the search for group &amp;quot;Staff&amp;quot;  &lt;br /&gt;
&lt;br /&gt;
 curl ...&amp;lt;as above&amp;gt; | jq -r &#039;.items[].id&#039;&lt;br /&gt;
&lt;br /&gt;
If you are doing anything serious, it is better to use a scripting language such as python or perl, which has better support for JSON and nested data structures. But by way of example, I have posted a simple example of a bash script to add a user to a group using the REST2 API: [https://gist.github.com/listerr/d847fe56e7d40fa06840d744762d1bc6 rt-groupadd]&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;br /&gt;
&lt;br /&gt;
== OpenAPI Documentation Generator ==&lt;br /&gt;
&lt;br /&gt;
* The following example installs and runs [https://openapi-generator.tech/docs/installation openapi-generator] on Debian to create documentation based on [https://forum.bestpractical.com/t/rest-2-0-openapi-spec/ Colloquial&#039;s] [https://gitlab-ext.utu.fi/rt/request-tracker-openapi OpenAPI Specification]. There are a number of other ways to install it (see documentation). This was the quickest way for me to install it on my existing RT dev server:&lt;br /&gt;
&lt;br /&gt;
1. Create html directory e.g. /var/www/apidocs and configure your web server to serve this directory from a suitable URL. (optional):&lt;br /&gt;
&lt;br /&gt;
 # mkdir /var/www/apidocs&lt;br /&gt;
&lt;br /&gt;
2. nginx config snippet (goes in the relevent server section). restart nginx after adding:&lt;br /&gt;
&lt;br /&gt;
        # REST2 API Docs&lt;br /&gt;
        location /apidocs {&lt;br /&gt;
                alias /var/www/apidocs;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
4. Install:&lt;br /&gt;
&lt;br /&gt;
  # apt-get install pip default-jre&lt;br /&gt;
  # pip install openapi-generator-cli&lt;br /&gt;
&lt;br /&gt;
5. Generate docs from yaml, e.g:&lt;br /&gt;
&lt;br /&gt;
  # root@support:~# cd /var/www/apidocs&lt;br /&gt;
  # root@support:/var/www/apidocs# wget https://gitlab-ext.utu.fi/rt/request-tracker-openapi/-/raw/main/request_tracker_rest2.yaml&lt;br /&gt;
  # root@support:/var/www/apidocs# openapi-generator-cli generate -g html2 -i https://&amp;lt;your_server&amp;gt;/apidocs/request_tracker_rest2.yaml&lt;br /&gt;
&lt;br /&gt;
6. Browse to your documentation location e.g. https://&amp;lt;your_server&amp;gt;/apidocs&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=Main_Page&amp;diff=27223</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=Main_Page&amp;diff=27223"/>
		<updated>2025-03-06T18:20:26Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Current Sections */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== RT ==&lt;br /&gt;
&lt;br /&gt;
RT is an open source issue tracking and workflow platform developed and supported by [https://bestpractical.com Best Practical Solutions]. This wiki site is a free resource for the RT community to share ideas, configurations, and customizations to RT, RTIR, and related products.&lt;br /&gt;
&lt;br /&gt;
== About RT Documentation ==&lt;br /&gt;
&lt;br /&gt;
In addition to the information found here, you can find RT&#039;s documentation in your install of RT on the server and also [https://bestpractical.com/resources/ on the web at the Best Practical website]. The [https://bestpractical.com/blog Best Practical blog] and the [https://forum.bestpractical.com Community Forum] are also great resources for RT information. Best Practical also offers commercial support services to help get the most out of RT and RTIR.&lt;br /&gt;
&lt;br /&gt;
This site is a [https://en.wikipedia.org/wiki/Wiki wiki], like Wikipedia, which means you can edit it right in your browser. If you use RT and have found it valuable, [[UpdateTheWiki]], post your hints, tricks, tips and tools about how to get the most out of RT. If you currently use RT, please head over to [[RTUsers]] and add yourself and your organization to the user list.&lt;br /&gt;
&lt;br /&gt;
As RT is continually evolving, the documentation and user configurations need to be kept up to date. Various pages on this wiki are marked as Outdated. &lt;br /&gt;
&lt;br /&gt;
If you have a few moments, take a look at [[:Category:Outdated | the pages marked on this wiki as Outdated]] and see what you can update. In some cases, the pages might just need to be linked to an older RT version, in others, the content needs replacing or brought in to line with current practice.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Getting Started with RT ==&lt;br /&gt;
&lt;br /&gt;
If you&#039;re new to RT, start with the [[UserManual]]. We also have a page with [[Videos]] with introductory and more advanced features.&lt;br /&gt;
&lt;br /&gt;
== Current Sections ==&lt;br /&gt;
&lt;br /&gt;
You can go through these pretty much in order if you&#039;re starting from scratch, but please plan to spend some quality time with the [[UserManual]] before you try to go live; RT is big and full of tons of features, and you probably can&#039;t get your head around all of it in one sitting.&lt;br /&gt;
&lt;br /&gt;
* [[UserManual]]&lt;br /&gt;
* [[Demo]]&lt;br /&gt;
* [[REST]] API&lt;br /&gt;
* [[REST2]] JSON API&lt;br /&gt;
&lt;br /&gt;
== Historical Sections ==&lt;br /&gt;
&lt;br /&gt;
RT has been in development for over 20 years, so information on this wiki dates back to many different versions. These sections may contain information on older versions of RT that is no longer completely accurate for modern RT. Pages known to be updated should be moved up to the Current Section.&lt;br /&gt;
&lt;br /&gt;
* [[FAQ]], [[FindingAnswersAboutRT]] and &amp;quot;Common Problems&amp;quot; below.&lt;br /&gt;
* [[BugReporting]]&lt;br /&gt;
* [[HowToHelp]], [[Translation]]&lt;br /&gt;
&lt;br /&gt;
* [[RTGlossary]] &amp;amp;amp; [[Documentation]]&lt;br /&gt;
* [[InstallationGuides]], [[RTTutorials]]&lt;br /&gt;
* [[RT Config|RT_Config]] &amp;amp;amp; [[RT SiteConfig|RT_SiteConfig]]&lt;br /&gt;
&lt;br /&gt;
* [[Extensions]] to RT available as installable plugins&lt;br /&gt;
* [[Contributions]] by the community to customize RT&lt;br /&gt;
* [[RTIR]] RT for Incident Response&lt;br /&gt;
* [[RTFM]] RT FAQ Manager&lt;br /&gt;
&lt;br /&gt;
* [[RTHistory]]&lt;br /&gt;
* [[InThePress]]&lt;br /&gt;
* Customizing with [[CustomizingWithCallbacks|Callbacks]]&lt;br /&gt;
&lt;br /&gt;
=== Common problems ===&lt;br /&gt;
&lt;br /&gt;
* [[CantLocateObject]] - Can&#039;t locate object method &amp;quot;new&amp;quot; via package &amp;quot;&amp;lt;code&amp;gt;RT::Handle&amp;lt;/code&amp;gt;&amp;quot; at rt3/lib/RT.pm&lt;br /&gt;
* [[NoConnectionToSyslog]] - no connection to syslog&lt;br /&gt;
* [[NoRecipFound]] - No recipients found. Not sending&lt;br /&gt;
* Getting information out of RT3 for [[reporting]] or inclusion elsewhere.&lt;br /&gt;
* [[PermissionDenied]] emails|&lt;br /&gt;
* Getting RT4.x to play nice with [[certbot]] to obtain free SSL certificates from [http://letsencrypt.org LetsEncrypt].&lt;br /&gt;
&lt;br /&gt;
== Wish lists ==&lt;br /&gt;
&lt;br /&gt;
* [[WikiWishList]] - wish list for this wiki&lt;br /&gt;
* [[WishList]] - wish list for the RT&lt;br /&gt;
* [[RT3StatisticsPackageWishList]]&lt;br /&gt;
&lt;br /&gt;
== Getting started with MediaWiki ==&lt;br /&gt;
&lt;br /&gt;
To learn more about how to edit wiki pages, click the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Help] link here or in the navigation on the left.&lt;br /&gt;
&lt;br /&gt;
We have installed the Markdown plugin, so you can edit pages in Markdown.&lt;br /&gt;
&lt;br /&gt;
Several years ago, we moved the wiki from Wikia as noted on the [[Wikia Move]] page.&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST&amp;diff=27222</id>
		<title>REST</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST&amp;diff=27222"/>
		<updated>2025-03-06T18:19:33Z</updated>

		<summary type="html">&lt;p&gt;Robl: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Abstract ==&lt;br /&gt;
&lt;br /&gt;
The REST Interface gives you access to your RT Database. The complete communication is encapsulated in the HTTP protocol. The interface should be accessible in your installation.&lt;br /&gt;
&lt;br /&gt;
Though you may see references to older 3.x releases of RT below, the REST 1.0 interface has not changed in any significant way in 4.x.&lt;br /&gt;
&lt;br /&gt;
This page is for REST version 1.0. &lt;br /&gt;
&lt;br /&gt;
The next version of the RT REST interface, version 2.0, is available as an [https://metacpan.org/pod/RT::Extension::REST2 extension] for RT 4.4 and above. &lt;br /&gt;
&lt;br /&gt;
REST2 is core in RT 5.0.0 and above. See: [[REST2]]&lt;br /&gt;
&lt;br /&gt;
[{{SERVER}}/index.php?search={{TALKPAGENAME}}&amp;amp;title=Special:Search Legacy Comments] are available from the previous Wikia instance.&lt;br /&gt;
&lt;br /&gt;
== Interface ==&lt;br /&gt;
&lt;br /&gt;
Base URL: &amp;lt;code&amp;gt;.../REST/1.0/&amp;lt;/code&amp;gt;. The default response should be:&lt;br /&gt;
&lt;br /&gt;
    RT/3.4.5 200 Ok&lt;br /&gt;
&lt;br /&gt;
    # Invalid object specification: &#039;index.html&#039;&lt;br /&gt;
&lt;br /&gt;
    id: index.html&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Authentication ===&lt;br /&gt;
&lt;br /&gt;
The REST Interface does not support HTTP-Authentication. So you must get a valid Session-Token and submit the cookie each request. You usually get a Session-Cookie by submitting the default login form. Use variables &amp;quot;&amp;lt;code&amp;gt;user&amp;lt;/code&amp;gt;&amp;quot; for login and &amp;quot;&amp;lt;code&amp;gt;pass&amp;lt;/code&amp;gt;&amp;quot; for password values. wget doesn&#039;t escape any characters in the --post-data option so make sure you properly escape any special characters in the password.&lt;br /&gt;
&lt;br /&gt;
See the wget invocation line below:&lt;br /&gt;
&lt;br /&gt;
    wget  --keep-session-cookies \&lt;br /&gt;
    --save-cookies cookies.txt \&lt;br /&gt;
    --post-data &#039;user=UUUU&amp;amp;pass=PPPP&#039; \&lt;br /&gt;
    http://my.rt.server&lt;br /&gt;
&lt;br /&gt;
You need the -keep-session-cookies option to make wget save session cookies.&lt;br /&gt;
&lt;br /&gt;
=== Ticket ===&lt;br /&gt;
&lt;br /&gt;
==== Ticket Properties ====&lt;br /&gt;
&lt;br /&gt;
Gets the data for a single ticket, not including the history and comments.&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/show&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 RT/3.4.5 200 Ok&lt;br /&gt;
 &lt;br /&gt;
 id: ticket/&amp;amp;lt;ticket-id&amp;amp;gt;&lt;br /&gt;
 Queue: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Owner: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Creator: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Subject: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Status: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Priority: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 InitialPriority: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 FinalPriority: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Requestors: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Cc: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 AdminCc: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Created: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Starts: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Started: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Due: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Resolved: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Told: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 TimeEstimated: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 TimeWorked: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 TimeLeft: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Ticket Links ====&lt;br /&gt;
&lt;br /&gt;
Gets the ticket links for a single ticket.&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/links/show&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 RT/3.8.2 200 Ok&lt;br /&gt;
 &lt;br /&gt;
 id: ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/links&lt;br /&gt;
 HasMember: fsck.com-rt://your.server.com/ticket/&amp;amp;lt;another-id&amp;amp;gt;&lt;br /&gt;
 ReferredToBy: fsck.com-rt://your.server.com/ticket/&amp;amp;lt;another-id&amp;amp;gt;&lt;br /&gt;
 DependedOnBy: fsck.com-rt://your.server.com/ticket/&amp;amp;lt;another-id&amp;amp;gt;&lt;br /&gt;
 MemberOf: fsck.com-rt://your.server.com/ticket/&amp;amp;lt;another-id&amp;amp;gt;&lt;br /&gt;
 RefersTo: fsck.com-rt://your.server.com/ticket/&amp;amp;lt;another-id&amp;amp;gt;&lt;br /&gt;
 DependsOn: fsck.com-rt://your.server.com/ticket/&amp;amp;lt;another-id&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Ticket Attachments ====&lt;br /&gt;
&lt;br /&gt;
Gets a list of all attachments related to the ticket&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/attachments&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Ticket Attachment ====&lt;br /&gt;
Gets the metadata and content of a specific attachment.&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/attachments/&amp;amp;lt;attachment-id&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
RT/3.8.0 200 Ok&lt;br /&gt;
 &lt;br /&gt;
id: &amp;lt;attachment-id&amp;gt;&lt;br /&gt;
Subject:&lt;br /&gt;
Creator: &amp;lt;user-id&amp;gt;&lt;br /&gt;
Created: &amp;lt;timestamp&amp;gt;&lt;br /&gt;
Transaction: &amp;lt;transaction-id&amp;gt;&lt;br /&gt;
Parent: &amp;lt;parent-id&amp;gt;&lt;br /&gt;
MessageId:&lt;br /&gt;
Filename: &amp;lt;filename&amp;gt;&lt;br /&gt;
ContentType: application/octet-stream&lt;br /&gt;
ContentEncoding: none&lt;br /&gt;
 &lt;br /&gt;
Headers: MIME-Version: 1.0&lt;br /&gt;
         X-Mailer: MIME-tools 5.427 (Entity 5.427)&lt;br /&gt;
         Content-Type: application/octet-stream;&lt;br /&gt;
           name=&amp;quot;&amp;lt;filename&amp;gt;&amp;quot;&lt;br /&gt;
         Content-Disposition: inline; filename=&amp;quot;&amp;lt;filename&amp;gt;&amp;quot;&lt;br /&gt;
         Content-Transfer-Encoding: base64&lt;br /&gt;
         Content-Length: &amp;lt;length in bytes&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Content: ...&lt;br /&gt;
         ...&lt;br /&gt;
         ...&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;NOTE: RT returns the content indented with 9 spaces on each line, so that it lines up with the &amp;quot;Content:&amp;quot; header. Even if you strip this out with a regexp, the content is still UTF-8, which is probably not what you want. To get the original binary data back, strip out the 9 spaces with a regexp, strip off the 3 carriage returns at the end, and then convert the whole thing from UTF-8 to the native character encoding of the attachment, whatever that is. RT doesn&#039;t tell you, so you have know. If the attachments were uploaded by a U.S. Windows system, odds are that Windows-1252 is what you want. If you can&#039;t get the binary back intact, see the next method below.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== Ticket Attachment Content ====&lt;br /&gt;
&lt;br /&gt;
Gets the attachment data content without additional metadata or whitespace characters&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/attachments/&amp;amp;lt;attachment-id&amp;amp;gt;/content&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 RT/3.8.0 200 Ok&lt;br /&gt;
 &lt;br /&gt;
 ...&lt;br /&gt;
 ...&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
So to get the original content you still have to strip the first 2 lines of the response.&lt;br /&gt;
&lt;br /&gt;
==== Ticket History ====&lt;br /&gt;
Gets a list of all the history items for a given ticket.&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/history&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
RT/3.4.5 200 Ok&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;history-count&amp;gt;/&amp;lt;history-count&amp;gt; (/total)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;history-id&amp;gt;: &amp;lt;history-name&amp;gt;&lt;br /&gt;
&amp;lt;history-id&amp;gt;: &amp;lt;history-name&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You will get an additional row, for each history entry found. The first entry is usually: &amp;quot;&amp;lt;code&amp;gt;Ticket created by ...&amp;lt;/code&amp;gt;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
There are two ways to get history item detail: you can do one of these and then recursively perform &amp;lt;code&amp;gt;ticket/history/id/&amp;amp;lt;history-id&amp;amp;gt;&amp;lt;/code&amp;gt; for each history-id from this REST call, but that is extremely wasteful and will scale horribly. What you really want to do is one REST call but get the long format:&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/history?format=l&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
RT/3.8.2 200 Ok&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;n&amp;gt;/&amp;lt;n&amp;gt; (id/&amp;amp;lt;history-id&amp;amp;gt;/total)&lt;br /&gt;
&lt;br /&gt;
id: &amp;lt;history-id&amp;gt;&lt;br /&gt;
Ticket: &amp;lt;ticket-id&amp;gt;&lt;br /&gt;
TimeTaken: &amp;lt;...&amp;gt;&lt;br /&gt;
Type: &amp;lt;...&amp;gt;&lt;br /&gt;
Field: &amp;lt;...&amp;gt;&lt;br /&gt;
OldValue: &amp;lt;...&amp;gt;&lt;br /&gt;
NewValue: &amp;lt;...&amp;gt;&lt;br /&gt;
Data: &amp;lt;...&amp;gt;&lt;br /&gt;
Description: &amp;lt;...&amp;gt;&lt;br /&gt;
Content: &amp;lt;...&amp;gt;&lt;br /&gt;
Creator: &amp;lt;...&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Created: &amp;lt;...&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Attachments:&lt;br /&gt;
             &amp;lt;attachment-id&amp;gt;: &amp;lt;filename&amp;gt; (&amp;lt;size&amp;gt;)&lt;br /&gt;
             &amp;lt;attachment-id&amp;gt;: &amp;lt;filename&amp;gt; (&amp;lt;size&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
--&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;n&amp;gt;/&amp;lt;n&amp;gt; (id/&amp;amp;lt;history-id&amp;amp;gt;/total)&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;NOTE: the double dash &amp;quot;--&amp;quot; will occur in the long format between each history item. You can split the output on &amp;quot;--&amp;quot; and iterate over it, parsing out the data with an RFC822 parser, such as an email handling library.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== Ticket History Entry ====&lt;br /&gt;
Gets the history information for a single history item. Note that the history item must actually correspond to the ticket.&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/history/id/&amp;amp;lt;history-id&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
RT/3.4.5 200 Ok&lt;br /&gt;
&lt;br /&gt;
# 70/70 (id/114856/total)&lt;br /&gt;
&lt;br /&gt;
id: &amp;lt;history-id&amp;gt;&lt;br /&gt;
Ticket: &amp;lt;ticket-id&amp;gt;&lt;br /&gt;
TimeTaken: &amp;lt;...&amp;gt;&lt;br /&gt;
Type: &amp;lt;...&amp;gt;&lt;br /&gt;
Field: &amp;lt;...&amp;gt;&lt;br /&gt;
OldValue: &amp;lt;...&amp;gt;&lt;br /&gt;
NewValue: &amp;lt;...&amp;gt;&lt;br /&gt;
Data: &amp;lt;...&amp;gt;&lt;br /&gt;
Description: &amp;lt;...&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Content: &amp;lt;lin1-0&amp;gt;&lt;br /&gt;
         &amp;lt;line-1&amp;gt;&lt;br /&gt;
         ...&lt;br /&gt;
         &amp;lt;line-n&amp;gt;&lt;br /&gt;
         &lt;br /&gt;
Creator: &amp;lt;...&amp;gt;&lt;br /&gt;
Created: &amp;lt;...&amp;gt;&lt;br /&gt;
Attachments: &amp;lt;...&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;IMPORTANT NOTE: At least with RT 3.8.0, when you request a history item with this method AND you have attached a file that has Mime type text/plain to the same item (eg. a comment with an attachement), RT will return the complete content of the attachment for the key &amp;quot;Content:&amp;quot; and not your real comment that you can see in the web frontend. This may lead to some problems if the requestor does not expect to get a comment content that is for example 1.8 MB of text. With other Mime type attachments this however seems to work. I don&#039;t know if this is a feature or a bug.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039;Ticket Search&#039;&#039;&#039; ====&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/search/ticket?query=&amp;amp;lt;query&amp;amp;gt;&amp;amp;orderby=&amp;amp;lt;sort-order&amp;amp;gt;&amp;amp;format=&amp;amp;lt;format&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;query&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You can use any query generated by the query builder - or feel free to write your own. Here an example that will do the following: Find all tickets that have no owner and the status new or open&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;query= Owner = &#039;Nobody&#039; AND ( Status = &#039;new&#039; OR Status = &#039;open&#039; )&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: to get all the tickets in &amp;quot;fooQueue&amp;quot; you&#039;d access:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;code&amp;gt;/REST/1.0/search/ticket?query=Queue=&#039;fooQueue&#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: to get the tickets for a custom field &amp;quot;Contact Name&amp;quot; you&#039;d access:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;code&amp;gt;/REST/1.0/search/ticket?query=&#039;CF.{Contact Name}&#039;=&#039;Shaun Wallace&#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;orderby&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
By this parameter you can change the sort field and order of the search result. To sort a list ascending just put a + before the fieldname, otherwise a -. Eg: -Created (will put the newest tickets at the beginning).&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
 &amp;lt;code&amp;gt;/REST/1.0/search/ticket?query=Queue=&#039;fooQueue&#039;&amp;amp;orderby=+Created&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;format&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* i: ticket/&amp;amp;lt;ticket-id&amp;amp;gt;&lt;br /&gt;
* s: &amp;amp;lt;ticket-id&amp;amp;gt;: &amp;amp;lt;ticket-subject&amp;amp;gt;&lt;br /&gt;
* l: a multi-line format (Full ticket details without content)&lt;br /&gt;
Example:&lt;br /&gt;
 &amp;lt;code&amp;gt;/REST/1.0/search/ticket?query=Queue=&#039;fooQueue&#039;&amp;amp;orderby=+Created&amp;amp;format=i&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;fields&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A list of fields you would like included in the result set.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
 &amp;lt;code&amp;gt;/REST/1.0/search/ticket?query=id=42&amp;amp;format=l&amp;amp;fields=Subject,Status,Priority,CF.\{Category\}&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that you may need to escape characters like the curly braces for CFs.&lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039;Ticket Create&#039;&#039;&#039; ====&lt;br /&gt;
&lt;br /&gt;
To create a new ticket: post on &amp;lt;code&amp;gt;/REST/1.0/ticket/new&amp;lt;/code&amp;gt; with a variable named &amp;quot;&amp;lt;code&amp;gt;content&amp;lt;/code&amp;gt;&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
containing &amp;quot;&amp;lt;code&amp;gt;key: value&amp;lt;/code&amp;gt;&amp;quot; line by line, example:&lt;br /&gt;
&lt;br /&gt;
Testing the new ticket section&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
id: ticket/new&lt;br /&gt;
Queue: &amp;lt;queue name&amp;gt;&lt;br /&gt;
Requestor: &amp;lt;requestor email address&amp;gt;&lt;br /&gt;
Subject: &amp;lt;subject&amp;gt;&lt;br /&gt;
Cc: &amp;lt;...&amp;gt;&lt;br /&gt;
AdminCc: &amp;lt;...&amp;gt;&lt;br /&gt;
Owner: &amp;lt;...&amp;gt;&lt;br /&gt;
Status: &amp;lt;...&amp;gt;&lt;br /&gt;
Priority: &amp;lt;...&amp;gt;&lt;br /&gt;
InitialPriority: &amp;lt;...&amp;gt;&lt;br /&gt;
FinalPriority: &amp;lt;...&amp;gt;&lt;br /&gt;
TimeEstimated: &amp;lt;...&amp;gt;&lt;br /&gt;
Starts: &amp;lt;...&amp;gt;&lt;br /&gt;
Due: &amp;lt;...&amp;gt;&lt;br /&gt;
Text: &amp;lt;The ticket content&amp;gt;&lt;br /&gt;
CF-&amp;lt;CustomFieldName&amp;gt;: &amp;lt;CustomFieldValue&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If there are any &amp;quot;special&amp;quot; characters (Umlauts, dash, ...?) in a custom field&#039;s name, you can still access it via its ID:&lt;br /&gt;
 CF-$id: &amp;lt;Value&amp;gt;&lt;br /&gt;
If you want to have a multiline Text, prefix every line with a blank.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Due: &amp;lt;...&amp;gt;&lt;br /&gt;
Text: This is &lt;br /&gt;
 a &lt;br /&gt;
 multiline Text&lt;br /&gt;
 !!!&lt;br /&gt;
CF-&amp;lt;CustomFieldName&amp;gt;: &amp;lt;CustomFieldValue&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The response should look like: &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
RT/4.0.6 200 Ok&lt;br /&gt;
&lt;br /&gt;
# Ticket 775 created.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== curl example =====&lt;br /&gt;
    &lt;br /&gt;
  * Create a file containing the ticket form:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
id: ticket/new&lt;br /&gt;
Queue: queue1&lt;br /&gt;
Requestor: requestor@email&lt;br /&gt;
Priority: 4&lt;br /&gt;
CF-Type of request: Demande&lt;br /&gt;
Subject: Test REST&lt;br /&gt;
Text: Multi line&lt;br /&gt;
 test with&lt;br /&gt;
 special chars: é&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  * Submit using curl:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
curl --data-urlencode content@file.name &#039;https://HOSTNAME/REST/1.0/ticket/new?user=USER&amp;amp;pass=PASSWORD&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Ticket Edit ====&lt;br /&gt;
To update an existing ticket: post on &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/edit&amp;lt;/code&amp;gt; with a variable named &amp;quot;content&amp;quot;, containing &amp;quot;key: value&amp;quot; line by line (like the one displayed when issuing &amp;lt;code&amp;gt;ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/show&amp;lt;/code&amp;gt;). Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Priority: 5&lt;br /&gt;
TimeWorked: 15&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
*PHP&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$username = rt_user;&lt;br /&gt;
$password = rt_pass;&lt;br /&gt;
$url = &amp;quot;http://server.domain.tld/REST/1.0/ticket/&amp;lt;ticket id&amp;gt;/edit?user=$username&amp;amp;pass=$password&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
$request = new HttpRequest($url, HTTP_METH_POST);&lt;br /&gt;
$post_data=array(&amp;quot;content&amp;quot;=&amp;gt;&amp;quot;AdminCc: userX\nText: This is a REST test edit ticket\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
// add the post fields  &lt;br /&gt;
$request-&amp;gt;addPostFields($postData);&lt;br /&gt;
&lt;br /&gt;
// response from RT&lt;br /&gt;
$response = $request-&amp;gt;send()-&amp;gt;getBody();&lt;br /&gt;
print_r($response);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tickets History Reply ====&lt;br /&gt;
Same as comment: post on &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/comment&amp;lt;/code&amp;gt; with a variable name &amp;lt;code&amp;gt;content&amp;lt;/code&amp;gt;, containing &amp;quot;&amp;lt;code&amp;gt;key: value&amp;lt;/code&amp;gt;&amp;quot; line by line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
id: &amp;lt;ticket-id&amp;gt;&lt;br /&gt;
Action: correspond&lt;br /&gt;
Text: the text comment&lt;br /&gt;
Cc: &amp;lt;...&amp;gt;&lt;br /&gt;
Bcc: &amp;lt;...&amp;gt;&lt;br /&gt;
TimeWorked: &amp;lt;...&amp;gt;&lt;br /&gt;
Attachment: an attachment filename/path&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Cc&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Bcc&amp;lt;/code&amp;gt; are for this reply only (&#039;&#039;I think&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
==== Ticket History Comment ====&lt;br /&gt;
To add a comment to an existing ticket: POST on &amp;quot;&amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/comment&amp;lt;/code&amp;gt;&amp;quot; with a variable name &amp;quot;&amp;lt;code&amp;gt;content&amp;lt;/code&amp;gt;&amp;quot;, containing &amp;quot;&amp;lt;code&amp;gt;key: value&amp;lt;/code&amp;gt;&amp;quot; line by line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
id: &amp;lt;ticket-id&amp;gt;&lt;br /&gt;
Action: comment&lt;br /&gt;
Text: the text comment&lt;br /&gt;
Attachment: an attachment filename/path&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Action can be &amp;quot;&amp;lt;code&amp;gt;comment&amp;lt;/code&amp;gt;&amp;quot; or &amp;quot;&amp;lt;code&amp;gt;correspond&amp;lt;/code&amp;gt;&amp;quot;. For a list of fields you can use in correspondence, try &amp;quot;&amp;lt;code&amp;gt;/opt/rt3/bin/rt correspond ticket/1&amp;lt;/code&amp;gt;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
If your comment contains multiple lines, each new line must be preceded by a space (e.g. &amp;quot;line 1\n line 2&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
If you want to use HTML replies, use &amp;lt;pre&amp;gt;Content-Type: text/html&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you used &amp;quot;&amp;lt;code&amp;gt;Attachment&amp;lt;/code&amp;gt;&amp;quot;, you must add to your POST a variable &amp;quot;&amp;lt;code&amp;gt;attachment_1&amp;lt;/code&amp;gt;&amp;quot; that contains the raw attachment in multi-part file object.&lt;br /&gt;
&lt;br /&gt;
You can upload more attachments as well, in this case you have to separate the file names in the &amp;quot;&amp;lt;code style=&amp;quot;border-style: initial; border-color: initial; &amp;quot;&amp;gt;Attachment&amp;lt;/code&amp;gt;&amp;quot; with &amp;quot;\n &amp;quot;(a newline and space, without quotes) and add a new variable &amp;quot;attachment_$i&amp;quot; to your POST where $i is the index of attachment.&lt;br /&gt;
&lt;br /&gt;
You need to send header to post comments&lt;br /&gt;
&lt;br /&gt;
==== Ticket Links Edit ====&lt;br /&gt;
To update links on an existing ticket: POST on &amp;quot;&amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/links&amp;lt;/code&amp;gt;&amp;quot; with a variable named &amp;quot;&amp;lt;code&amp;gt;content&amp;lt;/code&amp;gt;&amp;quot;, containing &amp;quot;&amp;lt;code&amp;gt;key: value&amp;lt;/code&amp;gt;&amp;quot; line by line (like the one displayed when issuing &amp;quot;&amp;lt;code&amp;gt;ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/links&amp;lt;/code&amp;gt;&amp;quot;). Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DependsOn: 54354&lt;br /&gt;
RefersTo: http://some.external/link&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Ticket Merge ====&lt;br /&gt;
To merge tickets: POST on &amp;quot;&amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;origin-ticket-id&amp;amp;gt;/merge/&amp;amp;lt;into-ticket-id&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;quot;. (I don&#039;t think any content is required, but I send &amp;quot;id&amp;quot; and &amp;quot;into&amp;quot; anyway.)&lt;br /&gt;
&lt;br /&gt;
=== User Properties ===&lt;br /&gt;
&lt;br /&gt;
Gets the data for a single user.&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/user/&amp;amp;lt;user-id&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 RT/3.8.4 200 Ok&lt;br /&gt;
 &lt;br /&gt;
 id: user/&amp;amp;lt;user-id&amp;amp;gt;&lt;br /&gt;
 Name: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Password: ********&lt;br /&gt;
 EmailAddress: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 RealName: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Organization: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Privileged: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Disabled: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
Also you can use user login instead of user ID.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== User ===&lt;br /&gt;
&lt;br /&gt;
==== User Create ====&lt;br /&gt;
&lt;br /&gt;
To create a new user: post on &amp;lt;code&amp;gt;/REST/1.0/user/new&amp;lt;/code&amp;gt; with a variable named &amp;quot;&amp;lt;code&amp;gt;content&amp;lt;/code&amp;gt;&amp;quot;, containing &amp;quot;&amp;lt;code&amp;gt;key: value&amp;lt;/code&amp;gt;&amp;quot; line by line, like the response to &amp;lt;code&amp;gt;/user/&amp;amp;lt;ticket-id&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== User Edit ====&lt;br /&gt;
To update an existing user: post on &amp;lt;code&amp;gt;/REST/1.0/user/&amp;amp;lt;user-id&amp;amp;gt;/edit&amp;lt;/code&amp;gt; with a variable named &amp;quot;content&amp;quot;, containing &amp;quot;key: value&amp;quot; line by line (like the one displayed when issuing &amp;lt;code&amp;gt;user/&amp;amp;lt;user-id&amp;amp;gt;/show&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
=== Queue ===&lt;br /&gt;
Search for all queues mail addresses like thist:&lt;br /&gt;
  /REST/1.0/search/queue?query=\&amp;amp;fields=CorrespondAddress,CommentAddress&lt;br /&gt;
&lt;br /&gt;
==== Single queue properties ====&lt;br /&gt;
&lt;br /&gt;
Gets the data for a single queue.&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/queue/&amp;amp;lt;queue-id&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 RT/3.8.4 200 Ok&lt;br /&gt;
 &lt;br /&gt;
 id: queue/&amp;amp;lt;queue-id&amp;amp;gt;&lt;br /&gt;
 Name: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Description: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 CorrespondAddress: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 CommentAddress: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 InitialPriority: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 FinalPriority: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 DefaultDueIn: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Logout ===&lt;br /&gt;
&lt;br /&gt;
To logout: post on &amp;lt;code&amp;gt;/REST/1.0/logout&amp;lt;/code&amp;gt; with empty content.&lt;br /&gt;
&lt;br /&gt;
== Types ==&lt;br /&gt;
&lt;br /&gt;
*new&lt;br /&gt;
*open&lt;br /&gt;
*stalled&lt;br /&gt;
*resolved&lt;br /&gt;
*rejected&lt;br /&gt;
*deleted&lt;br /&gt;
+ other custom values defined in you local RT portal.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== History entry type ===&lt;br /&gt;
* Create&lt;br /&gt;
* CustomField&lt;br /&gt;
* EmailRecord&lt;br /&gt;
* Status&lt;br /&gt;
* CommentEmailRecord&lt;br /&gt;
* Correspond&lt;br /&gt;
* Comment&lt;br /&gt;
* Priority&lt;br /&gt;
* Give&lt;br /&gt;
* Steal&lt;br /&gt;
* Take&lt;br /&gt;
* Untake&lt;br /&gt;
* AddWatcher&lt;br /&gt;
* DeleteWatcher&lt;br /&gt;
* AddLink&lt;br /&gt;
* DeleteLink&lt;br /&gt;
* AddReminder&lt;br /&gt;
* OpenReminder&lt;br /&gt;
* ResolveReminder&lt;br /&gt;
* Set&lt;br /&gt;
* Force&lt;br /&gt;
* Subject&lt;br /&gt;
* Told&lt;br /&gt;
* PurgeTransaction&lt;br /&gt;
&lt;br /&gt;
== Miscellaneous ==&lt;br /&gt;
=== Data format ===&lt;br /&gt;
&lt;br /&gt;
* History entries time returns in UTC, boolean returns as &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; (true) and &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;(false). &lt;br /&gt;
&lt;br /&gt;
* Use only &amp;lt;code&amp;gt;&amp;quot;\n&amp;quot;&amp;lt;/code&amp;gt;, not &amp;lt;code&amp;gt;&amp;quot;\r\n&amp;quot;&amp;lt;/code&amp;gt;in post content. &lt;br /&gt;
&lt;br /&gt;
* Comments in response body starts with with a hash (&amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt;) symbol.&lt;br /&gt;
&lt;br /&gt;
=== Request status ===&lt;br /&gt;
To get real request/post status you need to check status code in first line of server response.&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Perl ===&lt;br /&gt;
To get the results of a single request (without setting the Session-Cookie) - assuming you&#039;ve set:&lt;br /&gt;
&lt;br /&gt;
* $uri to your RT REST URL&lt;br /&gt;
* $access_user to your username&lt;br /&gt;
* $access_password to your password&lt;br /&gt;
* $ticketNumber to the ticket you want to see&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
my $ua = LWP::UserAgent-&amp;amp;gt;new;&lt;br /&gt;
$ua-&amp;gt;timeout(10);&lt;br /&gt;
$ua-&amp;gt;agent(&amp;quot;YOURUSERAGENTHERE&amp;quot;);&lt;br /&gt;
 &lt;br /&gt;
my $response = $ua-&amp;gt;post($uri.&amp;quot;ticket/$ticketNumber&amp;quot;,&lt;br /&gt;
   [&#039;user&#039; =&amp;gt; $access_user, &#039;pass&#039; =&amp;gt; $access_password],&lt;br /&gt;
    &#039;Content_Type&#039; =&amp;gt; &#039;form-data&#039;);&lt;br /&gt;
&lt;br /&gt;
if ($response-&amp;amp;gt;is_success) {&lt;br /&gt;
   print $response-&amp;gt;decoded_content;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Java ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import java.io.IOException;&lt;br /&gt;
import org.apache.commons.httpclient.HttpClient;&lt;br /&gt;
import org.apache.commons.httpclient.methods.PostMethod;&lt;br /&gt;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;&lt;br /&gt;
import org.apache.commons.httpclient.methods.multipart.Part;&lt;br /&gt;
import org.apache.commons.httpclient.methods.multipart.StringPart;&lt;br /&gt;
  &lt;br /&gt;
public class RtTicketCreator {&lt;br /&gt;
   static final String BASE_URI = &amp;quot;http://rt.xxx.com/REST/1.0&amp;quot;;&lt;br /&gt;
 &lt;br /&gt;
   public static void main(String[] args) throws IOException {&lt;br /&gt;
 &lt;br /&gt;
      PostMethod mPost = new PostMethod(BASE_URI + &amp;quot;/ticket/new?user=username&amp;amp;amp;pass=password&amp;quot;);&lt;br /&gt;
      Part[] parts = { new StringPart(&amp;quot;content&amp;quot;, &amp;quot;Queue: General\nSubject: 123&amp;quot;) };&lt;br /&gt;
      mPost.setRequestEntity(new MultipartRequestEntity(parts, mPost.getParams()));&lt;br /&gt;
      HttpClient cl = new HttpClient();&lt;br /&gt;
      cl.executeMethod(mPost);&lt;br /&gt;
      System.out.println(mPost.getResponseBodyAsString());&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
=== Java ( Based on new Apache HttpComponents library ) ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
* RT ticket creator based on the current Apache HttpComponents library 4.1.3&lt;br /&gt;
* Created by Koustubha Kale, kmkale at youtility dot in&lt;br /&gt;
*/&lt;br /&gt;
import org.apache.http.HttpEntity;&lt;br /&gt;
import org.apache.http.HttpResponse;&lt;br /&gt;
import org.apache.http.auth.AuthScope;&lt;br /&gt;
import org.apache.http.auth.UsernamePasswordCredentials;&lt;br /&gt;
import org.apache.http.client.HttpClient;&lt;br /&gt;
import org.apache.http.client.methods.HttpPost;&lt;br /&gt;
import org.apache.http.entity.mime.MultipartEntity;&lt;br /&gt;
import org.apache.http.entity.mime.content.StringBody;&lt;br /&gt;
import org.apache.http.impl.client.DefaultHttpClient;&lt;br /&gt;
import org.apache.http.util.EntityUtils;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
public class NewApacheHttpcomponentsRtTicketCreator {&lt;br /&gt;
&lt;br /&gt;
    public static void main(String[] args) throws Exception {&lt;br /&gt;
        &lt;br /&gt;
        DefaultHttpClient httpclient = new DefaultHttpClient();&lt;br /&gt;
	try {&lt;br /&gt;
            &lt;br /&gt;
            HttpPost httppost = new HttpPost(&amp;quot;http://rt.xxx.com/rt/REST/1.0&amp;quot; +&lt;br /&gt;
                    &amp;quot;/ticket/new?user=username&amp;amp;pass=password&amp;quot;);&lt;br /&gt;
	    StringBody content = new StringBody(&amp;quot;Queue: General\nSubject: 123&amp;quot;);&lt;br /&gt;
	    MultipartEntity reqEntity = new MultipartEntity();&lt;br /&gt;
	    reqEntity.addPart(&amp;quot;content&amp;quot;, content);&lt;br /&gt;
	    httppost.setEntity(reqEntity);&lt;br /&gt;
	    System.out.println(&amp;quot;executing request &amp;quot; + httppost.getRequestLine());&lt;br /&gt;
            HttpResponse response = httpclient.execute(httppost);&lt;br /&gt;
            HttpEntity resEntity = response.getEntity();&lt;br /&gt;
&lt;br /&gt;
            System.out.println(&amp;quot;----------------------------------------&amp;quot;);&lt;br /&gt;
            System.out.println(response.getStatusLine());&lt;br /&gt;
            if (resEntity != null) {&lt;br /&gt;
                System.out.println(&amp;quot;Response content length: &amp;quot; + resEntity.getContentLength());&lt;br /&gt;
            }&lt;br /&gt;
            EntityUtils.consume(resEntity);&lt;br /&gt;
        } finally {&lt;br /&gt;
            try { httpclient.getConnectionManager().shutdown(); } catch (Exception ignore) {}&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Python ===&lt;br /&gt;
&amp;lt;SyntaxHighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import cookielib&lt;br /&gt;
import urllib&lt;br /&gt;
import urllib2&lt;br /&gt;
       &lt;br /&gt;
  # creates a cookie for the rtserver with the credentials given at initialization.&lt;br /&gt;
  # define your credentials here&lt;br /&gt;
access_user = &#039;your_login&#039;&lt;br /&gt;
access_password = &#039;your_password&#039;&lt;br /&gt;
       &lt;br /&gt;
 # here is the RequestTracker URI we try to access&lt;br /&gt;
uri = &#039;http://your-rt-instance.com/REST/1.0/&#039;&lt;br /&gt;
       &lt;br /&gt;
 # trying login on rt server&lt;br /&gt;
cj = cookielib.LWPCookieJar()&lt;br /&gt;
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))&lt;br /&gt;
urllib2.install_opener(opener)&lt;br /&gt;
data = {&#039;user&#039;: access_user, &#039;pass&#039;: access_password}&lt;br /&gt;
ldata = urllib.urlencode(data)&lt;br /&gt;
login = urllib2.Request(uri, ldata)&lt;br /&gt;
try:&lt;br /&gt;
   response = urllib2.urlopen(login)&lt;br /&gt;
   print response.read()&lt;br /&gt;
   print &amp;quot;login successful&amp;quot;&lt;br /&gt;
except urllib2.URLError:&lt;br /&gt;
   # could not connect to server&lt;br /&gt;
   print &amp;quot;Not able to login&amp;quot;&lt;br /&gt;
&amp;lt;/SyntaxHighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Ruby ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/bin/env ruby&lt;br /&gt;
require &#039;net/http&#039;&lt;br /&gt;
&lt;br /&gt;
user = &#039;username&#039;&lt;br /&gt;
pass = &#039;password&#039;&lt;br /&gt;
uri = URI(&#039;https://rt.example.com/REST/1.0/ticket/123/show&#039;)&lt;br /&gt;
&lt;br /&gt;
req = Net::HTTP::Post.new(uri.path)&lt;br /&gt;
req.set_form_data(&#039;user&#039; =&amp;gt; user, &#039;pass&#039; =&amp;gt; pass)&lt;br /&gt;
&lt;br /&gt;
res = Net::HTTP.start(uri.hostname, uri.port, &lt;br /&gt;
  :use_ssl =&amp;gt; uri.scheme == &#039;https&#039;, &lt;br /&gt;
  :set_debug_output =&amp;gt; $stderr) do |http|&lt;br /&gt;
  http.request(req)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
case res&lt;br /&gt;
when Net::HTTPSuccess, Net::HTTPRedirection&lt;br /&gt;
  # OK&lt;br /&gt;
  puts &amp;quot;HTTP response code:  #{res.code}&amp;quot;&lt;br /&gt;
  puts &amp;quot;HTTP message: #{res.message}&amp;quot;&lt;br /&gt;
  puts &amp;quot;Response:&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  res.each do |key,val|&lt;br /&gt;
    puts &amp;quot;#{key} =&amp;gt; #{val}&amp;quot;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  puts &amp;quot;Data:&amp;quot;&lt;br /&gt;
  puts res.body&lt;br /&gt;
else&lt;br /&gt;
  res.value&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PHP ===&lt;br /&gt;
&amp;lt;SyntaxHighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$username = rt_user;&lt;br /&gt;
$password = rt_pass;&lt;br /&gt;
$url = &amp;quot;http://server.domain.tld/REST/1.0/ticket/&amp;lt;ticket id&amp;gt;/show?user=$username&amp;amp;pass=$password&amp;quot;;&lt;br /&gt;
$request = new HttpRequest($url, HTTP_METH_GET);&lt;br /&gt;
/* if you want&#039;s to pass additional parameters */&lt;br /&gt;
$request-&amp;gt;addQueryData(array());&lt;br /&gt;
$response = $request-&amp;gt;send();&lt;br /&gt;
print_r($response);&lt;br /&gt;
&amp;lt;/SyntaxHighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
OR&lt;br /&gt;
&lt;br /&gt;
&amp;lt;SyntaxHighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$username = rt_user;&lt;br /&gt;
$password = rt_pass;&lt;br /&gt;
$url = &amp;quot;http://server.domain.tld/REST/1.0/ticket/new?user=$username&amp;amp;pass=$password&amp;quot;;&lt;br /&gt;
$request = new HttpRequest($url, HTTP_METH_POST);&lt;br /&gt;
// Note : to add data in custom fields you need to add element like below example&lt;br /&gt;
// in $post_data array&lt;br /&gt;
// Example : \nCF-customfield1:testdata&lt;br /&gt;
$post_data = array(&lt;br /&gt;
    &amp;quot;content&amp;quot; =&amp;gt; &lt;br /&gt;
        &amp;quot;Queue: General&amp;quot; . PHP_EOL .&lt;br /&gt;
        &amp;quot;Requestor: user@domain&amp;quot; . PHP_EOL .&lt;br /&gt;
        &amp;quot;Subject: REST test 1&amp;quot; . PHP_EOL .&lt;br /&gt;
        &amp;quot;Owner: userX&amp;quot; . PHP_EOL .&lt;br /&gt;
        &amp;quot;AdminCc: userX&amp;quot; . PHP_EOL .&lt;br /&gt;
        &amp;quot;Text: This is a REST test&amp;quot; . PHP_EOL&lt;br /&gt;
);&lt;br /&gt;
$request-&amp;gt;addPostFields($post_data);&lt;br /&gt;
&lt;br /&gt;
try&lt;br /&gt;
{&lt;br /&gt;
    response = $request-&amp;gt;send()-&amp;gt;getBody();&lt;br /&gt;
    print_r($response);&lt;br /&gt;
} catch (HttpException $ex) {&lt;br /&gt;
    echo $ex;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/SyntaxHighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== C# ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
// Using &amp;quot;WCF REST Starter Kit&amp;quot; (http://msdn.microsoft.com/en-us/netframework/cc950529.aspx)&lt;br /&gt;
using (var client = new HttpClient(&amp;quot;http://rt.site.com/REST/1.0/&amp;quot;))&lt;br /&gt;
{&lt;br /&gt;
   client.TransportSettings.Cookies = new CookieContainer();&lt;br /&gt;
&lt;br /&gt;
   var form = new HttpUrlEncodedForm();&lt;br /&gt;
   form.Add(&amp;quot;user&amp;quot;, &amp;quot;LOGIN&amp;quot;);&lt;br /&gt;
   form.Add(&amp;quot;pass&amp;quot;, &amp;quot;PASSWORD&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
   client.Post(string.Empty, form.CreateHttpContent());&lt;br /&gt;
&lt;br /&gt;
   // 1. Get ticket data&lt;br /&gt;
   using (var request = client.Get(&amp;quot;ticket/1234/show&amp;quot;))&lt;br /&gt;
   {&lt;br /&gt;
      string content = request.Content.ReadAsString();&lt;br /&gt;
      // Some logic&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
   // 2. Post ticket reply with attachment&lt;br /&gt;
   var formPost = new HttpMultipartMimeForm();&lt;br /&gt;
   byte[] attachment = new byte[];&lt;br /&gt;
   string content = string.Empty;&lt;br /&gt;
         &lt;br /&gt;
   // Store data in attachment&lt;br /&gt;
   // Store data in content string (&amp;quot;Field: Value&amp;quot; line by line)&lt;br /&gt;
   // ...&lt;br /&gt;
         &lt;br /&gt;
   formPost.Add(&amp;quot;content&amp;quot;, content);&lt;br /&gt;
   formPost.Add(&amp;quot;attachment_1&amp;quot;, &amp;quot;attachment_1&amp;quot;, HttpContent.Create(attachment, &amp;quot;application/octet-stream&amp;quot;));&lt;br /&gt;
                             &lt;br /&gt;
   using (var post = client.Post(&amp;quot;ticket/1234/comment&amp;quot;), formPost.CreateHttpContent()))&lt;br /&gt;
      // Some logic&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Powershell (v3)===&lt;br /&gt;
* Escape Powershell reserved chars in user/pass&lt;br /&gt;
* If connecting via SSL, use fqdn of server, not cname&lt;br /&gt;
*Use `n (newline) when composing new ticket structure&lt;br /&gt;
Read RT queue based on query: &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$servername=&amp;quot;Your Servername here&amp;quot;&lt;br /&gt;
$u=&amp;quot;user=Username&amp;quot;&lt;br /&gt;
$p=&amp;quot;pass=Password&amp;quot;&lt;br /&gt;
$q=&amp;quot;search/ticket?query=(Status=&#039;open&#039; OR Status=&#039;new&#039;) AND (Queue=&#039;GENERAL&#039; OR Queue=&#039;FOO&#039;) AND Owner=&#039;Nobody&#039;&amp;quot;&lt;br /&gt;
$uri=&amp;quot;https://&amp;quot; + $servername + &amp;quot;/rt/REST/1.0/&amp;quot; + $q + &amp;quot;&amp;amp;&amp;quot; + $u + &amp;quot;&amp;amp;&amp;quot; + $p&lt;br /&gt;
$RT=Invoke-WebRequest -Uri  $uri -SessionVariable sess&lt;br /&gt;
$rt.Content&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===VB.NET===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Public Sub AddAttachmentToRT(ByVal url As String, ByVal fileName As String, ByVal filePath As String)&lt;br /&gt;
&lt;br /&gt;
        Dim dataBoundary As String = &amp;quot;--xYzZY&amp;quot;&lt;br /&gt;
        Dim request As HttpWebRequest&lt;br /&gt;
        Dim fileType As String = &amp;quot;image/jpeg&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        &#039;Create a POST web request to the REST interface using the passed URL&lt;br /&gt;
        request = CType(WebRequest.Create(url), HttpWebRequest)&lt;br /&gt;
        request.ContentType = &amp;quot;multipart/form-data; boundary=xYzZY&amp;quot;&lt;br /&gt;
        request.Method = &amp;quot;POST&amp;quot;&lt;br /&gt;
        request.KeepAlive = True&lt;br /&gt;
&lt;br /&gt;
        &#039;Write the request to the requestStream&lt;br /&gt;
        Using requestStream As IO.Stream = request.GetRequestStream()&lt;br /&gt;
&lt;br /&gt;
            &#039;Create a variable &amp;quot;attachment_1&amp;quot; in the POST, specify the file name and file type&lt;br /&gt;
            Dim preAttachment As String = dataBoundary + vbCrLf _&lt;br /&gt;
            + &amp;quot;Content-Disposition: form-data; name=&amp;quot;&amp;quot;attachment_1&amp;quot;&amp;quot;; filename=&amp;quot;&amp;quot;&amp;quot; + fileName + &amp;quot;&amp;quot;&amp;quot;&amp;quot; + vbCrLf _&lt;br /&gt;
            + &amp;quot;Content-Type: &amp;quot; + fileType + vbCrLf _&lt;br /&gt;
            + vbCrLf&lt;br /&gt;
&lt;br /&gt;
            &#039;Convert this preAttachment string to bytes&lt;br /&gt;
            Dim preAttachmentBytes As Byte() = System.Text.Encoding.UTF8.GetBytes(preAttachment)&lt;br /&gt;
&lt;br /&gt;
            &#039;Write this preAttachment string to the stream&lt;br /&gt;
            requestStream.Write(preAttachmentBytes, 0, preAttachmentBytes.Length)&lt;br /&gt;
&lt;br /&gt;
            &#039;Write the file as bytes to the stream by passing its exact location&lt;br /&gt;
            Using fileStream As New IO.FileStream(Server.MapPath(filePath + fileName), IO.FileMode.Open, IO.FileAccess.Read)&lt;br /&gt;
&lt;br /&gt;
                Dim buffer(4096) As Byte&lt;br /&gt;
                Dim bytesRead As Int32 = fileStream.Read(buffer, 0, buffer.Length)&lt;br /&gt;
&lt;br /&gt;
                Do While (bytesRead &amp;gt; 0)&lt;br /&gt;
&lt;br /&gt;
                    requestStream.Write(buffer, 0, bytesRead)&lt;br /&gt;
                    bytesRead = fileStream.Read(buffer, 0, buffer.Length)&lt;br /&gt;
&lt;br /&gt;
                Loop&lt;br /&gt;
&lt;br /&gt;
            End Using&lt;br /&gt;
&lt;br /&gt;
            &#039;Create a variable named content in the POST, specify the attachment name and comment text&lt;br /&gt;
            Dim postAttachment As String = vbCrLf _&lt;br /&gt;
            + dataBoundary + vbCrLf _&lt;br /&gt;
            + &amp;quot;Content-Disposition: form-data; name=&amp;quot;&amp;quot;content&amp;quot;&amp;quot;&amp;quot; + vbCrLf _&lt;br /&gt;
            + vbCrLf _&lt;br /&gt;
            + &amp;quot;Action: comment&amp;quot; + vbLf _&lt;br /&gt;
            + &amp;quot;Attachment: &amp;quot; + fileName + vbCrLf _&lt;br /&gt;
            + &amp;quot;Text: Whatever You Want&amp;quot; + vbCrLf _&lt;br /&gt;
            + vbCrLf _&lt;br /&gt;
            + &amp;quot;--xYzZY--&amp;quot;&lt;br /&gt;
&lt;br /&gt;
            &#039;Convert postAttachment string to bytes&lt;br /&gt;
            Dim postAttachmentBytes As Byte() = System.Text.Encoding.UTF8.GetBytes(postAttachment)&lt;br /&gt;
&lt;br /&gt;
            &#039;Write the postAttachment string to the stream&lt;br /&gt;
            requestStream.Write(postAttachmentBytes, 0, postAttachmentBytes.Length)&lt;br /&gt;
&lt;br /&gt;
        End Using&lt;br /&gt;
&lt;br /&gt;
        Dim response As Net.WebResponse = Nothing&lt;br /&gt;
&lt;br /&gt;
        &#039;Get the response from our REST request to RT&lt;br /&gt;
        &#039;Required to capture response, without this Try-Catch attaching will fail&lt;br /&gt;
        Try&lt;br /&gt;
            response = request.GetResponse()&lt;br /&gt;
&lt;br /&gt;
            Using responseStream As IO.Stream = response.GetResponseStream()&lt;br /&gt;
&lt;br /&gt;
                Using responseReader As New IO.StreamReader(responseStream)&lt;br /&gt;
&lt;br /&gt;
                    Dim responseText = responseReader.ReadToEnd()&lt;br /&gt;
&lt;br /&gt;
                End Using&lt;br /&gt;
&lt;br /&gt;
            End Using&lt;br /&gt;
&lt;br /&gt;
        Catch exception As Net.WebException&lt;br /&gt;
&lt;br /&gt;
            response = exception.Response&lt;br /&gt;
&lt;br /&gt;
            If (response IsNot Nothing) Then&lt;br /&gt;
&lt;br /&gt;
                Using reader As New IO.StreamReader(response.GetResponseStream())&lt;br /&gt;
&lt;br /&gt;
                    Dim responseText = reader.ReadToEnd()&lt;br /&gt;
&lt;br /&gt;
                End Using&lt;br /&gt;
&lt;br /&gt;
                response.Close()&lt;br /&gt;
&lt;br /&gt;
            End If&lt;br /&gt;
&lt;br /&gt;
        Finally&lt;br /&gt;
&lt;br /&gt;
            request = Nothing&lt;br /&gt;
&lt;br /&gt;
        End Try&lt;br /&gt;
&lt;br /&gt;
    End Sub&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
===JavaScript===&lt;br /&gt;
This is an example of updating the RefersTo field for a ticket. Assuming Zepto/jQuery is also being used.&lt;br /&gt;
 function link_back(url, ticket_num, rt) {&lt;br /&gt;
     var data, link;&lt;br /&gt;
     link = &amp;quot;refersto: &amp;quot; + url;&lt;br /&gt;
     data = {&amp;quot;content&amp;quot;: link};&lt;br /&gt;
 &lt;br /&gt;
     $.ajax({&lt;br /&gt;
         type: &amp;quot;POST&amp;quot;,&lt;br /&gt;
         url: rt + ticket_num + &amp;quot;/links&amp;quot;,&lt;br /&gt;
         data: data,&lt;br /&gt;
         success: function (reply) {&lt;br /&gt;
             console.log(reply);&lt;br /&gt;
         }&lt;br /&gt;
     });&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 var url = &amp;quot;https://www.wikipedia.org/&amp;quot;;&lt;br /&gt;
 var ticket_num = &amp;quot;123456&amp;quot;;&lt;br /&gt;
 var rt = &amp;quot;https://some.rt.url/REST/1.0/ticket/&amp;quot;;&lt;br /&gt;
 &lt;br /&gt;
 link_back(url, ticket_num, rt);&lt;br /&gt;
&lt;br /&gt;
== Convenience libraries ==&lt;br /&gt;
&lt;br /&gt;
There are libraries which do much of the low-level work shown in the examples below for you, and provide an easier programming interface for dealing with RT:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Language&lt;br /&gt;
!Package/Module&lt;br /&gt;
!Source&lt;br /&gt;
!Example&lt;br /&gt;
!Note&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;Perl&#039;&#039;&#039;&lt;br /&gt;
|[http://metacpan.org/module/RT::Client::REST RT::Client::REST]&lt;br /&gt;
|[http://rt-client-rest.googlecode.com/svn/trunk/ Google Code]&lt;br /&gt;
|&amp;lt;code&amp;gt;&amp;quot;perl -MCPAN -e install RT::Client::REST&amp;quot;&amp;lt;/code&amp;gt; &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;Ruby&#039;&#039;&#039;&lt;br /&gt;
|[http://rubygems.org/gems/rt-client rt-client]&lt;br /&gt;
|[https://github.com/uidzip/rt-client GitHub]&lt;br /&gt;
|&amp;lt;code&amp;gt;gem install rt-client&amp;lt;/code&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;Ruby&#039;&#039;&#039;&lt;br /&gt;
|[http://rubygems.org/gems/roart Roart]&lt;br /&gt;
|[https://github.com/pjdavis/roart GitHub]&lt;br /&gt;
|&amp;lt;code&amp;gt;gem install roart&amp;lt;/code&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;Python&#039;&#039;&#039;&lt;br /&gt;
|[https://github.com/Rickerd0613/rtapi rtapi]&lt;br /&gt;
|[https://github.com/Rickerd0613/rtapi GitHub]&lt;br /&gt;
|&amp;lt;code&amp;gt;pip install rtapi&amp;lt;/code&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;Python&#039;&#039;&#039;&lt;br /&gt;
|[https://github.com/z4r/python-rtkit rtkit]&lt;br /&gt;
|[https://github.com/z4r/python-rtkit GitHub]&lt;br /&gt;
|&amp;lt;code&amp;gt;pip install python-rtkit&amp;lt;/code&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;Python&#039;&#039;&#039;&lt;br /&gt;
|[https://github.com/CZ-NIC/python-rt python-rt]&lt;br /&gt;
|[https://github.com/CZ-NIC/python-rt Github]&lt;br /&gt;
|&amp;lt;code&amp;gt;pip install rt&amp;lt;/code&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;Java&#039;&#039;&#039;&lt;br /&gt;
|[http://projects.boksa.de/RT-REST/ RT-REST]&lt;br /&gt;
|[https://github.com/bboksa/RT-REST GitHub]&lt;br /&gt;
|&amp;lt;code&amp;gt;N/A&amp;lt;/code&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;PHP&#039;&#039;&#039;&lt;br /&gt;
|[https://github.com/dersam/RTPHPLib RTPHPLib]&lt;br /&gt;
|[https://github.com/dersam/RTPHPLib GitHub]&lt;br /&gt;
|composer require dersam/rt-php-lib&lt;br /&gt;
|&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST&amp;diff=27221</id>
		<title>REST</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST&amp;diff=27221"/>
		<updated>2025-03-06T18:18:07Z</updated>

		<summary type="html">&lt;p&gt;Robl: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Abstract ==&lt;br /&gt;
&lt;br /&gt;
The REST Interface gives you access to your RT Database. The complete communication is encapsulated in the HTTP protocol. The interface should be accessible in your installation.&lt;br /&gt;
&lt;br /&gt;
Though you may see references to older 3.x releases of RT below, the REST 1.0 interface has not changed in any significant way in 4.x.&lt;br /&gt;
&lt;br /&gt;
This page is for REST version 1.0. The next version of the RT REST interface, version 2.0, is available as an [https://metacpan.org/pod/RT::Extension::REST2 extension] for RT 4.4 and above, and is core in RT 5.0.0 and above. See: [[REST2]]&lt;br /&gt;
&lt;br /&gt;
[{{SERVER}}/index.php?search={{TALKPAGENAME}}&amp;amp;title=Special:Search Legacy Comments] are available from the previous Wikia instance.&lt;br /&gt;
&lt;br /&gt;
== Interface ==&lt;br /&gt;
&lt;br /&gt;
Base URL: &amp;lt;code&amp;gt;.../REST/1.0/&amp;lt;/code&amp;gt;. The default response should be:&lt;br /&gt;
&lt;br /&gt;
    RT/3.4.5 200 Ok&lt;br /&gt;
&lt;br /&gt;
    # Invalid object specification: &#039;index.html&#039;&lt;br /&gt;
&lt;br /&gt;
    id: index.html&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Authentication ===&lt;br /&gt;
&lt;br /&gt;
The REST Interface does not support HTTP-Authentication. So you must get a valid Session-Token and submit the cookie each request. You usually get a Session-Cookie by submitting the default login form. Use variables &amp;quot;&amp;lt;code&amp;gt;user&amp;lt;/code&amp;gt;&amp;quot; for login and &amp;quot;&amp;lt;code&amp;gt;pass&amp;lt;/code&amp;gt;&amp;quot; for password values. wget doesn&#039;t escape any characters in the --post-data option so make sure you properly escape any special characters in the password.&lt;br /&gt;
&lt;br /&gt;
See the wget invocation line below:&lt;br /&gt;
&lt;br /&gt;
    wget  --keep-session-cookies \&lt;br /&gt;
    --save-cookies cookies.txt \&lt;br /&gt;
    --post-data &#039;user=UUUU&amp;amp;pass=PPPP&#039; \&lt;br /&gt;
    http://my.rt.server&lt;br /&gt;
&lt;br /&gt;
You need the -keep-session-cookies option to make wget save session cookies.&lt;br /&gt;
&lt;br /&gt;
=== Ticket ===&lt;br /&gt;
&lt;br /&gt;
==== Ticket Properties ====&lt;br /&gt;
&lt;br /&gt;
Gets the data for a single ticket, not including the history and comments.&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/show&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 RT/3.4.5 200 Ok&lt;br /&gt;
 &lt;br /&gt;
 id: ticket/&amp;amp;lt;ticket-id&amp;amp;gt;&lt;br /&gt;
 Queue: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Owner: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Creator: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Subject: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Status: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Priority: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 InitialPriority: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 FinalPriority: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Requestors: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Cc: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 AdminCc: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Created: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Starts: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Started: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Due: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Resolved: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Told: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 TimeEstimated: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 TimeWorked: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 TimeLeft: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Ticket Links ====&lt;br /&gt;
&lt;br /&gt;
Gets the ticket links for a single ticket.&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/links/show&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 RT/3.8.2 200 Ok&lt;br /&gt;
 &lt;br /&gt;
 id: ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/links&lt;br /&gt;
 HasMember: fsck.com-rt://your.server.com/ticket/&amp;amp;lt;another-id&amp;amp;gt;&lt;br /&gt;
 ReferredToBy: fsck.com-rt://your.server.com/ticket/&amp;amp;lt;another-id&amp;amp;gt;&lt;br /&gt;
 DependedOnBy: fsck.com-rt://your.server.com/ticket/&amp;amp;lt;another-id&amp;amp;gt;&lt;br /&gt;
 MemberOf: fsck.com-rt://your.server.com/ticket/&amp;amp;lt;another-id&amp;amp;gt;&lt;br /&gt;
 RefersTo: fsck.com-rt://your.server.com/ticket/&amp;amp;lt;another-id&amp;amp;gt;&lt;br /&gt;
 DependsOn: fsck.com-rt://your.server.com/ticket/&amp;amp;lt;another-id&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Ticket Attachments ====&lt;br /&gt;
&lt;br /&gt;
Gets a list of all attachments related to the ticket&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/attachments&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Ticket Attachment ====&lt;br /&gt;
Gets the metadata and content of a specific attachment.&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/attachments/&amp;amp;lt;attachment-id&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
RT/3.8.0 200 Ok&lt;br /&gt;
 &lt;br /&gt;
id: &amp;lt;attachment-id&amp;gt;&lt;br /&gt;
Subject:&lt;br /&gt;
Creator: &amp;lt;user-id&amp;gt;&lt;br /&gt;
Created: &amp;lt;timestamp&amp;gt;&lt;br /&gt;
Transaction: &amp;lt;transaction-id&amp;gt;&lt;br /&gt;
Parent: &amp;lt;parent-id&amp;gt;&lt;br /&gt;
MessageId:&lt;br /&gt;
Filename: &amp;lt;filename&amp;gt;&lt;br /&gt;
ContentType: application/octet-stream&lt;br /&gt;
ContentEncoding: none&lt;br /&gt;
 &lt;br /&gt;
Headers: MIME-Version: 1.0&lt;br /&gt;
         X-Mailer: MIME-tools 5.427 (Entity 5.427)&lt;br /&gt;
         Content-Type: application/octet-stream;&lt;br /&gt;
           name=&amp;quot;&amp;lt;filename&amp;gt;&amp;quot;&lt;br /&gt;
         Content-Disposition: inline; filename=&amp;quot;&amp;lt;filename&amp;gt;&amp;quot;&lt;br /&gt;
         Content-Transfer-Encoding: base64&lt;br /&gt;
         Content-Length: &amp;lt;length in bytes&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Content: ...&lt;br /&gt;
         ...&lt;br /&gt;
         ...&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;NOTE: RT returns the content indented with 9 spaces on each line, so that it lines up with the &amp;quot;Content:&amp;quot; header. Even if you strip this out with a regexp, the content is still UTF-8, which is probably not what you want. To get the original binary data back, strip out the 9 spaces with a regexp, strip off the 3 carriage returns at the end, and then convert the whole thing from UTF-8 to the native character encoding of the attachment, whatever that is. RT doesn&#039;t tell you, so you have know. If the attachments were uploaded by a U.S. Windows system, odds are that Windows-1252 is what you want. If you can&#039;t get the binary back intact, see the next method below.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== Ticket Attachment Content ====&lt;br /&gt;
&lt;br /&gt;
Gets the attachment data content without additional metadata or whitespace characters&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/attachments/&amp;amp;lt;attachment-id&amp;amp;gt;/content&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 RT/3.8.0 200 Ok&lt;br /&gt;
 &lt;br /&gt;
 ...&lt;br /&gt;
 ...&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
So to get the original content you still have to strip the first 2 lines of the response.&lt;br /&gt;
&lt;br /&gt;
==== Ticket History ====&lt;br /&gt;
Gets a list of all the history items for a given ticket.&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/history&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
RT/3.4.5 200 Ok&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;history-count&amp;gt;/&amp;lt;history-count&amp;gt; (/total)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;history-id&amp;gt;: &amp;lt;history-name&amp;gt;&lt;br /&gt;
&amp;lt;history-id&amp;gt;: &amp;lt;history-name&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You will get an additional row, for each history entry found. The first entry is usually: &amp;quot;&amp;lt;code&amp;gt;Ticket created by ...&amp;lt;/code&amp;gt;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
There are two ways to get history item detail: you can do one of these and then recursively perform &amp;lt;code&amp;gt;ticket/history/id/&amp;amp;lt;history-id&amp;amp;gt;&amp;lt;/code&amp;gt; for each history-id from this REST call, but that is extremely wasteful and will scale horribly. What you really want to do is one REST call but get the long format:&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/history?format=l&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
RT/3.8.2 200 Ok&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;n&amp;gt;/&amp;lt;n&amp;gt; (id/&amp;amp;lt;history-id&amp;amp;gt;/total)&lt;br /&gt;
&lt;br /&gt;
id: &amp;lt;history-id&amp;gt;&lt;br /&gt;
Ticket: &amp;lt;ticket-id&amp;gt;&lt;br /&gt;
TimeTaken: &amp;lt;...&amp;gt;&lt;br /&gt;
Type: &amp;lt;...&amp;gt;&lt;br /&gt;
Field: &amp;lt;...&amp;gt;&lt;br /&gt;
OldValue: &amp;lt;...&amp;gt;&lt;br /&gt;
NewValue: &amp;lt;...&amp;gt;&lt;br /&gt;
Data: &amp;lt;...&amp;gt;&lt;br /&gt;
Description: &amp;lt;...&amp;gt;&lt;br /&gt;
Content: &amp;lt;...&amp;gt;&lt;br /&gt;
Creator: &amp;lt;...&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Created: &amp;lt;...&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Attachments:&lt;br /&gt;
             &amp;lt;attachment-id&amp;gt;: &amp;lt;filename&amp;gt; (&amp;lt;size&amp;gt;)&lt;br /&gt;
             &amp;lt;attachment-id&amp;gt;: &amp;lt;filename&amp;gt; (&amp;lt;size&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
--&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;n&amp;gt;/&amp;lt;n&amp;gt; (id/&amp;amp;lt;history-id&amp;amp;gt;/total)&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;NOTE: the double dash &amp;quot;--&amp;quot; will occur in the long format between each history item. You can split the output on &amp;quot;--&amp;quot; and iterate over it, parsing out the data with an RFC822 parser, such as an email handling library.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== Ticket History Entry ====&lt;br /&gt;
Gets the history information for a single history item. Note that the history item must actually correspond to the ticket.&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/history/id/&amp;amp;lt;history-id&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
RT/3.4.5 200 Ok&lt;br /&gt;
&lt;br /&gt;
# 70/70 (id/114856/total)&lt;br /&gt;
&lt;br /&gt;
id: &amp;lt;history-id&amp;gt;&lt;br /&gt;
Ticket: &amp;lt;ticket-id&amp;gt;&lt;br /&gt;
TimeTaken: &amp;lt;...&amp;gt;&lt;br /&gt;
Type: &amp;lt;...&amp;gt;&lt;br /&gt;
Field: &amp;lt;...&amp;gt;&lt;br /&gt;
OldValue: &amp;lt;...&amp;gt;&lt;br /&gt;
NewValue: &amp;lt;...&amp;gt;&lt;br /&gt;
Data: &amp;lt;...&amp;gt;&lt;br /&gt;
Description: &amp;lt;...&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Content: &amp;lt;lin1-0&amp;gt;&lt;br /&gt;
         &amp;lt;line-1&amp;gt;&lt;br /&gt;
         ...&lt;br /&gt;
         &amp;lt;line-n&amp;gt;&lt;br /&gt;
         &lt;br /&gt;
Creator: &amp;lt;...&amp;gt;&lt;br /&gt;
Created: &amp;lt;...&amp;gt;&lt;br /&gt;
Attachments: &amp;lt;...&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;IMPORTANT NOTE: At least with RT 3.8.0, when you request a history item with this method AND you have attached a file that has Mime type text/plain to the same item (eg. a comment with an attachement), RT will return the complete content of the attachment for the key &amp;quot;Content:&amp;quot; and not your real comment that you can see in the web frontend. This may lead to some problems if the requestor does not expect to get a comment content that is for example 1.8 MB of text. With other Mime type attachments this however seems to work. I don&#039;t know if this is a feature or a bug.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039;Ticket Search&#039;&#039;&#039; ====&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/search/ticket?query=&amp;amp;lt;query&amp;amp;gt;&amp;amp;orderby=&amp;amp;lt;sort-order&amp;amp;gt;&amp;amp;format=&amp;amp;lt;format&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Parameters&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;query&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You can use any query generated by the query builder - or feel free to write your own. Here an example that will do the following: Find all tickets that have no owner and the status new or open&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;query= Owner = &#039;Nobody&#039; AND ( Status = &#039;new&#039; OR Status = &#039;open&#039; )&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: to get all the tickets in &amp;quot;fooQueue&amp;quot; you&#039;d access:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;code&amp;gt;/REST/1.0/search/ticket?query=Queue=&#039;fooQueue&#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example: to get the tickets for a custom field &amp;quot;Contact Name&amp;quot; you&#039;d access:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;code&amp;gt;/REST/1.0/search/ticket?query=&#039;CF.{Contact Name}&#039;=&#039;Shaun Wallace&#039;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;orderby&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
By this parameter you can change the sort field and order of the search result. To sort a list ascending just put a + before the fieldname, otherwise a -. Eg: -Created (will put the newest tickets at the beginning).&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
 &amp;lt;code&amp;gt;/REST/1.0/search/ticket?query=Queue=&#039;fooQueue&#039;&amp;amp;orderby=+Created&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;format&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* i: ticket/&amp;amp;lt;ticket-id&amp;amp;gt;&lt;br /&gt;
* s: &amp;amp;lt;ticket-id&amp;amp;gt;: &amp;amp;lt;ticket-subject&amp;amp;gt;&lt;br /&gt;
* l: a multi-line format (Full ticket details without content)&lt;br /&gt;
Example:&lt;br /&gt;
 &amp;lt;code&amp;gt;/REST/1.0/search/ticket?query=Queue=&#039;fooQueue&#039;&amp;amp;orderby=+Created&amp;amp;format=i&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;fields&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
A list of fields you would like included in the result set.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
 &amp;lt;code&amp;gt;/REST/1.0/search/ticket?query=id=42&amp;amp;format=l&amp;amp;fields=Subject,Status,Priority,CF.\{Category\}&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that you may need to escape characters like the curly braces for CFs.&lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039;Ticket Create&#039;&#039;&#039; ====&lt;br /&gt;
&lt;br /&gt;
To create a new ticket: post on &amp;lt;code&amp;gt;/REST/1.0/ticket/new&amp;lt;/code&amp;gt; with a variable named &amp;quot;&amp;lt;code&amp;gt;content&amp;lt;/code&amp;gt;&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
containing &amp;quot;&amp;lt;code&amp;gt;key: value&amp;lt;/code&amp;gt;&amp;quot; line by line, example:&lt;br /&gt;
&lt;br /&gt;
Testing the new ticket section&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
id: ticket/new&lt;br /&gt;
Queue: &amp;lt;queue name&amp;gt;&lt;br /&gt;
Requestor: &amp;lt;requestor email address&amp;gt;&lt;br /&gt;
Subject: &amp;lt;subject&amp;gt;&lt;br /&gt;
Cc: &amp;lt;...&amp;gt;&lt;br /&gt;
AdminCc: &amp;lt;...&amp;gt;&lt;br /&gt;
Owner: &amp;lt;...&amp;gt;&lt;br /&gt;
Status: &amp;lt;...&amp;gt;&lt;br /&gt;
Priority: &amp;lt;...&amp;gt;&lt;br /&gt;
InitialPriority: &amp;lt;...&amp;gt;&lt;br /&gt;
FinalPriority: &amp;lt;...&amp;gt;&lt;br /&gt;
TimeEstimated: &amp;lt;...&amp;gt;&lt;br /&gt;
Starts: &amp;lt;...&amp;gt;&lt;br /&gt;
Due: &amp;lt;...&amp;gt;&lt;br /&gt;
Text: &amp;lt;The ticket content&amp;gt;&lt;br /&gt;
CF-&amp;lt;CustomFieldName&amp;gt;: &amp;lt;CustomFieldValue&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If there are any &amp;quot;special&amp;quot; characters (Umlauts, dash, ...?) in a custom field&#039;s name, you can still access it via its ID:&lt;br /&gt;
 CF-$id: &amp;lt;Value&amp;gt;&lt;br /&gt;
If you want to have a multiline Text, prefix every line with a blank.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Due: &amp;lt;...&amp;gt;&lt;br /&gt;
Text: This is &lt;br /&gt;
 a &lt;br /&gt;
 multiline Text&lt;br /&gt;
 !!!&lt;br /&gt;
CF-&amp;lt;CustomFieldName&amp;gt;: &amp;lt;CustomFieldValue&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The response should look like: &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
RT/4.0.6 200 Ok&lt;br /&gt;
&lt;br /&gt;
# Ticket 775 created.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== curl example =====&lt;br /&gt;
    &lt;br /&gt;
  * Create a file containing the ticket form:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
id: ticket/new&lt;br /&gt;
Queue: queue1&lt;br /&gt;
Requestor: requestor@email&lt;br /&gt;
Priority: 4&lt;br /&gt;
CF-Type of request: Demande&lt;br /&gt;
Subject: Test REST&lt;br /&gt;
Text: Multi line&lt;br /&gt;
 test with&lt;br /&gt;
 special chars: é&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
  * Submit using curl:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
curl --data-urlencode content@file.name &#039;https://HOSTNAME/REST/1.0/ticket/new?user=USER&amp;amp;pass=PASSWORD&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Ticket Edit ====&lt;br /&gt;
To update an existing ticket: post on &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/edit&amp;lt;/code&amp;gt; with a variable named &amp;quot;content&amp;quot;, containing &amp;quot;key: value&amp;quot; line by line (like the one displayed when issuing &amp;lt;code&amp;gt;ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/show&amp;lt;/code&amp;gt;). Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Priority: 5&lt;br /&gt;
TimeWorked: 15&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
*PHP&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$username = rt_user;&lt;br /&gt;
$password = rt_pass;&lt;br /&gt;
$url = &amp;quot;http://server.domain.tld/REST/1.0/ticket/&amp;lt;ticket id&amp;gt;/edit?user=$username&amp;amp;pass=$password&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
$request = new HttpRequest($url, HTTP_METH_POST);&lt;br /&gt;
$post_data=array(&amp;quot;content&amp;quot;=&amp;gt;&amp;quot;AdminCc: userX\nText: This is a REST test edit ticket\n&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
// add the post fields  &lt;br /&gt;
$request-&amp;gt;addPostFields($postData);&lt;br /&gt;
&lt;br /&gt;
// response from RT&lt;br /&gt;
$response = $request-&amp;gt;send()-&amp;gt;getBody();&lt;br /&gt;
print_r($response);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Tickets History Reply ====&lt;br /&gt;
Same as comment: post on &amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/comment&amp;lt;/code&amp;gt; with a variable name &amp;lt;code&amp;gt;content&amp;lt;/code&amp;gt;, containing &amp;quot;&amp;lt;code&amp;gt;key: value&amp;lt;/code&amp;gt;&amp;quot; line by line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
id: &amp;lt;ticket-id&amp;gt;&lt;br /&gt;
Action: correspond&lt;br /&gt;
Text: the text comment&lt;br /&gt;
Cc: &amp;lt;...&amp;gt;&lt;br /&gt;
Bcc: &amp;lt;...&amp;gt;&lt;br /&gt;
TimeWorked: &amp;lt;...&amp;gt;&lt;br /&gt;
Attachment: an attachment filename/path&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;Cc&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Bcc&amp;lt;/code&amp;gt; are for this reply only (&#039;&#039;I think&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
==== Ticket History Comment ====&lt;br /&gt;
To add a comment to an existing ticket: POST on &amp;quot;&amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/comment&amp;lt;/code&amp;gt;&amp;quot; with a variable name &amp;quot;&amp;lt;code&amp;gt;content&amp;lt;/code&amp;gt;&amp;quot;, containing &amp;quot;&amp;lt;code&amp;gt;key: value&amp;lt;/code&amp;gt;&amp;quot; line by line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
id: &amp;lt;ticket-id&amp;gt;&lt;br /&gt;
Action: comment&lt;br /&gt;
Text: the text comment&lt;br /&gt;
Attachment: an attachment filename/path&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Action can be &amp;quot;&amp;lt;code&amp;gt;comment&amp;lt;/code&amp;gt;&amp;quot; or &amp;quot;&amp;lt;code&amp;gt;correspond&amp;lt;/code&amp;gt;&amp;quot;. For a list of fields you can use in correspondence, try &amp;quot;&amp;lt;code&amp;gt;/opt/rt3/bin/rt correspond ticket/1&amp;lt;/code&amp;gt;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
If your comment contains multiple lines, each new line must be preceded by a space (e.g. &amp;quot;line 1\n line 2&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
If you want to use HTML replies, use &amp;lt;pre&amp;gt;Content-Type: text/html&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you used &amp;quot;&amp;lt;code&amp;gt;Attachment&amp;lt;/code&amp;gt;&amp;quot;, you must add to your POST a variable &amp;quot;&amp;lt;code&amp;gt;attachment_1&amp;lt;/code&amp;gt;&amp;quot; that contains the raw attachment in multi-part file object.&lt;br /&gt;
&lt;br /&gt;
You can upload more attachments as well, in this case you have to separate the file names in the &amp;quot;&amp;lt;code style=&amp;quot;border-style: initial; border-color: initial; &amp;quot;&amp;gt;Attachment&amp;lt;/code&amp;gt;&amp;quot; with &amp;quot;\n &amp;quot;(a newline and space, without quotes) and add a new variable &amp;quot;attachment_$i&amp;quot; to your POST where $i is the index of attachment.&lt;br /&gt;
&lt;br /&gt;
You need to send header to post comments&lt;br /&gt;
&lt;br /&gt;
==== Ticket Links Edit ====&lt;br /&gt;
To update links on an existing ticket: POST on &amp;quot;&amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/links&amp;lt;/code&amp;gt;&amp;quot; with a variable named &amp;quot;&amp;lt;code&amp;gt;content&amp;lt;/code&amp;gt;&amp;quot;, containing &amp;quot;&amp;lt;code&amp;gt;key: value&amp;lt;/code&amp;gt;&amp;quot; line by line (like the one displayed when issuing &amp;quot;&amp;lt;code&amp;gt;ticket/&amp;amp;lt;ticket-id&amp;amp;gt;/links&amp;lt;/code&amp;gt;&amp;quot;). Example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DependsOn: 54354&lt;br /&gt;
RefersTo: http://some.external/link&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Ticket Merge ====&lt;br /&gt;
To merge tickets: POST on &amp;quot;&amp;lt;code&amp;gt;/REST/1.0/ticket/&amp;amp;lt;origin-ticket-id&amp;amp;gt;/merge/&amp;amp;lt;into-ticket-id&amp;amp;gt;&amp;lt;/code&amp;gt;&amp;quot;. (I don&#039;t think any content is required, but I send &amp;quot;id&amp;quot; and &amp;quot;into&amp;quot; anyway.)&lt;br /&gt;
&lt;br /&gt;
=== User Properties ===&lt;br /&gt;
&lt;br /&gt;
Gets the data for a single user.&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/user/&amp;amp;lt;user-id&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 RT/3.8.4 200 Ok&lt;br /&gt;
 &lt;br /&gt;
 id: user/&amp;amp;lt;user-id&amp;amp;gt;&lt;br /&gt;
 Name: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Password: ********&lt;br /&gt;
 EmailAddress: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 RealName: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Organization: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Privileged: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Disabled: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
Also you can use user login instead of user ID.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== User ===&lt;br /&gt;
&lt;br /&gt;
==== User Create ====&lt;br /&gt;
&lt;br /&gt;
To create a new user: post on &amp;lt;code&amp;gt;/REST/1.0/user/new&amp;lt;/code&amp;gt; with a variable named &amp;quot;&amp;lt;code&amp;gt;content&amp;lt;/code&amp;gt;&amp;quot;, containing &amp;quot;&amp;lt;code&amp;gt;key: value&amp;lt;/code&amp;gt;&amp;quot; line by line, like the response to &amp;lt;code&amp;gt;/user/&amp;amp;lt;ticket-id&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== User Edit ====&lt;br /&gt;
To update an existing user: post on &amp;lt;code&amp;gt;/REST/1.0/user/&amp;amp;lt;user-id&amp;amp;gt;/edit&amp;lt;/code&amp;gt; with a variable named &amp;quot;content&amp;quot;, containing &amp;quot;key: value&amp;quot; line by line (like the one displayed when issuing &amp;lt;code&amp;gt;user/&amp;amp;lt;user-id&amp;amp;gt;/show&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
=== Queue ===&lt;br /&gt;
Search for all queues mail addresses like thist:&lt;br /&gt;
  /REST/1.0/search/queue?query=\&amp;amp;fields=CorrespondAddress,CommentAddress&lt;br /&gt;
&lt;br /&gt;
==== Single queue properties ====&lt;br /&gt;
&lt;br /&gt;
Gets the data for a single queue.&lt;br /&gt;
&lt;br /&gt;
Request: &amp;lt;code&amp;gt;/REST/1.0/queue/&amp;amp;lt;queue-id&amp;amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 RT/3.8.4 200 Ok&lt;br /&gt;
 &lt;br /&gt;
 id: queue/&amp;amp;lt;queue-id&amp;amp;gt;&lt;br /&gt;
 Name: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 Description: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 CorrespondAddress: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 CommentAddress: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 InitialPriority: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 FinalPriority: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
 DefaultDueIn: &amp;amp;lt;...&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Logout ===&lt;br /&gt;
&lt;br /&gt;
To logout: post on &amp;lt;code&amp;gt;/REST/1.0/logout&amp;lt;/code&amp;gt; with empty content.&lt;br /&gt;
&lt;br /&gt;
== Types ==&lt;br /&gt;
&lt;br /&gt;
*new&lt;br /&gt;
*open&lt;br /&gt;
*stalled&lt;br /&gt;
*resolved&lt;br /&gt;
*rejected&lt;br /&gt;
*deleted&lt;br /&gt;
+ other custom values defined in you local RT portal.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== History entry type ===&lt;br /&gt;
* Create&lt;br /&gt;
* CustomField&lt;br /&gt;
* EmailRecord&lt;br /&gt;
* Status&lt;br /&gt;
* CommentEmailRecord&lt;br /&gt;
* Correspond&lt;br /&gt;
* Comment&lt;br /&gt;
* Priority&lt;br /&gt;
* Give&lt;br /&gt;
* Steal&lt;br /&gt;
* Take&lt;br /&gt;
* Untake&lt;br /&gt;
* AddWatcher&lt;br /&gt;
* DeleteWatcher&lt;br /&gt;
* AddLink&lt;br /&gt;
* DeleteLink&lt;br /&gt;
* AddReminder&lt;br /&gt;
* OpenReminder&lt;br /&gt;
* ResolveReminder&lt;br /&gt;
* Set&lt;br /&gt;
* Force&lt;br /&gt;
* Subject&lt;br /&gt;
* Told&lt;br /&gt;
* PurgeTransaction&lt;br /&gt;
&lt;br /&gt;
== Miscellaneous ==&lt;br /&gt;
=== Data format ===&lt;br /&gt;
&lt;br /&gt;
* History entries time returns in UTC, boolean returns as &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; (true) and &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;(false). &lt;br /&gt;
&lt;br /&gt;
* Use only &amp;lt;code&amp;gt;&amp;quot;\n&amp;quot;&amp;lt;/code&amp;gt;, not &amp;lt;code&amp;gt;&amp;quot;\r\n&amp;quot;&amp;lt;/code&amp;gt;in post content. &lt;br /&gt;
&lt;br /&gt;
* Comments in response body starts with with a hash (&amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt;) symbol.&lt;br /&gt;
&lt;br /&gt;
=== Request status ===&lt;br /&gt;
To get real request/post status you need to check status code in first line of server response.&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
=== Perl ===&lt;br /&gt;
To get the results of a single request (without setting the Session-Cookie) - assuming you&#039;ve set:&lt;br /&gt;
&lt;br /&gt;
* $uri to your RT REST URL&lt;br /&gt;
* $access_user to your username&lt;br /&gt;
* $access_password to your password&lt;br /&gt;
* $ticketNumber to the ticket you want to see&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
my $ua = LWP::UserAgent-&amp;amp;gt;new;&lt;br /&gt;
$ua-&amp;gt;timeout(10);&lt;br /&gt;
$ua-&amp;gt;agent(&amp;quot;YOURUSERAGENTHERE&amp;quot;);&lt;br /&gt;
 &lt;br /&gt;
my $response = $ua-&amp;gt;post($uri.&amp;quot;ticket/$ticketNumber&amp;quot;,&lt;br /&gt;
   [&#039;user&#039; =&amp;gt; $access_user, &#039;pass&#039; =&amp;gt; $access_password],&lt;br /&gt;
    &#039;Content_Type&#039; =&amp;gt; &#039;form-data&#039;);&lt;br /&gt;
&lt;br /&gt;
if ($response-&amp;amp;gt;is_success) {&lt;br /&gt;
   print $response-&amp;gt;decoded_content;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Java ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
import java.io.IOException;&lt;br /&gt;
import org.apache.commons.httpclient.HttpClient;&lt;br /&gt;
import org.apache.commons.httpclient.methods.PostMethod;&lt;br /&gt;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;&lt;br /&gt;
import org.apache.commons.httpclient.methods.multipart.Part;&lt;br /&gt;
import org.apache.commons.httpclient.methods.multipart.StringPart;&lt;br /&gt;
  &lt;br /&gt;
public class RtTicketCreator {&lt;br /&gt;
   static final String BASE_URI = &amp;quot;http://rt.xxx.com/REST/1.0&amp;quot;;&lt;br /&gt;
 &lt;br /&gt;
   public static void main(String[] args) throws IOException {&lt;br /&gt;
 &lt;br /&gt;
      PostMethod mPost = new PostMethod(BASE_URI + &amp;quot;/ticket/new?user=username&amp;amp;amp;pass=password&amp;quot;);&lt;br /&gt;
      Part[] parts = { new StringPart(&amp;quot;content&amp;quot;, &amp;quot;Queue: General\nSubject: 123&amp;quot;) };&lt;br /&gt;
      mPost.setRequestEntity(new MultipartRequestEntity(parts, mPost.getParams()));&lt;br /&gt;
      HttpClient cl = new HttpClient();&lt;br /&gt;
      cl.executeMethod(mPost);&lt;br /&gt;
      System.out.println(mPost.getResponseBodyAsString());&lt;br /&gt;
   }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
=== Java ( Based on new Apache HttpComponents library ) ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
* RT ticket creator based on the current Apache HttpComponents library 4.1.3&lt;br /&gt;
* Created by Koustubha Kale, kmkale at youtility dot in&lt;br /&gt;
*/&lt;br /&gt;
import org.apache.http.HttpEntity;&lt;br /&gt;
import org.apache.http.HttpResponse;&lt;br /&gt;
import org.apache.http.auth.AuthScope;&lt;br /&gt;
import org.apache.http.auth.UsernamePasswordCredentials;&lt;br /&gt;
import org.apache.http.client.HttpClient;&lt;br /&gt;
import org.apache.http.client.methods.HttpPost;&lt;br /&gt;
import org.apache.http.entity.mime.MultipartEntity;&lt;br /&gt;
import org.apache.http.entity.mime.content.StringBody;&lt;br /&gt;
import org.apache.http.impl.client.DefaultHttpClient;&lt;br /&gt;
import org.apache.http.util.EntityUtils;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
public class NewApacheHttpcomponentsRtTicketCreator {&lt;br /&gt;
&lt;br /&gt;
    public static void main(String[] args) throws Exception {&lt;br /&gt;
        &lt;br /&gt;
        DefaultHttpClient httpclient = new DefaultHttpClient();&lt;br /&gt;
	try {&lt;br /&gt;
            &lt;br /&gt;
            HttpPost httppost = new HttpPost(&amp;quot;http://rt.xxx.com/rt/REST/1.0&amp;quot; +&lt;br /&gt;
                    &amp;quot;/ticket/new?user=username&amp;amp;pass=password&amp;quot;);&lt;br /&gt;
	    StringBody content = new StringBody(&amp;quot;Queue: General\nSubject: 123&amp;quot;);&lt;br /&gt;
	    MultipartEntity reqEntity = new MultipartEntity();&lt;br /&gt;
	    reqEntity.addPart(&amp;quot;content&amp;quot;, content);&lt;br /&gt;
	    httppost.setEntity(reqEntity);&lt;br /&gt;
	    System.out.println(&amp;quot;executing request &amp;quot; + httppost.getRequestLine());&lt;br /&gt;
            HttpResponse response = httpclient.execute(httppost);&lt;br /&gt;
            HttpEntity resEntity = response.getEntity();&lt;br /&gt;
&lt;br /&gt;
            System.out.println(&amp;quot;----------------------------------------&amp;quot;);&lt;br /&gt;
            System.out.println(response.getStatusLine());&lt;br /&gt;
            if (resEntity != null) {&lt;br /&gt;
                System.out.println(&amp;quot;Response content length: &amp;quot; + resEntity.getContentLength());&lt;br /&gt;
            }&lt;br /&gt;
            EntityUtils.consume(resEntity);&lt;br /&gt;
        } finally {&lt;br /&gt;
            try { httpclient.getConnectionManager().shutdown(); } catch (Exception ignore) {}&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Python ===&lt;br /&gt;
&amp;lt;SyntaxHighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
import cookielib&lt;br /&gt;
import urllib&lt;br /&gt;
import urllib2&lt;br /&gt;
       &lt;br /&gt;
  # creates a cookie for the rtserver with the credentials given at initialization.&lt;br /&gt;
  # define your credentials here&lt;br /&gt;
access_user = &#039;your_login&#039;&lt;br /&gt;
access_password = &#039;your_password&#039;&lt;br /&gt;
       &lt;br /&gt;
 # here is the RequestTracker URI we try to access&lt;br /&gt;
uri = &#039;http://your-rt-instance.com/REST/1.0/&#039;&lt;br /&gt;
       &lt;br /&gt;
 # trying login on rt server&lt;br /&gt;
cj = cookielib.LWPCookieJar()&lt;br /&gt;
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))&lt;br /&gt;
urllib2.install_opener(opener)&lt;br /&gt;
data = {&#039;user&#039;: access_user, &#039;pass&#039;: access_password}&lt;br /&gt;
ldata = urllib.urlencode(data)&lt;br /&gt;
login = urllib2.Request(uri, ldata)&lt;br /&gt;
try:&lt;br /&gt;
   response = urllib2.urlopen(login)&lt;br /&gt;
   print response.read()&lt;br /&gt;
   print &amp;quot;login successful&amp;quot;&lt;br /&gt;
except urllib2.URLError:&lt;br /&gt;
   # could not connect to server&lt;br /&gt;
   print &amp;quot;Not able to login&amp;quot;&lt;br /&gt;
&amp;lt;/SyntaxHighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Ruby ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/bin/env ruby&lt;br /&gt;
require &#039;net/http&#039;&lt;br /&gt;
&lt;br /&gt;
user = &#039;username&#039;&lt;br /&gt;
pass = &#039;password&#039;&lt;br /&gt;
uri = URI(&#039;https://rt.example.com/REST/1.0/ticket/123/show&#039;)&lt;br /&gt;
&lt;br /&gt;
req = Net::HTTP::Post.new(uri.path)&lt;br /&gt;
req.set_form_data(&#039;user&#039; =&amp;gt; user, &#039;pass&#039; =&amp;gt; pass)&lt;br /&gt;
&lt;br /&gt;
res = Net::HTTP.start(uri.hostname, uri.port, &lt;br /&gt;
  :use_ssl =&amp;gt; uri.scheme == &#039;https&#039;, &lt;br /&gt;
  :set_debug_output =&amp;gt; $stderr) do |http|&lt;br /&gt;
  http.request(req)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
case res&lt;br /&gt;
when Net::HTTPSuccess, Net::HTTPRedirection&lt;br /&gt;
  # OK&lt;br /&gt;
  puts &amp;quot;HTTP response code:  #{res.code}&amp;quot;&lt;br /&gt;
  puts &amp;quot;HTTP message: #{res.message}&amp;quot;&lt;br /&gt;
  puts &amp;quot;Response:&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  res.each do |key,val|&lt;br /&gt;
    puts &amp;quot;#{key} =&amp;gt; #{val}&amp;quot;&lt;br /&gt;
  end&lt;br /&gt;
&lt;br /&gt;
  puts &amp;quot;Data:&amp;quot;&lt;br /&gt;
  puts res.body&lt;br /&gt;
else&lt;br /&gt;
  res.value&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PHP ===&lt;br /&gt;
&amp;lt;SyntaxHighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$username = rt_user;&lt;br /&gt;
$password = rt_pass;&lt;br /&gt;
$url = &amp;quot;http://server.domain.tld/REST/1.0/ticket/&amp;lt;ticket id&amp;gt;/show?user=$username&amp;amp;pass=$password&amp;quot;;&lt;br /&gt;
$request = new HttpRequest($url, HTTP_METH_GET);&lt;br /&gt;
/* if you want&#039;s to pass additional parameters */&lt;br /&gt;
$request-&amp;gt;addQueryData(array());&lt;br /&gt;
$response = $request-&amp;gt;send();&lt;br /&gt;
print_r($response);&lt;br /&gt;
&amp;lt;/SyntaxHighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
OR&lt;br /&gt;
&lt;br /&gt;
&amp;lt;SyntaxHighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$username = rt_user;&lt;br /&gt;
$password = rt_pass;&lt;br /&gt;
$url = &amp;quot;http://server.domain.tld/REST/1.0/ticket/new?user=$username&amp;amp;pass=$password&amp;quot;;&lt;br /&gt;
$request = new HttpRequest($url, HTTP_METH_POST);&lt;br /&gt;
// Note : to add data in custom fields you need to add element like below example&lt;br /&gt;
// in $post_data array&lt;br /&gt;
// Example : \nCF-customfield1:testdata&lt;br /&gt;
$post_data = array(&lt;br /&gt;
    &amp;quot;content&amp;quot; =&amp;gt; &lt;br /&gt;
        &amp;quot;Queue: General&amp;quot; . PHP_EOL .&lt;br /&gt;
        &amp;quot;Requestor: user@domain&amp;quot; . PHP_EOL .&lt;br /&gt;
        &amp;quot;Subject: REST test 1&amp;quot; . PHP_EOL .&lt;br /&gt;
        &amp;quot;Owner: userX&amp;quot; . PHP_EOL .&lt;br /&gt;
        &amp;quot;AdminCc: userX&amp;quot; . PHP_EOL .&lt;br /&gt;
        &amp;quot;Text: This is a REST test&amp;quot; . PHP_EOL&lt;br /&gt;
);&lt;br /&gt;
$request-&amp;gt;addPostFields($post_data);&lt;br /&gt;
&lt;br /&gt;
try&lt;br /&gt;
{&lt;br /&gt;
    response = $request-&amp;gt;send()-&amp;gt;getBody();&lt;br /&gt;
    print_r($response);&lt;br /&gt;
} catch (HttpException $ex) {&lt;br /&gt;
    echo $ex;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/SyntaxHighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== C# ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
// Using &amp;quot;WCF REST Starter Kit&amp;quot; (http://msdn.microsoft.com/en-us/netframework/cc950529.aspx)&lt;br /&gt;
using (var client = new HttpClient(&amp;quot;http://rt.site.com/REST/1.0/&amp;quot;))&lt;br /&gt;
{&lt;br /&gt;
   client.TransportSettings.Cookies = new CookieContainer();&lt;br /&gt;
&lt;br /&gt;
   var form = new HttpUrlEncodedForm();&lt;br /&gt;
   form.Add(&amp;quot;user&amp;quot;, &amp;quot;LOGIN&amp;quot;);&lt;br /&gt;
   form.Add(&amp;quot;pass&amp;quot;, &amp;quot;PASSWORD&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
   client.Post(string.Empty, form.CreateHttpContent());&lt;br /&gt;
&lt;br /&gt;
   // 1. Get ticket data&lt;br /&gt;
   using (var request = client.Get(&amp;quot;ticket/1234/show&amp;quot;))&lt;br /&gt;
   {&lt;br /&gt;
      string content = request.Content.ReadAsString();&lt;br /&gt;
      // Some logic&lt;br /&gt;
   }&lt;br /&gt;
&lt;br /&gt;
   // 2. Post ticket reply with attachment&lt;br /&gt;
   var formPost = new HttpMultipartMimeForm();&lt;br /&gt;
   byte[] attachment = new byte[];&lt;br /&gt;
   string content = string.Empty;&lt;br /&gt;
         &lt;br /&gt;
   // Store data in attachment&lt;br /&gt;
   // Store data in content string (&amp;quot;Field: Value&amp;quot; line by line)&lt;br /&gt;
   // ...&lt;br /&gt;
         &lt;br /&gt;
   formPost.Add(&amp;quot;content&amp;quot;, content);&lt;br /&gt;
   formPost.Add(&amp;quot;attachment_1&amp;quot;, &amp;quot;attachment_1&amp;quot;, HttpContent.Create(attachment, &amp;quot;application/octet-stream&amp;quot;));&lt;br /&gt;
                             &lt;br /&gt;
   using (var post = client.Post(&amp;quot;ticket/1234/comment&amp;quot;), formPost.CreateHttpContent()))&lt;br /&gt;
      // Some logic&lt;br /&gt;
}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Powershell (v3)===&lt;br /&gt;
* Escape Powershell reserved chars in user/pass&lt;br /&gt;
* If connecting via SSL, use fqdn of server, not cname&lt;br /&gt;
*Use `n (newline) when composing new ticket structure&lt;br /&gt;
Read RT queue based on query: &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$servername=&amp;quot;Your Servername here&amp;quot;&lt;br /&gt;
$u=&amp;quot;user=Username&amp;quot;&lt;br /&gt;
$p=&amp;quot;pass=Password&amp;quot;&lt;br /&gt;
$q=&amp;quot;search/ticket?query=(Status=&#039;open&#039; OR Status=&#039;new&#039;) AND (Queue=&#039;GENERAL&#039; OR Queue=&#039;FOO&#039;) AND Owner=&#039;Nobody&#039;&amp;quot;&lt;br /&gt;
$uri=&amp;quot;https://&amp;quot; + $servername + &amp;quot;/rt/REST/1.0/&amp;quot; + $q + &amp;quot;&amp;amp;&amp;quot; + $u + &amp;quot;&amp;amp;&amp;quot; + $p&lt;br /&gt;
$RT=Invoke-WebRequest -Uri  $uri -SessionVariable sess&lt;br /&gt;
$rt.Content&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===VB.NET===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Public Sub AddAttachmentToRT(ByVal url As String, ByVal fileName As String, ByVal filePath As String)&lt;br /&gt;
&lt;br /&gt;
        Dim dataBoundary As String = &amp;quot;--xYzZY&amp;quot;&lt;br /&gt;
        Dim request As HttpWebRequest&lt;br /&gt;
        Dim fileType As String = &amp;quot;image/jpeg&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        &#039;Create a POST web request to the REST interface using the passed URL&lt;br /&gt;
        request = CType(WebRequest.Create(url), HttpWebRequest)&lt;br /&gt;
        request.ContentType = &amp;quot;multipart/form-data; boundary=xYzZY&amp;quot;&lt;br /&gt;
        request.Method = &amp;quot;POST&amp;quot;&lt;br /&gt;
        request.KeepAlive = True&lt;br /&gt;
&lt;br /&gt;
        &#039;Write the request to the requestStream&lt;br /&gt;
        Using requestStream As IO.Stream = request.GetRequestStream()&lt;br /&gt;
&lt;br /&gt;
            &#039;Create a variable &amp;quot;attachment_1&amp;quot; in the POST, specify the file name and file type&lt;br /&gt;
            Dim preAttachment As String = dataBoundary + vbCrLf _&lt;br /&gt;
            + &amp;quot;Content-Disposition: form-data; name=&amp;quot;&amp;quot;attachment_1&amp;quot;&amp;quot;; filename=&amp;quot;&amp;quot;&amp;quot; + fileName + &amp;quot;&amp;quot;&amp;quot;&amp;quot; + vbCrLf _&lt;br /&gt;
            + &amp;quot;Content-Type: &amp;quot; + fileType + vbCrLf _&lt;br /&gt;
            + vbCrLf&lt;br /&gt;
&lt;br /&gt;
            &#039;Convert this preAttachment string to bytes&lt;br /&gt;
            Dim preAttachmentBytes As Byte() = System.Text.Encoding.UTF8.GetBytes(preAttachment)&lt;br /&gt;
&lt;br /&gt;
            &#039;Write this preAttachment string to the stream&lt;br /&gt;
            requestStream.Write(preAttachmentBytes, 0, preAttachmentBytes.Length)&lt;br /&gt;
&lt;br /&gt;
            &#039;Write the file as bytes to the stream by passing its exact location&lt;br /&gt;
            Using fileStream As New IO.FileStream(Server.MapPath(filePath + fileName), IO.FileMode.Open, IO.FileAccess.Read)&lt;br /&gt;
&lt;br /&gt;
                Dim buffer(4096) As Byte&lt;br /&gt;
                Dim bytesRead As Int32 = fileStream.Read(buffer, 0, buffer.Length)&lt;br /&gt;
&lt;br /&gt;
                Do While (bytesRead &amp;gt; 0)&lt;br /&gt;
&lt;br /&gt;
                    requestStream.Write(buffer, 0, bytesRead)&lt;br /&gt;
                    bytesRead = fileStream.Read(buffer, 0, buffer.Length)&lt;br /&gt;
&lt;br /&gt;
                Loop&lt;br /&gt;
&lt;br /&gt;
            End Using&lt;br /&gt;
&lt;br /&gt;
            &#039;Create a variable named content in the POST, specify the attachment name and comment text&lt;br /&gt;
            Dim postAttachment As String = vbCrLf _&lt;br /&gt;
            + dataBoundary + vbCrLf _&lt;br /&gt;
            + &amp;quot;Content-Disposition: form-data; name=&amp;quot;&amp;quot;content&amp;quot;&amp;quot;&amp;quot; + vbCrLf _&lt;br /&gt;
            + vbCrLf _&lt;br /&gt;
            + &amp;quot;Action: comment&amp;quot; + vbLf _&lt;br /&gt;
            + &amp;quot;Attachment: &amp;quot; + fileName + vbCrLf _&lt;br /&gt;
            + &amp;quot;Text: Whatever You Want&amp;quot; + vbCrLf _&lt;br /&gt;
            + vbCrLf _&lt;br /&gt;
            + &amp;quot;--xYzZY--&amp;quot;&lt;br /&gt;
&lt;br /&gt;
            &#039;Convert postAttachment string to bytes&lt;br /&gt;
            Dim postAttachmentBytes As Byte() = System.Text.Encoding.UTF8.GetBytes(postAttachment)&lt;br /&gt;
&lt;br /&gt;
            &#039;Write the postAttachment string to the stream&lt;br /&gt;
            requestStream.Write(postAttachmentBytes, 0, postAttachmentBytes.Length)&lt;br /&gt;
&lt;br /&gt;
        End Using&lt;br /&gt;
&lt;br /&gt;
        Dim response As Net.WebResponse = Nothing&lt;br /&gt;
&lt;br /&gt;
        &#039;Get the response from our REST request to RT&lt;br /&gt;
        &#039;Required to capture response, without this Try-Catch attaching will fail&lt;br /&gt;
        Try&lt;br /&gt;
            response = request.GetResponse()&lt;br /&gt;
&lt;br /&gt;
            Using responseStream As IO.Stream = response.GetResponseStream()&lt;br /&gt;
&lt;br /&gt;
                Using responseReader As New IO.StreamReader(responseStream)&lt;br /&gt;
&lt;br /&gt;
                    Dim responseText = responseReader.ReadToEnd()&lt;br /&gt;
&lt;br /&gt;
                End Using&lt;br /&gt;
&lt;br /&gt;
            End Using&lt;br /&gt;
&lt;br /&gt;
        Catch exception As Net.WebException&lt;br /&gt;
&lt;br /&gt;
            response = exception.Response&lt;br /&gt;
&lt;br /&gt;
            If (response IsNot Nothing) Then&lt;br /&gt;
&lt;br /&gt;
                Using reader As New IO.StreamReader(response.GetResponseStream())&lt;br /&gt;
&lt;br /&gt;
                    Dim responseText = reader.ReadToEnd()&lt;br /&gt;
&lt;br /&gt;
                End Using&lt;br /&gt;
&lt;br /&gt;
                response.Close()&lt;br /&gt;
&lt;br /&gt;
            End If&lt;br /&gt;
&lt;br /&gt;
        Finally&lt;br /&gt;
&lt;br /&gt;
            request = Nothing&lt;br /&gt;
&lt;br /&gt;
        End Try&lt;br /&gt;
&lt;br /&gt;
    End Sub&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
===JavaScript===&lt;br /&gt;
This is an example of updating the RefersTo field for a ticket. Assuming Zepto/jQuery is also being used.&lt;br /&gt;
 function link_back(url, ticket_num, rt) {&lt;br /&gt;
     var data, link;&lt;br /&gt;
     link = &amp;quot;refersto: &amp;quot; + url;&lt;br /&gt;
     data = {&amp;quot;content&amp;quot;: link};&lt;br /&gt;
 &lt;br /&gt;
     $.ajax({&lt;br /&gt;
         type: &amp;quot;POST&amp;quot;,&lt;br /&gt;
         url: rt + ticket_num + &amp;quot;/links&amp;quot;,&lt;br /&gt;
         data: data,&lt;br /&gt;
         success: function (reply) {&lt;br /&gt;
             console.log(reply);&lt;br /&gt;
         }&lt;br /&gt;
     });&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 var url = &amp;quot;https://www.wikipedia.org/&amp;quot;;&lt;br /&gt;
 var ticket_num = &amp;quot;123456&amp;quot;;&lt;br /&gt;
 var rt = &amp;quot;https://some.rt.url/REST/1.0/ticket/&amp;quot;;&lt;br /&gt;
 &lt;br /&gt;
 link_back(url, ticket_num, rt);&lt;br /&gt;
&lt;br /&gt;
== Convenience libraries ==&lt;br /&gt;
&lt;br /&gt;
There are libraries which do much of the low-level work shown in the examples below for you, and provide an easier programming interface for dealing with RT:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Language&lt;br /&gt;
!Package/Module&lt;br /&gt;
!Source&lt;br /&gt;
!Example&lt;br /&gt;
!Note&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;Perl&#039;&#039;&#039;&lt;br /&gt;
|[http://metacpan.org/module/RT::Client::REST RT::Client::REST]&lt;br /&gt;
|[http://rt-client-rest.googlecode.com/svn/trunk/ Google Code]&lt;br /&gt;
|&amp;lt;code&amp;gt;&amp;quot;perl -MCPAN -e install RT::Client::REST&amp;quot;&amp;lt;/code&amp;gt; &lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;Ruby&#039;&#039;&#039;&lt;br /&gt;
|[http://rubygems.org/gems/rt-client rt-client]&lt;br /&gt;
|[https://github.com/uidzip/rt-client GitHub]&lt;br /&gt;
|&amp;lt;code&amp;gt;gem install rt-client&amp;lt;/code&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;Ruby&#039;&#039;&#039;&lt;br /&gt;
|[http://rubygems.org/gems/roart Roart]&lt;br /&gt;
|[https://github.com/pjdavis/roart GitHub]&lt;br /&gt;
|&amp;lt;code&amp;gt;gem install roart&amp;lt;/code&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;Python&#039;&#039;&#039;&lt;br /&gt;
|[https://github.com/Rickerd0613/rtapi rtapi]&lt;br /&gt;
|[https://github.com/Rickerd0613/rtapi GitHub]&lt;br /&gt;
|&amp;lt;code&amp;gt;pip install rtapi&amp;lt;/code&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;Python&#039;&#039;&#039;&lt;br /&gt;
|[https://github.com/z4r/python-rtkit rtkit]&lt;br /&gt;
|[https://github.com/z4r/python-rtkit GitHub]&lt;br /&gt;
|&amp;lt;code&amp;gt;pip install python-rtkit&amp;lt;/code&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;Python&#039;&#039;&#039;&lt;br /&gt;
|[https://github.com/CZ-NIC/python-rt python-rt]&lt;br /&gt;
|[https://github.com/CZ-NIC/python-rt Github]&lt;br /&gt;
|&amp;lt;code&amp;gt;pip install rt&amp;lt;/code&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;Java&#039;&#039;&#039;&lt;br /&gt;
|[http://projects.boksa.de/RT-REST/ RT-REST]&lt;br /&gt;
|[https://github.com/bboksa/RT-REST GitHub]&lt;br /&gt;
|&amp;lt;code&amp;gt;N/A&amp;lt;/code&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;PHP&#039;&#039;&#039;&lt;br /&gt;
|[https://github.com/dersam/RTPHPLib RTPHPLib]&lt;br /&gt;
|[https://github.com/dersam/RTPHPLib GitHub]&lt;br /&gt;
|composer require dersam/rt-php-lib&lt;br /&gt;
|&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27220</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27220"/>
		<updated>2025-03-06T18:16:20Z</updated>

		<summary type="html">&lt;p&gt;Robl: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
== RT REST2 (JSON) API ==&lt;br /&gt;
&lt;br /&gt;
* RT has two REST APIs - the original [[REST]] v1.0 API, and a newer, REST2 JSON API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API is more modern and uses JSON for queries and responses, making it easier to integrate RT with other systems, and to read and update RT with scripts running on remote servers.&lt;br /&gt;
&lt;br /&gt;
The REST2 API was originally provided as an [https://github.com/bestpractical/rt-extension-rest2 extension] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
REST2 is core in RT 5.0.0 and later, so you do not need this extension for those versions.&lt;br /&gt;
&lt;br /&gt;
[https://docs.bestpractical.com/rt/5.0.7/RT/REST2.html REST2 Documentation]: https://docs.bestpractical.com/rt/5.0.7/RT/REST2.html&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Clients for RT REST2 API ==&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27219</id>
		<title>REST2</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=REST2&amp;diff=27219"/>
		<updated>2025-03-06T17:38:23Z</updated>

		<summary type="html">&lt;p&gt;Robl: Created page with &amp;quot; == RT REST2 (JSON) API ==  * RT has two REST APIs - the original REST v1.0 API, and a newer, REST2 JSON API. * The REST1 API is still used and supported. Many things use it, including RT&amp;#039;s mail gateway, rt-mailgate.  The REST2 API is more modern and uses JSON for queries and responses, making it easier to integrate RT with other systems, and to read and update RT with scripts running on remote servers.  The REST2 API was originally provided as an [https://github.com...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
== RT REST2 (JSON) API ==&lt;br /&gt;
&lt;br /&gt;
* RT has two REST APIs - the original [[REST]] v1.0 API, and a newer, REST2 JSON API.&lt;br /&gt;
* The REST1 API is still used and supported. Many things use it, including RT&#039;s mail gateway, rt-mailgate.&lt;br /&gt;
&lt;br /&gt;
The REST2 API is more modern and uses JSON for queries and responses, making it easier to integrate RT with other systems, and to read and update RT with scripts running on remote servers.&lt;br /&gt;
&lt;br /&gt;
The REST2 API was originally provided as an [https://github.com/bestpractical/rt-extension-rest2 extension] for RT 4.4 and above.&lt;br /&gt;
&lt;br /&gt;
REST2 is core in RT 5.0.0 and later, so you do not need this extension for those versions.&lt;br /&gt;
&lt;br /&gt;
[https://docs.bestpractical.com/rt/5.0.7/RT/REST2.html REST2 Documentation]: https://docs.bestpractical.com/rt/5.0.7/RT/REST2.html&lt;br /&gt;
&lt;br /&gt;
=== Examples ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Clients for RT REST2 API ===&lt;br /&gt;
&lt;br /&gt;
* [https://python-rt.readthedocs.io/en/stable/rest2.html python-rt] - Python client.&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=Shredder&amp;diff=27216</id>
		<title>Shredder</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=Shredder&amp;diff=27216"/>
		<updated>2024-11-09T12:15:27Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Daily cron script to purge deleted tickets and unlinked users */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
As of 3.8.0 the Shredder extension is built into RT.&lt;br /&gt;
&lt;br /&gt;
This page documents how to get it working quickly for basic uses.&lt;br /&gt;
&lt;br /&gt;
Shredder has [[#WebUI|web (WebUI)]] and [[#CLI|command line (CLI)]] interfaces. Both are equivalent in terms of available search plugins and options to pick objects.&lt;br /&gt;
&lt;br /&gt;
== WebUI ==&lt;br /&gt;
&lt;br /&gt;
Only users with SuperUser rights can shred through WebUI.&lt;br /&gt;
&lt;br /&gt;
The easiest way to shred tickets (particularly tickets, users, attachments) is to build a custom search with your desired criteria in the WebUI. You will then have a chance to review and select specific objects then remove them from the database while a backup SQL dump is created.&lt;br /&gt;
&lt;br /&gt;
Note that while the interface indicates it accepts DOS-like wildcards (* and ?) these are translated to the standard SQL wildcards of % and _ internally, and you may specify them directly if you prefer. Specifically this means that the claim * matches non-empty sequences is misleading since % will match null.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Don&#039;t use&#039;&#039;&#039; to delete many objects as shredder is quite slow and may hit browser or server timeout that will abort operation. A large number of target tickets may also result in a query to the web server that is larger than the maximum allowable URI length. Use the CLI instead.&lt;br /&gt;
&lt;br /&gt;
== CLI ==&lt;br /&gt;
&lt;br /&gt;
You can use shredder from command line, here is a few commands to help you start:&lt;br /&gt;
&lt;br /&gt;
 perldoc RT/Shredder.pm&lt;br /&gt;
 rt-shredder --help&lt;br /&gt;
 rt-shredder --plugin help-Tickets&lt;br /&gt;
 rt-shredder --plugin help-Users&lt;br /&gt;
&lt;br /&gt;
See a few [[#Examples examples below]].&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
Examples presented with shell commands, but the same can be performed in the [[#WebUI |WebUI]].&lt;br /&gt;
&lt;br /&gt;
=== Shred Deleted Tickets by Status and Age ===&lt;br /&gt;
You can run the following command by hand and see the results.&lt;br /&gt;
 rt-shredder --plugin &amp;quot;Tickets=query,Status = &#039;Deleted&#039; AND LastUpdated &amp;amp;lt; &#039;30 days ago&#039;;limit,100&amp;quot; --sqldump /{somepath}/shredder-restore-tickets.sql&lt;br /&gt;
&lt;br /&gt;
=== Shred Users with no Tickets ===&lt;br /&gt;
Users with no tickets are users who have had their tickets deleted -- spam senders, or users whose tickets have been moved to another user.&lt;br /&gt;
 rt-shredder --plugin &amp;quot;Users=no_tickets,1;status,any;replace_relations,Nobody;limit,5&amp;quot; --sqldump /{somepath}/shredder-restore-users.sql --force&lt;br /&gt;
&lt;br /&gt;
=== Shred multiple Scrips ===&lt;br /&gt;
 /opt/rt4/sbin/rt-shredder --plugin &amp;quot;Objects=Scrip,26;Scrip,28;Scrip,53&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Daily cron script to purge deleted tickets and unlinked users ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; The example above to &#039;&#039;&#039;delete users with no tickets&#039;&#039;&#039; deletes &#039;&#039;&#039;new admin users&#039;&#039;&#039; who have not yet been assigned or linked with any tickets!&lt;br /&gt;
&lt;br /&gt;
For example, we would create an RT account for a new employee. The cron would then delete their account overnight every day until they had been assigned a ticket.&lt;br /&gt;
(Fortunately it is easy to restore a deleted account from the .sql files.)&lt;br /&gt;
&lt;br /&gt;
To prevent new users getting clobbered, I have modified the user query to exclude users of group &amp;quot;Staff&amp;quot; (in our case) and only delete &#039;&#039;&#039;unprivileged&#039;&#039;&#039; users. This works on RT5.0.5. (Untested on earlier versions.)&lt;br /&gt;
&lt;br /&gt;
   &amp;quot;Users=not_member_of,Staff;member_of,unprivileged;no_tickets,1;status,any;replace_relations,Nobody;limit,50&amp;quot;&lt;br /&gt;
&lt;br /&gt;
You will need to modify this script/query for your use. &lt;br /&gt;
&lt;br /&gt;
* The limits I have used work fine in our installation, but you may need to reduce (especially for the first run where it would delete lots of tickets or users.)&lt;br /&gt;
* For testing purposes, remove the --force option. As it&#039;s designed to be run from cron, it does not prompt before deleting.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
&lt;br /&gt;
# robl 20190822&lt;br /&gt;
# Script to expunge tickets and all related objects marked as &amp;quot;deleted&amp;quot; after 30 days.&lt;br /&gt;
# Database cleanup tasks.&lt;br /&gt;
#&lt;br /&gt;
# Backup .sql files are retained.&lt;br /&gt;
&lt;br /&gt;
# Set as appropriate:&lt;br /&gt;
RT_SHREDDER=&amp;quot;/usr/local/sbin/rt-shredder&amp;quot;&lt;br /&gt;
BACKUPDIR=&amp;quot;/srv/rt5/var/rt-shredder&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Needs bzip or similar installed:&lt;br /&gt;
BZIP=&amp;quot;/usr/bin/bzip2&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Check things exist:&lt;br /&gt;
&lt;br /&gt;
if [ ! -x &amp;quot;$RT_SHREDDER&amp;quot; ] ; then&lt;br /&gt;
        echo &amp;quot;ERR: could not execute $RT_SHREDDER.&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if [ ! -d &amp;quot;$BACKUPDIR&amp;quot; ] ; then&lt;br /&gt;
        echo &amp;quot;ERR: $BACKUPDIR does not exist, trying to create...&amp;quot;&lt;br /&gt;
        mkdir -p &amp;quot;$BACKUPDIR&amp;quot;&lt;br /&gt;
        chown www-data:www-data $BACKUPDIR&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ ! -d &amp;quot;$BACKUPDIR&amp;quot; ] ; then&lt;br /&gt;
        echo &amp;quot;ERR: $BACKUPDIR could not be created.&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
# Keep running rt-shredder until we get 0 tickets returned:&lt;br /&gt;
&lt;br /&gt;
while :&lt;br /&gt;
  do&lt;br /&gt;
    FILEDATE=$(date +%Y-%m-%d-%H%M%S)&lt;br /&gt;
    SHREDDED=$($RT_SHREDDER --force --plugin &amp;quot;Tickets=query,Status = &#039;Deleted&#039; AND LastUpdated &amp;lt; &#039;30 days ago&#039;;limit,1000&amp;quot; \&lt;br /&gt;
               --sqldump ${BACKUPDIR}/${FILEDATE}-shredder-restore-tickets.sql 2&amp;gt;&amp;amp;1 | grep &amp;quot;RT::Ticket&amp;quot; | wc -l)&lt;br /&gt;
&lt;br /&gt;
    if [ $SHREDDED -ge 1 ] ; then&lt;br /&gt;
       echo &amp;quot;$FILEDATE : Shredded $SHREDDED tickets.&amp;quot;&lt;br /&gt;
       sleep 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ $SHREDDED -eq 0 ]; then&lt;br /&gt;
      break&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
  done&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Purge users with no tickets.&lt;br /&gt;
# Spam senders, or users whose tickets have been moved to another user.)&lt;br /&gt;
&lt;br /&gt;
while :&lt;br /&gt;
  do&lt;br /&gt;
    FILEDATE=$(date +%Y-%m-%d-%H%M%S)&lt;br /&gt;
    SHREDDED=$($RT_SHREDDER --force --plugin &amp;quot;Users=not_member_of,Staff;member_of,unprivileged;no_tickets,1;status,any;replace_relations,Nobody;limit,50&amp;quot; \&lt;br /&gt;
               --sqldump ${BACKUPDIR}/${FILEDATE}-shredder-restore-users.sql 2&amp;gt;&amp;amp;1 | grep &amp;quot;RT::User&amp;quot; | wc -l)&lt;br /&gt;
&lt;br /&gt;
    if [ $SHREDDED -ge 1 ] ; then&lt;br /&gt;
       echo &amp;quot;$FILEDATE : Shredded $SHREDDED users.&amp;quot;&lt;br /&gt;
       sleep 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ $SHREDDED -eq 0 ]; then&lt;br /&gt;
      break&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
  done&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#&lt;br /&gt;
# Cleanup operations&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
# Delete any 0 length .sql files left by rt-shredder:&lt;br /&gt;
for f in ${BACKUPDIR}/*.sql ; do&lt;br /&gt;
    [ -e &amp;quot;$f&amp;quot; ] &amp;amp;&amp;amp; /usr/bin/find ${BACKUPDIR} -name &#039;*.sql&#039; -size 0 -exec rm {} \;&lt;br /&gt;
    break&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
# Bzip any remaining .sql files:&lt;br /&gt;
for f in ${BACKUPDIR}/*.sql ; do&lt;br /&gt;
    [ -e &amp;quot;$f&amp;quot; ] &amp;amp;&amp;amp;  ${BZIP} -q ${BACKUPDIR}/*.sql&lt;br /&gt;
    break&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
# Remove old backup files after a period of time:&lt;br /&gt;
for f in ${BACKUPDIR}/*.bz2 ; do&lt;br /&gt;
    [ -e &amp;quot;$f&amp;quot; ] &amp;amp;&amp;amp;  /usr/bin/find ${BACKUPDIR} -name &#039;*.bz2&#039; -ctime +364 -exec rm {} \;&lt;br /&gt;
    break&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example for /etc/cron.d/rt5:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
# Purge tickets marked as deleted and related records:&lt;br /&gt;
0 23 * * * www-data  /srv/rt5/scripts/RTPurgeDeletedTickets.sh&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Shredding many tickets ==&lt;br /&gt;
&lt;br /&gt;
You may also be interested in the information in [[ShredderControl]].&lt;br /&gt;
&lt;br /&gt;
== Shred ALL TICKETS ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;WARNING WARNING WARNING:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If for some reason you want to reset your entire RT instance&#039;s TICKETS AND TICKET DATA ONLY (and keep Scrips, Custom Fields, etc), you could do something like the following. This was useful for me when I wanted to take our production RT instance and duplicate it onto a development box but not have the huge database full of tickets and ticket-related data.&lt;br /&gt;
&lt;br /&gt;
=== With shredder and shell script ===&lt;br /&gt;
&lt;br /&gt;
Bourne shell syntax is shown below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
  cd /tmp&lt;br /&gt;
  while :&lt;br /&gt;
  do&lt;br /&gt;
  date&lt;br /&gt;
  SHREDDED=`rt-shredder --plugin &amp;quot;Tickets=query,id &amp;gt; 0;limit,100&amp;quot; --force --sqldump foo.sql 2&amp;gt;&amp;amp;1 | grep RT::Ticket | wc -l`&lt;br /&gt;
  echo &amp;quot;Shredded roughly $SHREDDED tickets.&amp;quot;&lt;br /&gt;
  sleep 3 # let the system get a breath&lt;br /&gt;
  rm -f foo.sql # we don&#039;t care about restoring what we shredded in this case&lt;br /&gt;
  if [ $SHREDDED -eq 0 ]; then&lt;br /&gt;
      break&lt;br /&gt;
  fi&lt;br /&gt;
  done&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== With rt-validator ===&lt;br /&gt;
&lt;br /&gt;
Delete all tickets with SQL command:&lt;br /&gt;
&lt;br /&gt;
 DELETE FROM Tickets;&lt;br /&gt;
&lt;br /&gt;
Use rt-validator to delete records that are broken now:&lt;br /&gt;
&lt;br /&gt;
 ./sbin/rt-validator -c --resolve&lt;br /&gt;
&lt;br /&gt;
=== With rt-delete-tickets-mysql from RT-Extension-Utils ===&lt;br /&gt;
&lt;br /&gt;
Mark all tickets with status deleted with SQL command:&lt;br /&gt;
&lt;br /&gt;
 UPDATE Tickets SET Status = &#039;deleted&#039;;&lt;br /&gt;
&lt;br /&gt;
Use the tool to delete tickets and everything related then check consistency:&lt;br /&gt;
&lt;br /&gt;
  ./sbin/rt-delete-tickets-mysql&lt;br /&gt;
  ./sbin/rt-validator -c&lt;br /&gt;
&lt;br /&gt;
Afterward, you will probably want to do the &amp;quot;Shred Users with no Tickets&amp;quot; run as described above as well.&lt;br /&gt;
&lt;br /&gt;
== Speed up shredding process ==&lt;br /&gt;
You need to set up the database indexes according to [https://docs.bestpractical.com/rt/4.2.16/RT/Shredder.html#Database-Indexes Shredder documentation (RT 4.2.16)]:&lt;br /&gt;
  CREATE INDEX SHREDDER_CGM1 ON CachedGroupMembers(MemberId, GroupId, Disabled);&lt;br /&gt;
  CREATE INDEX SHREDDER_CGM2 ON CachedGroupMembers(ImmediateParentId,MemberId);&lt;br /&gt;
  CREATE INDEX SHREDDER_CGM3 on CachedGroupMembers (Via, Id);&lt;br /&gt;
  CREATE UNIQUE INDEX SHREDDER_GM1 ON GroupMembers(MemberId, GroupId);&lt;br /&gt;
  CREATE INDEX SHREDDER_TXN1 ON Transactions(ReferenceType, OldReference);&lt;br /&gt;
  CREATE INDEX SHREDDER_TXN2 ON Transactions(ReferenceType, NewReference);&lt;br /&gt;
  CREATE INDEX SHREDDER_TXN3 ON Transactions(Type, OldValue);&lt;br /&gt;
  CREATE INDEX SHREDDER_TXN4 ON Transactions(Type, NewValue);&lt;br /&gt;
  CREATE INDEX SHREDDER_ATTACHMENTS1 ON Attachments(Creator);&lt;br /&gt;
&lt;br /&gt;
For newer RT versions ([https://docs.bestpractical.com/rt/4.4.6/RT/Shredder.html#Database-Indexes 4.4.6] and also [https://docs.bestpractical.com/rt/4.4.6/RT/Shredder.html#Database-Indexes 5.0.4]) you may need more indexes:&lt;br /&gt;
  CREATE INDEX SHREDDER_LINKS1 ON Links(Target);&lt;br /&gt;
  CREATE INDEX SHREDDER_ACL1 ON ACL(ObjectType, ObjectId);&lt;br /&gt;
  CREATE INDEX SHREDDER_OCFV1 ON ObjectCustomFieldValues(ObjectType, ObjectId);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Shredder for RT 3.6 and older ==&lt;br /&gt;
&lt;br /&gt;
For older releases extension available from [http://search.cpan.org/dist/RTx-Shredder/lib/RTx/Shredder.pm CPAN].&lt;br /&gt;
However, it&#039;s &#039;&#039;&#039;highly&#039;&#039;&#039; recommended to upgrade RT first and use built in solution.&lt;br /&gt;
[[Category:Shredder]]&lt;br /&gt;
[[Category:Documentation]]&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=Shredder&amp;diff=27215</id>
		<title>Shredder</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=Shredder&amp;diff=27215"/>
		<updated>2024-11-09T12:09:09Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Daily cron script to purge deleted tickets and unlinked users */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
As of 3.8.0 the Shredder extension is built into RT.&lt;br /&gt;
&lt;br /&gt;
This page documents how to get it working quickly for basic uses.&lt;br /&gt;
&lt;br /&gt;
Shredder has [[#WebUI|web (WebUI)]] and [[#CLI|command line (CLI)]] interfaces. Both are equivalent in terms of available search plugins and options to pick objects.&lt;br /&gt;
&lt;br /&gt;
== WebUI ==&lt;br /&gt;
&lt;br /&gt;
Only users with SuperUser rights can shred through WebUI.&lt;br /&gt;
&lt;br /&gt;
The easiest way to shred tickets (particularly tickets, users, attachments) is to build a custom search with your desired criteria in the WebUI. You will then have a chance to review and select specific objects then remove them from the database while a backup SQL dump is created.&lt;br /&gt;
&lt;br /&gt;
Note that while the interface indicates it accepts DOS-like wildcards (* and ?) these are translated to the standard SQL wildcards of % and _ internally, and you may specify them directly if you prefer. Specifically this means that the claim * matches non-empty sequences is misleading since % will match null.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Don&#039;t use&#039;&#039;&#039; to delete many objects as shredder is quite slow and may hit browser or server timeout that will abort operation. A large number of target tickets may also result in a query to the web server that is larger than the maximum allowable URI length. Use the CLI instead.&lt;br /&gt;
&lt;br /&gt;
== CLI ==&lt;br /&gt;
&lt;br /&gt;
You can use shredder from command line, here is a few commands to help you start:&lt;br /&gt;
&lt;br /&gt;
 perldoc RT/Shredder.pm&lt;br /&gt;
 rt-shredder --help&lt;br /&gt;
 rt-shredder --plugin help-Tickets&lt;br /&gt;
 rt-shredder --plugin help-Users&lt;br /&gt;
&lt;br /&gt;
See a few [[#Examples examples below]].&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
Examples presented with shell commands, but the same can be performed in the [[#WebUI |WebUI]].&lt;br /&gt;
&lt;br /&gt;
=== Shred Deleted Tickets by Status and Age ===&lt;br /&gt;
You can run the following command by hand and see the results.&lt;br /&gt;
 rt-shredder --plugin &amp;quot;Tickets=query,Status = &#039;Deleted&#039; AND LastUpdated &amp;amp;lt; &#039;30 days ago&#039;;limit,100&amp;quot; --sqldump /{somepath}/shredder-restore-tickets.sql&lt;br /&gt;
&lt;br /&gt;
=== Shred Users with no Tickets ===&lt;br /&gt;
Users with no tickets are users who have had their tickets deleted -- spam senders, or users whose tickets have been moved to another user.&lt;br /&gt;
 rt-shredder --plugin &amp;quot;Users=no_tickets,1;status,any;replace_relations,Nobody;limit,5&amp;quot; --sqldump /{somepath}/shredder-restore-users.sql --force&lt;br /&gt;
&lt;br /&gt;
=== Shred multiple Scrips ===&lt;br /&gt;
 /opt/rt4/sbin/rt-shredder --plugin &amp;quot;Objects=Scrip,26;Scrip,28;Scrip,53&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Daily cron script to purge deleted tickets and unlinked users ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; The example above to &#039;&#039;&#039;delete users with no tickets&#039;&#039;&#039; deletes &#039;&#039;&#039;new admin users&#039;&#039;&#039; who have not yet been assigned or linked with any tickets!&lt;br /&gt;
&lt;br /&gt;
For example, we would create an RT account for a new employee. The cron would then delete their account overnight every day until they had been assigned a ticket.&lt;br /&gt;
(Fortunately it is easy to restore a deleted account from the .sql files.)&lt;br /&gt;
&lt;br /&gt;
To prevent new users getting clobbered, I have modified the user query to exclude users of group &amp;quot;Staff&amp;quot; (in our case) and only delete &#039;&#039;&#039;unprivileged&#039;&#039;&#039; users. This works on RT5.0.5. (Untested on earlier versions.)&lt;br /&gt;
&lt;br /&gt;
   &amp;quot;Users=not_member_of,Staff;member_of,unprivileged;no_tickets,1;status,any;replace_relations,Nobody;limit,50&amp;quot;&lt;br /&gt;
&lt;br /&gt;
You will need to modify this script/query for your use. The limits I have set work fine in our installation, but you may need to reduce (especially for the first run where it would delete lots of tickets or users.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
&lt;br /&gt;
# robl 20190822&lt;br /&gt;
# Script to expunge tickets and all related objects marked as &amp;quot;deleted&amp;quot; after 30 days.&lt;br /&gt;
# Database cleanup tasks.&lt;br /&gt;
#&lt;br /&gt;
# Backup .sql files are retained.&lt;br /&gt;
&lt;br /&gt;
RT_SHREDDER=&amp;quot;/usr/local/sbin/rt-shredder&amp;quot;&lt;br /&gt;
BACKUPDIR=&amp;quot;/srv/rt5/var/rt-shredder&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Check things exist:&lt;br /&gt;
&lt;br /&gt;
if [ ! -x &amp;quot;$RT_SHREDDER&amp;quot; ] ; then&lt;br /&gt;
        echo &amp;quot;ERR: could not execute $RT_SHREDDER.&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if [ ! -d &amp;quot;$BACKUPDIR&amp;quot; ] ; then&lt;br /&gt;
        echo &amp;quot;ERR: $BACKUPDIR does not exist, trying to create...&amp;quot;&lt;br /&gt;
        mkdir -p &amp;quot;$BACKUPDIR&amp;quot;&lt;br /&gt;
        chown www-data:www-data $BACKUPDIR&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ ! -d &amp;quot;$BACKUPDIR&amp;quot; ] ; then&lt;br /&gt;
        echo &amp;quot;ERR: $BACKUPDIR could not be created.&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
# Keep running rt-shredder until we get 0 tickets returned:&lt;br /&gt;
&lt;br /&gt;
while :&lt;br /&gt;
  do&lt;br /&gt;
    FILEDATE=$(date +%Y-%m-%d-%H%M%S)&lt;br /&gt;
    SHREDDED=$($RT_SHREDDER --force --plugin &amp;quot;Tickets=query,Status = &#039;Deleted&#039; AND LastUpdated &amp;lt; &#039;30 days ago&#039;;limit,1000&amp;quot; \&lt;br /&gt;
               --sqldump ${BACKUPDIR}/${FILEDATE}-shredder-restore-tickets.sql 2&amp;gt;&amp;amp;1 | grep &amp;quot;RT::Ticket&amp;quot; | wc -l)&lt;br /&gt;
&lt;br /&gt;
    if [ $SHREDDED -ge 1 ] ; then&lt;br /&gt;
       echo &amp;quot;$FILEDATE : Shredded $SHREDDED tickets.&amp;quot;&lt;br /&gt;
       sleep 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ $SHREDDED -eq 0 ]; then&lt;br /&gt;
      break&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
  done&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Purge users with no tickets.&lt;br /&gt;
# Spam senders, or users whose tickets have been moved to another user.)&lt;br /&gt;
&lt;br /&gt;
while :&lt;br /&gt;
  do&lt;br /&gt;
    FILEDATE=$(date +%Y-%m-%d-%H%M%S)&lt;br /&gt;
    SHREDDED=$($RT_SHREDDER --force --plugin &amp;quot;Users=not_member_of,Staff;member_of,unprivileged;no_tickets,1;status,any;replace_relations,Nobody;limit,50&amp;quot; \&lt;br /&gt;
               --sqldump ${BACKUPDIR}/${FILEDATE}-shredder-restore-users.sql 2&amp;gt;&amp;amp;1 | grep &amp;quot;RT::User&amp;quot; | wc -l)&lt;br /&gt;
&lt;br /&gt;
    if [ $SHREDDED -ge 1 ] ; then&lt;br /&gt;
       echo &amp;quot;$FILEDATE : Shredded $SHREDDED users.&amp;quot;&lt;br /&gt;
       sleep 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ $SHREDDED -eq 0 ]; then&lt;br /&gt;
      break&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
  done&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#&lt;br /&gt;
# Cleanup operations&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
# Delete any 0 length .sql files left by rt-shredder:&lt;br /&gt;
for f in ${BACKUPDIR}/*.sql ; do&lt;br /&gt;
    [ -e &amp;quot;$f&amp;quot; ] &amp;amp;&amp;amp; /usr/bin/find ${BACKUPDIR} -name &#039;*.sql&#039; -size 0 -exec rm {} \;&lt;br /&gt;
    break&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
# Bzip any remaining .sql files:&lt;br /&gt;
for f in ${BACKUPDIR}/*.sql ; do&lt;br /&gt;
    [ -e &amp;quot;$f&amp;quot; ] &amp;amp;&amp;amp;  /usr/bin/bzip2 -q ${BACKUPDIR}/*.sql&lt;br /&gt;
    break&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
# Remove old backup files after a period of time:&lt;br /&gt;
for f in ${BACKUPDIR}/*.bz2 ; do&lt;br /&gt;
    [ -e &amp;quot;$f&amp;quot; ] &amp;amp;&amp;amp;  /usr/bin/find ${BACKUPDIR} -name &#039;*.bz2&#039; -ctime +364 -exec rm {} \;&lt;br /&gt;
    break&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example for /etc/cron.d/rt5:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
# Purge tickets marked as deleted and related records:&lt;br /&gt;
0 23 * * * www-data  /srv/rt5/scripts/RTPurgeDeletedTickets.sh&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Shredding many tickets ==&lt;br /&gt;
&lt;br /&gt;
You may also be interested in the information in [[ShredderControl]].&lt;br /&gt;
&lt;br /&gt;
== Shred ALL TICKETS ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;WARNING WARNING WARNING:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If for some reason you want to reset your entire RT instance&#039;s TICKETS AND TICKET DATA ONLY (and keep Scrips, Custom Fields, etc), you could do something like the following. This was useful for me when I wanted to take our production RT instance and duplicate it onto a development box but not have the huge database full of tickets and ticket-related data.&lt;br /&gt;
&lt;br /&gt;
=== With shredder and shell script ===&lt;br /&gt;
&lt;br /&gt;
Bourne shell syntax is shown below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
  cd /tmp&lt;br /&gt;
  while :&lt;br /&gt;
  do&lt;br /&gt;
  date&lt;br /&gt;
  SHREDDED=`rt-shredder --plugin &amp;quot;Tickets=query,id &amp;gt; 0;limit,100&amp;quot; --force --sqldump foo.sql 2&amp;gt;&amp;amp;1 | grep RT::Ticket | wc -l`&lt;br /&gt;
  echo &amp;quot;Shredded roughly $SHREDDED tickets.&amp;quot;&lt;br /&gt;
  sleep 3 # let the system get a breath&lt;br /&gt;
  rm -f foo.sql # we don&#039;t care about restoring what we shredded in this case&lt;br /&gt;
  if [ $SHREDDED -eq 0 ]; then&lt;br /&gt;
      break&lt;br /&gt;
  fi&lt;br /&gt;
  done&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== With rt-validator ===&lt;br /&gt;
&lt;br /&gt;
Delete all tickets with SQL command:&lt;br /&gt;
&lt;br /&gt;
 DELETE FROM Tickets;&lt;br /&gt;
&lt;br /&gt;
Use rt-validator to delete records that are broken now:&lt;br /&gt;
&lt;br /&gt;
 ./sbin/rt-validator -c --resolve&lt;br /&gt;
&lt;br /&gt;
=== With rt-delete-tickets-mysql from RT-Extension-Utils ===&lt;br /&gt;
&lt;br /&gt;
Mark all tickets with status deleted with SQL command:&lt;br /&gt;
&lt;br /&gt;
 UPDATE Tickets SET Status = &#039;deleted&#039;;&lt;br /&gt;
&lt;br /&gt;
Use the tool to delete tickets and everything related then check consistency:&lt;br /&gt;
&lt;br /&gt;
  ./sbin/rt-delete-tickets-mysql&lt;br /&gt;
  ./sbin/rt-validator -c&lt;br /&gt;
&lt;br /&gt;
Afterward, you will probably want to do the &amp;quot;Shred Users with no Tickets&amp;quot; run as described above as well.&lt;br /&gt;
&lt;br /&gt;
== Speed up shredding process ==&lt;br /&gt;
You need to set up the database indexes according to [https://docs.bestpractical.com/rt/4.2.16/RT/Shredder.html#Database-Indexes Shredder documentation (RT 4.2.16)]:&lt;br /&gt;
  CREATE INDEX SHREDDER_CGM1 ON CachedGroupMembers(MemberId, GroupId, Disabled);&lt;br /&gt;
  CREATE INDEX SHREDDER_CGM2 ON CachedGroupMembers(ImmediateParentId,MemberId);&lt;br /&gt;
  CREATE INDEX SHREDDER_CGM3 on CachedGroupMembers (Via, Id);&lt;br /&gt;
  CREATE UNIQUE INDEX SHREDDER_GM1 ON GroupMembers(MemberId, GroupId);&lt;br /&gt;
  CREATE INDEX SHREDDER_TXN1 ON Transactions(ReferenceType, OldReference);&lt;br /&gt;
  CREATE INDEX SHREDDER_TXN2 ON Transactions(ReferenceType, NewReference);&lt;br /&gt;
  CREATE INDEX SHREDDER_TXN3 ON Transactions(Type, OldValue);&lt;br /&gt;
  CREATE INDEX SHREDDER_TXN4 ON Transactions(Type, NewValue);&lt;br /&gt;
  CREATE INDEX SHREDDER_ATTACHMENTS1 ON Attachments(Creator);&lt;br /&gt;
&lt;br /&gt;
For newer RT versions ([https://docs.bestpractical.com/rt/4.4.6/RT/Shredder.html#Database-Indexes 4.4.6] and also [https://docs.bestpractical.com/rt/4.4.6/RT/Shredder.html#Database-Indexes 5.0.4]) you may need more indexes:&lt;br /&gt;
  CREATE INDEX SHREDDER_LINKS1 ON Links(Target);&lt;br /&gt;
  CREATE INDEX SHREDDER_ACL1 ON ACL(ObjectType, ObjectId);&lt;br /&gt;
  CREATE INDEX SHREDDER_OCFV1 ON ObjectCustomFieldValues(ObjectType, ObjectId);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Shredder for RT 3.6 and older ==&lt;br /&gt;
&lt;br /&gt;
For older releases extension available from [http://search.cpan.org/dist/RTx-Shredder/lib/RTx/Shredder.pm CPAN].&lt;br /&gt;
However, it&#039;s &#039;&#039;&#039;highly&#039;&#039;&#039; recommended to upgrade RT first and use built in solution.&lt;br /&gt;
[[Category:Shredder]]&lt;br /&gt;
[[Category:Documentation]]&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=Shredder&amp;diff=27214</id>
		<title>Shredder</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=Shredder&amp;diff=27214"/>
		<updated>2024-11-09T12:01:56Z</updated>

		<summary type="html">&lt;p&gt;Robl: updates. add example cron script.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
As of 3.8.0 the Shredder extension is built into RT.&lt;br /&gt;
&lt;br /&gt;
This page documents how to get it working quickly for basic uses.&lt;br /&gt;
&lt;br /&gt;
Shredder has [[#WebUI|web (WebUI)]] and [[#CLI|command line (CLI)]] interfaces. Both are equivalent in terms of available search plugins and options to pick objects.&lt;br /&gt;
&lt;br /&gt;
== WebUI ==&lt;br /&gt;
&lt;br /&gt;
Only users with SuperUser rights can shred through WebUI.&lt;br /&gt;
&lt;br /&gt;
The easiest way to shred tickets (particularly tickets, users, attachments) is to build a custom search with your desired criteria in the WebUI. You will then have a chance to review and select specific objects then remove them from the database while a backup SQL dump is created.&lt;br /&gt;
&lt;br /&gt;
Note that while the interface indicates it accepts DOS-like wildcards (* and ?) these are translated to the standard SQL wildcards of % and _ internally, and you may specify them directly if you prefer. Specifically this means that the claim * matches non-empty sequences is misleading since % will match null.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Don&#039;t use&#039;&#039;&#039; to delete many objects as shredder is quite slow and may hit browser or server timeout that will abort operation. A large number of target tickets may also result in a query to the web server that is larger than the maximum allowable URI length. Use the CLI instead.&lt;br /&gt;
&lt;br /&gt;
== CLI ==&lt;br /&gt;
&lt;br /&gt;
You can use shredder from command line, here is a few commands to help you start:&lt;br /&gt;
&lt;br /&gt;
 perldoc RT/Shredder.pm&lt;br /&gt;
 rt-shredder --help&lt;br /&gt;
 rt-shredder --plugin help-Tickets&lt;br /&gt;
 rt-shredder --plugin help-Users&lt;br /&gt;
&lt;br /&gt;
See a few [[#Examples examples below]].&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
Examples presented with shell commands, but the same can be performed in the [[#WebUI |WebUI]].&lt;br /&gt;
&lt;br /&gt;
=== Shred Deleted Tickets by Status and Age ===&lt;br /&gt;
You can run the following command by hand and see the results.&lt;br /&gt;
 rt-shredder --plugin &amp;quot;Tickets=query,Status = &#039;Deleted&#039; AND LastUpdated &amp;amp;lt; &#039;30 days ago&#039;;limit,100&amp;quot; --sqldump /{somepath}/shredder-restore-tickets.sql&lt;br /&gt;
&lt;br /&gt;
=== Shred Users with no Tickets ===&lt;br /&gt;
Users with no tickets are users who have had their tickets deleted -- spam senders, or users whose tickets have been moved to another user.&lt;br /&gt;
 rt-shredder --plugin &amp;quot;Users=no_tickets,1;status,any;replace_relations,Nobody;limit,5&amp;quot; --sqldump /{somepath}/shredder-restore-users.sql --force&lt;br /&gt;
&lt;br /&gt;
=== Shred multiple Scrips ===&lt;br /&gt;
 /opt/rt4/sbin/rt-shredder --plugin &amp;quot;Objects=Scrip,26;Scrip,28;Scrip,53&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Daily cron script to purge deleted tickets and unlinked users ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; The example above to &#039;&#039;&#039;delete users with no tickets&#039;&#039;&#039; deletes &#039;&#039;&#039;new admin users&#039;&#039;&#039; who have not yet been assigned or linked with any tickets!&lt;br /&gt;
&lt;br /&gt;
For example, we would create an RT account for a new employee. The cron would then delete their account overnight every day until they had been assigned a ticket.&lt;br /&gt;
(Fortunately it is easy to restore a deleted account from the .sql files.)&lt;br /&gt;
&lt;br /&gt;
To prevent new users getting clobbered, I have modified the user query to exclude users of group &amp;quot;Staff&amp;quot; (in our case) and only delete &#039;&#039;&#039;unprivileged&#039;&#039;&#039; users. This works on RT5.0.5. (Untested on earlier versions.)&lt;br /&gt;
&lt;br /&gt;
   &amp;quot;Users=not_member_of,Staff;member_of,unprivileged;no_tickets,1;status,any;replace_relations,Nobody;limit,50&amp;quot;&lt;br /&gt;
&lt;br /&gt;
You will need to modify this script/query for your use:-&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
&lt;br /&gt;
# robl 20190822&lt;br /&gt;
# Script to expunge tickets and all related objects marked as &amp;quot;deleted&amp;quot; after 30 days.&lt;br /&gt;
# Database cleanup tasks.&lt;br /&gt;
#&lt;br /&gt;
# Backup .sql files are retained.&lt;br /&gt;
&lt;br /&gt;
RT_SHREDDER=&amp;quot;/usr/local/sbin/rt-shredder&amp;quot;&lt;br /&gt;
BACKUPDIR=&amp;quot;/srv/rt5/var/rt-shredder&amp;quot;&lt;br /&gt;
&lt;br /&gt;
# Check things exist:&lt;br /&gt;
&lt;br /&gt;
if [ ! -x &amp;quot;$RT_SHREDDER&amp;quot; ] ; then&lt;br /&gt;
        echo &amp;quot;ERR: could not execute $RT_SHREDDER.&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if [ ! -d &amp;quot;$BACKUPDIR&amp;quot; ] ; then&lt;br /&gt;
        echo &amp;quot;ERR: $BACKUPDIR does not exist, trying to create...&amp;quot;&lt;br /&gt;
        mkdir -p &amp;quot;$BACKUPDIR&amp;quot;&lt;br /&gt;
        chown www-data:www-data $BACKUPDIR&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ ! -d &amp;quot;$BACKUPDIR&amp;quot; ] ; then&lt;br /&gt;
        echo &amp;quot;ERR: $BACKUPDIR could not be created.&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
# Keep running rt-shredder until we get 0 tickets returned:&lt;br /&gt;
&lt;br /&gt;
while :&lt;br /&gt;
  do&lt;br /&gt;
    FILEDATE=$(date +%Y-%m-%d-%H%M%S)&lt;br /&gt;
    SHREDDED=$($RT_SHREDDER --force --plugin &amp;quot;Tickets=query,Status = &#039;Deleted&#039; AND LastUpdated &amp;lt; &#039;30 days ago&#039;;limit,1000&amp;quot; \&lt;br /&gt;
               --sqldump ${BACKUPDIR}/${FILEDATE}-shredder-restore-tickets.sql 2&amp;gt;&amp;amp;1 | grep &amp;quot;RT::Ticket&amp;quot; | wc -l)&lt;br /&gt;
&lt;br /&gt;
    if [ $SHREDDED -ge 1 ] ; then&lt;br /&gt;
       echo &amp;quot;$FILEDATE : Shredded $SHREDDED tickets.&amp;quot;&lt;br /&gt;
       sleep 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ $SHREDDED -eq 0 ]; then&lt;br /&gt;
      break&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
  done&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# Purge users with no tickets.&lt;br /&gt;
# Spam senders, or users whose tickets have been moved to another user.)&lt;br /&gt;
&lt;br /&gt;
while :&lt;br /&gt;
  do&lt;br /&gt;
    FILEDATE=$(date +%Y-%m-%d-%H%M%S)&lt;br /&gt;
    SHREDDED=$($RT_SHREDDER --force --plugin &amp;quot;Users=not_member_of,Staff;member_of,unprivileged;no_tickets,1;status,any;replace_relations,Nobody;limit,50&amp;quot; \&lt;br /&gt;
               --sqldump ${BACKUPDIR}/${FILEDATE}-shredder-restore-users.sql 2&amp;gt;&amp;amp;1 | grep &amp;quot;RT::User&amp;quot; | wc -l)&lt;br /&gt;
&lt;br /&gt;
    if [ $SHREDDED -ge 1 ] ; then&lt;br /&gt;
       echo &amp;quot;$FILEDATE : Shredded $SHREDDED users.&amp;quot;&lt;br /&gt;
       sleep 1&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
    if [ $SHREDDED -eq 0 ]; then&lt;br /&gt;
      break&lt;br /&gt;
    fi&lt;br /&gt;
&lt;br /&gt;
  done&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#&lt;br /&gt;
# Cleanup operations&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
# Delete any 0 length .sql files left by rt-shredder:&lt;br /&gt;
for f in ${BACKUPDIR}/*.sql ; do&lt;br /&gt;
    [ -e &amp;quot;$f&amp;quot; ] &amp;amp;&amp;amp; /usr/bin/find ${BACKUPDIR} -name &#039;*.sql&#039; -size 0 -exec rm {} \;&lt;br /&gt;
    break&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
# Bzip any remaining .sql files:&lt;br /&gt;
for f in ${BACKUPDIR}/*.sql ; do&lt;br /&gt;
    [ -e &amp;quot;$f&amp;quot; ] &amp;amp;&amp;amp;  /usr/bin/bzip2 -q ${BACKUPDIR}/*.sql&lt;br /&gt;
    break&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
# Remove old backup files after a period of time:&lt;br /&gt;
for f in ${BACKUPDIR}/*.bz2 ; do&lt;br /&gt;
    [ -e &amp;quot;$f&amp;quot; ] &amp;amp;&amp;amp;  /usr/bin/find ${BACKUPDIR} -name &#039;*.bz2&#039; -ctime +364 -exec rm {} \;&lt;br /&gt;
    break&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example for /etc/cron.d/rt5:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
# Purge tickets marked as deleted and related records:&lt;br /&gt;
0 23 * * * www-data  /srv/rt5/scripts/RTPurgeDeletedTickets.sh&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Shredding many tickets ==&lt;br /&gt;
&lt;br /&gt;
You may also be interested in the information in [[ShredderControl]].&lt;br /&gt;
&lt;br /&gt;
== Shred ALL TICKETS ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;WARNING WARNING WARNING:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If for some reason you want to reset your entire RT instance&#039;s TICKETS AND TICKET DATA ONLY (and keep Scrips, Custom Fields, etc), you could do something like the following. This was useful for me when I wanted to take our production RT instance and duplicate it onto a development box but not have the huge database full of tickets and ticket-related data.&lt;br /&gt;
&lt;br /&gt;
=== With shredder and shell script ===&lt;br /&gt;
&lt;br /&gt;
Bourne shell syntax is shown below:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
  cd /tmp&lt;br /&gt;
  while :&lt;br /&gt;
  do&lt;br /&gt;
  date&lt;br /&gt;
  SHREDDED=`rt-shredder --plugin &amp;quot;Tickets=query,id &amp;gt; 0;limit,100&amp;quot; --force --sqldump foo.sql 2&amp;gt;&amp;amp;1 | grep RT::Ticket | wc -l`&lt;br /&gt;
  echo &amp;quot;Shredded roughly $SHREDDED tickets.&amp;quot;&lt;br /&gt;
  sleep 3 # let the system get a breath&lt;br /&gt;
  rm -f foo.sql # we don&#039;t care about restoring what we shredded in this case&lt;br /&gt;
  if [ $SHREDDED -eq 0 ]; then&lt;br /&gt;
      break&lt;br /&gt;
  fi&lt;br /&gt;
  done&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== With rt-validator ===&lt;br /&gt;
&lt;br /&gt;
Delete all tickets with SQL command:&lt;br /&gt;
&lt;br /&gt;
 DELETE FROM Tickets;&lt;br /&gt;
&lt;br /&gt;
Use rt-validator to delete records that are broken now:&lt;br /&gt;
&lt;br /&gt;
 ./sbin/rt-validator -c --resolve&lt;br /&gt;
&lt;br /&gt;
=== With rt-delete-tickets-mysql from RT-Extension-Utils ===&lt;br /&gt;
&lt;br /&gt;
Mark all tickets with status deleted with SQL command:&lt;br /&gt;
&lt;br /&gt;
 UPDATE Tickets SET Status = &#039;deleted&#039;;&lt;br /&gt;
&lt;br /&gt;
Use the tool to delete tickets and everything related then check consistency:&lt;br /&gt;
&lt;br /&gt;
  ./sbin/rt-delete-tickets-mysql&lt;br /&gt;
  ./sbin/rt-validator -c&lt;br /&gt;
&lt;br /&gt;
Afterward, you will probably want to do the &amp;quot;Shred Users with no Tickets&amp;quot; run as described above as well.&lt;br /&gt;
&lt;br /&gt;
== Speed up shredding process ==&lt;br /&gt;
You need to set up the database indexes according to [https://docs.bestpractical.com/rt/4.2.16/RT/Shredder.html#Database-Indexes Shredder documentation (RT 4.2.16)]:&lt;br /&gt;
  CREATE INDEX SHREDDER_CGM1 ON CachedGroupMembers(MemberId, GroupId, Disabled);&lt;br /&gt;
  CREATE INDEX SHREDDER_CGM2 ON CachedGroupMembers(ImmediateParentId,MemberId);&lt;br /&gt;
  CREATE INDEX SHREDDER_CGM3 on CachedGroupMembers (Via, Id);&lt;br /&gt;
  CREATE UNIQUE INDEX SHREDDER_GM1 ON GroupMembers(MemberId, GroupId);&lt;br /&gt;
  CREATE INDEX SHREDDER_TXN1 ON Transactions(ReferenceType, OldReference);&lt;br /&gt;
  CREATE INDEX SHREDDER_TXN2 ON Transactions(ReferenceType, NewReference);&lt;br /&gt;
  CREATE INDEX SHREDDER_TXN3 ON Transactions(Type, OldValue);&lt;br /&gt;
  CREATE INDEX SHREDDER_TXN4 ON Transactions(Type, NewValue);&lt;br /&gt;
  CREATE INDEX SHREDDER_ATTACHMENTS1 ON Attachments(Creator);&lt;br /&gt;
&lt;br /&gt;
For newer RT versions ([https://docs.bestpractical.com/rt/4.4.6/RT/Shredder.html#Database-Indexes 4.4.6] and also [https://docs.bestpractical.com/rt/4.4.6/RT/Shredder.html#Database-Indexes 5.0.4]) you may need more indexes:&lt;br /&gt;
  CREATE INDEX SHREDDER_LINKS1 ON Links(Target);&lt;br /&gt;
  CREATE INDEX SHREDDER_ACL1 ON ACL(ObjectType, ObjectId);&lt;br /&gt;
  CREATE INDEX SHREDDER_OCFV1 ON ObjectCustomFieldValues(ObjectType, ObjectId);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Shredder for RT 3.6 and older ==&lt;br /&gt;
&lt;br /&gt;
For older releases extension available from [http://search.cpan.org/dist/RTx-Shredder/lib/RTx/Shredder.pm CPAN].&lt;br /&gt;
However, it&#039;s &#039;&#039;&#039;highly&#039;&#039;&#039; recommended to upgrade RT first and use built in solution.&lt;br /&gt;
[[Category:Shredder]]&lt;br /&gt;
[[Category:Documentation]]&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=Exim4Config&amp;diff=27181</id>
		<title>Exim4Config</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=Exim4Config&amp;diff=27181"/>
		<updated>2023-08-23T07:42:57Z</updated>

		<summary type="html">&lt;p&gt;Robl: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Example for exim4 configs:&lt;br /&gt;
&lt;br /&gt;
== Debian Exim ==&lt;br /&gt;
&lt;br /&gt;
The following was tested on Exim 4.94.2 Debian split config.&lt;br /&gt;
&lt;br /&gt;
# Install either exim4-daemon-light or exim4-daemon-heavy, depending on feature requirements.&lt;br /&gt;
&lt;br /&gt;
exim4-daemon-light is sufficient unless you need database lookup features (MySQL etc.) or content/malware scanning etc.&lt;br /&gt;
&lt;br /&gt;
 apt-get install exim4-daemon-light&lt;br /&gt;
&lt;br /&gt;
=== Initial setup  ===&lt;br /&gt;
&lt;br /&gt;
If RT is on a &#039;&#039;&#039;dedicated server or VM&#039;&#039;&#039;, you will probably want to configure Exim in smarthost mode. &lt;br /&gt;
&lt;br /&gt;
smarthost mode will send all outgoing email to your mail server. There are some exceptions which you may want to configure.&lt;br /&gt;
&lt;br /&gt;
/etc/exim4/update-exim4.conf.conf example below - set any domains for your RT box, and the mail server name,&lt;br /&gt;
and then &#039;&#039;&#039;execute&#039;&#039;&#039; update-exim4.conf&lt;br /&gt;
&lt;br /&gt;
 # /etc/exim4/update-exim4.conf.conf&lt;br /&gt;
 #&lt;br /&gt;
 # Edit this file and /etc/mailname by hand and execute update-exim4.conf&lt;br /&gt;
 # yourself or use &#039;dpkg-reconfigure exim4-config&#039;&lt;br /&gt;
 #&lt;br /&gt;
 # Please note that this is _not_ a dpkg-conffile and that automatic changes&lt;br /&gt;
 # to this file might happen. The code handling this will honor your local&lt;br /&gt;
 # changes, so this is usually fine, but will break local schemes that mess&lt;br /&gt;
 # around with multiple versions of the file.&lt;br /&gt;
 #&lt;br /&gt;
 # update-exim4.conf uses this file to determine variable values to generate&lt;br /&gt;
 # exim configuration macros for the configuration file.&lt;br /&gt;
 #&lt;br /&gt;
 # Most settings found in here do have corresponding questions in the&lt;br /&gt;
 # Debconf configuration, but not all of them.&lt;br /&gt;
 #&lt;br /&gt;
 # This is a Debian specific file&lt;br /&gt;
 &lt;br /&gt;
 dc_eximconfig_configtype=&#039;smarthost&#039;&lt;br /&gt;
 dc_other_hostnames=&#039;rt.example.com:support.example.com&#039;&lt;br /&gt;
 dc_local_interfaces=&#039;&#039;&lt;br /&gt;
 dc_readhost=&#039;&#039;&lt;br /&gt;
 dc_relay_domains=&#039;&#039;&lt;br /&gt;
 dc_minimaldns=&#039;false&#039;&lt;br /&gt;
 dc_relay_nets=&#039;&#039;&lt;br /&gt;
 dc_smarthost=&#039;your-mail-server.example.com&#039;&lt;br /&gt;
 CFILEMODE=&#039;644&#039;&lt;br /&gt;
 dc_use_split_config=&#039;true&#039;&lt;br /&gt;
 dc_hide_mailname=&#039;false&#039;&lt;br /&gt;
 dc_mailname_in_oh=&#039;true&#039;&lt;br /&gt;
 dc_localdelivery=&#039;mail_spool&#039;&lt;br /&gt;
&lt;br /&gt;
=== Macros (Defined in debian-exim config. Allows easy enabling or config of certain features.) ===&lt;br /&gt;
&lt;br /&gt;
/etc/exim4/conf.d/main/000_localmacros:&lt;br /&gt;
&lt;br /&gt;
 REWRITE_LOCAL_DOMAIN = example.com&lt;br /&gt;
 SYSTEM_ALIASES_PIPE_TRANSPORT = address_pipe&lt;br /&gt;
 IGNORE_SMTP_LINE_LENGTH_LIMIT = 1&lt;br /&gt;
 MAIN_TRUSTED_USERS = www-data&lt;br /&gt;
&lt;br /&gt;
=== Outgoing mail - extra tweaks ===&lt;br /&gt;
&lt;br /&gt;
==== 1. Optional: Allow exceptions to sending everything to the smarthost, for example &#039;&#039;&#039;some&#039;&#039;&#039; local mailboxes. ====&lt;br /&gt;
&lt;br /&gt;
The following will deliver mail to bob@rt.example.com, but &#039;&#039;&#039;only if&#039;&#039;&#039; the user bob actually exists,&lt;br /&gt;
&#039;&#039;&#039;and&#039;&#039;&#039; there is a mailbox file in /var/mail/bob&lt;br /&gt;
&lt;br /&gt;
/etc/exim4/conf.d/router/900_exim4-config_local_user&lt;br /&gt;
&lt;br /&gt;
 # /etc/exim4/conf.d/router/900_exim4-config_local_user&lt;br /&gt;
 #&lt;br /&gt;
 # This router matches local user mailboxes.&lt;br /&gt;
   &lt;br /&gt;
 local_user:&lt;br /&gt;
   debug_print = &amp;quot;R: local_user for $local_part@$domain&amp;quot;&lt;br /&gt;
   driver = accept&lt;br /&gt;
   domains = +local_domains&lt;br /&gt;
   require_files = /var/mail/${local_part}&lt;br /&gt;
   check_local_user&lt;br /&gt;
   # local_parts = ! root&lt;br /&gt;
   transport = LOCAL_DELIVERY&lt;br /&gt;
   cannot_route_message = Unknown user&lt;br /&gt;
&lt;br /&gt;
Everything else will get sent to the smarthost, (Even if the user exists, a mailbox file must also exist)&lt;br /&gt;
&lt;br /&gt;
Test with:&lt;br /&gt;
&lt;br /&gt;
  # touch /var/mail/bob &amp;amp;&amp;amp; chown bob:bob /var/mail/bob&lt;br /&gt;
  # service exim4 restart&lt;br /&gt;
  # id bob&lt;br /&gt;
  uid=1002(bob) gid=1002(bob) groups=1002(bob),27(sudo)&lt;br /&gt;
  # exim -bt bob@rt.example.com&lt;br /&gt;
  R: system_aliases for bob@rt.example.com&lt;br /&gt;
  R: userforward for bob@rt.example.com&lt;br /&gt;
  R: procmail for bob@rt.example.com&lt;br /&gt;
  R: maildrop for bob@rt.example.com&lt;br /&gt;
  R: lowuid_aliases for bob@rt.example.com (UID 1002)&lt;br /&gt;
  R: local_user for bob@rt.example.com&lt;br /&gt;
 bob@rt.example.com&lt;br /&gt;
  router = local_user, transport = mail_spool&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== 2. Optional: Reroute *@rt.example.com to *@example.com ====&lt;br /&gt;
&lt;br /&gt;
Bear in mind that Exim will be the MTA for everything on the local machine, not just RT. It can be annoying if local processes,&lt;br /&gt;
(for example cron) send mail to local users where it will either bounce, or sit in /var/mail and never be seen.&lt;br /&gt;
&lt;br /&gt;
To ensure local mail gets delivered to a user&#039;s actual mailbox (and save a bit of /etc/aliases hell), the following&lt;br /&gt;
will deliver any mail for local users to the main domain (and via the smarthost):&lt;br /&gt;
&lt;br /&gt;
 # /etc/exim4/conf.d/router/950_exim4-config_local_user_smarthost&lt;br /&gt;
 # force delivery of user@rt.example.com -&amp;gt; user@example.com:&lt;br /&gt;
 &lt;br /&gt;
 not_local_user:&lt;br /&gt;
   debug_print = &amp;quot;R: not_local_user for $local_part@$domain&amp;quot;&lt;br /&gt;
   driver = redirect&lt;br /&gt;
   domains = +local_domains&lt;br /&gt;
   data = ${local_part}@REWRITE_LOCAL_DOMAIN&lt;br /&gt;
   no_verify&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Test with:&lt;br /&gt;
&lt;br /&gt;
  # service exim4 restart&lt;br /&gt;
  # exim -bt bob@rt.example.com&lt;br /&gt;
   R: system_aliases for bob@rt.example.com&lt;br /&gt;
   R: userforward for bob@rt.example.com&lt;br /&gt;
   R: procmail for bob@rt.example.com&lt;br /&gt;
   R: maildrop for bob@rt.example.com&lt;br /&gt;
   R: lowuid_aliases for bob@rt.example.com (UID 1002)&lt;br /&gt;
   R: local_user for bob@rt.example.com&lt;br /&gt;
   R: not_local_user for bob@rt.example.com&lt;br /&gt;
   R: smarthost for bob@example.com&lt;br /&gt;
   bob@example.com&lt;br /&gt;
   &amp;lt;-- bob@rt.example.com&lt;br /&gt;
   router = smarthost, transport = remote_smtp_smarthost&lt;br /&gt;
   host your-mail-server.example.com [x:x:x::x]&lt;br /&gt;
   host your-mail-server.example.com [x.x.x.x]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Incoming mail ===&lt;br /&gt;
&lt;br /&gt;
For incoming mail to RT, the simplest method is to add the queue aliases to /etc/aliases:&lt;br /&gt;
&lt;br /&gt;
On your mail server (if appropriate):&lt;br /&gt;
&lt;br /&gt;
 ### RT/Ticketer Addresses:&lt;br /&gt;
 sales:                  sales@rt.example.com&lt;br /&gt;
 sales-comment:          sales-comment@rt.example.com&lt;br /&gt;
 support:                support@rt.example.com&lt;br /&gt;
 support-comment:        support-comment@rt.example.com&lt;br /&gt;
 accounts:               accounts@rt.example.com&lt;br /&gt;
 accounts-comment:       accounts-comment@rt.example.com&lt;br /&gt;
&lt;br /&gt;
On the RT server:&lt;br /&gt;
&lt;br /&gt;
 # forwarders for RT queues:&lt;br /&gt;
 &lt;br /&gt;
 sales: &amp;quot;|/usr/bin/rt-mailgate --queue \&amp;quot;Sales\&amp;quot; --action correspond --url https://rt.example.com/rt&amp;quot;&lt;br /&gt;
 sales-comment: &amp;quot;|/usr/bin/rt-mailgate --queue \&amp;quot;Sales\&amp;quot; --action comment --url https://rt.example.com/rt&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 support: &amp;quot;|/usr/bin/rt-mailgate --queue \&amp;quot;Support\&amp;quot; --action correspond --url https://rt.example.com/rt&amp;quot;&lt;br /&gt;
 support-comment: &amp;quot;|/usr/bin/rt-mailgate --queue \&amp;quot;Support\&amp;quot; --action comment --url https://rt.example.com/rt&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 accounts: &amp;quot;|/usr/bin/rt-mailgate --queue \&amp;quot;Accounts\&amp;quot; --action correspond --url https://rt.example.com/rt&amp;quot;&lt;br /&gt;
 accounts-comment: &amp;quot;|/usr/bin/rt-mailgate --queue \&amp;quot;Accounts\&amp;quot; --action comment --url https://rt.example.com/rt&amp;quot;&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=EximConfig&amp;diff=27180</id>
		<title>EximConfig</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=EximConfig&amp;diff=27180"/>
		<updated>2023-08-23T03:17:33Z</updated>

		<summary type="html">&lt;p&gt;Robl: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;If you are using exim as mail transport (for instance on Debian), you may need specific configuration&lt;br /&gt;
&lt;br /&gt;
* [[Exim4Config]] - Debian exim4 example config (Separate mail host and RT servers)&lt;br /&gt;
* [[EximConfigVirtualDomain]] - Configure exim virtual domains for RT mail. For specific case where you need a full virtual domain system in exim so that RT can be supported in a virtual domain.&lt;br /&gt;
* For more basic case, you may try and check if this might suit your needs : http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=238345&lt;br /&gt;
* [[ConfigEximFromRTDB]] - configure Exim to use [[Queue]] info from RT database.&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=Exim4Config&amp;diff=27179</id>
		<title>Exim4Config</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=Exim4Config&amp;diff=27179"/>
		<updated>2023-08-23T02:51:13Z</updated>

		<summary type="html">&lt;p&gt;Robl: Created page with &amp;quot; Example for exim4 configs:  == Debian Exim ==  The following was tested on Exim 4.94.2 Debian split config.  # Install either exim4-daemon-light or exim4-daemon-heavy, depending on feature requirements.  exim4-daemon-light is sufficient unless you need database lookup features (MySQL etc.) or content/malware scanning etc.   apt-get install exim4-daemon-light  === Initial setup  ===  If RT is on a &amp;#039;&amp;#039;&amp;#039;dedicated server or VM&amp;#039;&amp;#039;&amp;#039;, you will probably want to configure Exim in...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Example for exim4 configs:&lt;br /&gt;
&lt;br /&gt;
== Debian Exim ==&lt;br /&gt;
&lt;br /&gt;
The following was tested on Exim 4.94.2 Debian split config.&lt;br /&gt;
&lt;br /&gt;
# Install either exim4-daemon-light or exim4-daemon-heavy, depending on feature requirements.&lt;br /&gt;
&lt;br /&gt;
exim4-daemon-light is sufficient unless you need database lookup features (MySQL etc.) or content/malware scanning etc.&lt;br /&gt;
&lt;br /&gt;
 apt-get install exim4-daemon-light&lt;br /&gt;
&lt;br /&gt;
=== Initial setup  ===&lt;br /&gt;
&lt;br /&gt;
If RT is on a &#039;&#039;&#039;dedicated server or VM&#039;&#039;&#039;, you will probably want to configure Exim in smarthost mode. &lt;br /&gt;
&lt;br /&gt;
smarthost mode will send all outgoing email to your mail server. There are some exceptions which you may want to configure.&lt;br /&gt;
&lt;br /&gt;
/etc/exim4/update-exim4.conf.conf example below - set any domains for your RT box, and the mail server name,&lt;br /&gt;
and then &#039;&#039;&#039;execute&#039;&#039;&#039; update-exim4.conf&lt;br /&gt;
&lt;br /&gt;
 # /etc/exim4/update-exim4.conf.conf&lt;br /&gt;
 #&lt;br /&gt;
 # Edit this file and /etc/mailname by hand and execute update-exim4.conf&lt;br /&gt;
 # yourself or use &#039;dpkg-reconfigure exim4-config&#039;&lt;br /&gt;
 #&lt;br /&gt;
 # Please note that this is _not_ a dpkg-conffile and that automatic changes&lt;br /&gt;
 # to this file might happen. The code handling this will honor your local&lt;br /&gt;
 # changes, so this is usually fine, but will break local schemes that mess&lt;br /&gt;
 # around with multiple versions of the file.&lt;br /&gt;
 #&lt;br /&gt;
 # update-exim4.conf uses this file to determine variable values to generate&lt;br /&gt;
 # exim configuration macros for the configuration file.&lt;br /&gt;
 #&lt;br /&gt;
 # Most settings found in here do have corresponding questions in the&lt;br /&gt;
 # Debconf configuration, but not all of them.&lt;br /&gt;
 #&lt;br /&gt;
 # This is a Debian specific file&lt;br /&gt;
 &lt;br /&gt;
 dc_eximconfig_configtype=&#039;smarthost&#039;&lt;br /&gt;
 dc_other_hostnames=&#039;rt.example.com:support.example.com&#039;&lt;br /&gt;
 dc_local_interfaces=&#039;&#039;&lt;br /&gt;
 dc_readhost=&#039;&#039;&lt;br /&gt;
 dc_relay_domains=&#039;&#039;&lt;br /&gt;
 dc_minimaldns=&#039;false&#039;&lt;br /&gt;
 dc_relay_nets=&#039;&#039;&lt;br /&gt;
 dc_smarthost=&#039;your-mail-server.example.com&#039;&lt;br /&gt;
 CFILEMODE=&#039;644&#039;&lt;br /&gt;
 dc_use_split_config=&#039;true&#039;&lt;br /&gt;
 dc_hide_mailname=&#039;false&#039;&lt;br /&gt;
 dc_mailname_in_oh=&#039;true&#039;&lt;br /&gt;
 dc_localdelivery=&#039;mail_spool&#039;&lt;br /&gt;
&lt;br /&gt;
=== Macros (Defined in debian-exim config. Allows easy enabling or config of certain features.) ===&lt;br /&gt;
&lt;br /&gt;
/etc/exim4/conf.d/main/000_localmacros:&lt;br /&gt;
&lt;br /&gt;
 REWRITE_LOCAL_DOMAIN = example.com&lt;br /&gt;
 SYSTEM_ALIASES_PIPE_TRANSPORT = address_pipe&lt;br /&gt;
 IGNORE_SMTP_LINE_LENGTH_LIMIT = 1&lt;br /&gt;
 MAIN_TRUSTED_USERS = www-data&lt;br /&gt;
&lt;br /&gt;
=== Outgoing mail - extra tweaks ===&lt;br /&gt;
&lt;br /&gt;
==== 1. Optional: Allow exceptions to sending everything to the smarthost, for example &#039;&#039;&#039;some&#039;&#039;&#039; local mailboxes. ====&lt;br /&gt;
&lt;br /&gt;
The following will deliver mail to bob@rt.example.com, but &#039;&#039;&#039;only if&#039;&#039;&#039; the user bob actually exists,&lt;br /&gt;
&#039;&#039;&#039;and&#039;&#039;&#039; there is a mailbox file in /var/mail/bob&lt;br /&gt;
&lt;br /&gt;
/etc/exim4/conf.d/router/900_exim4-config_local_user&lt;br /&gt;
&lt;br /&gt;
 # /etc/exim4/conf.d/router/900_exim4-config_local_user&lt;br /&gt;
 #&lt;br /&gt;
 # This router matches local user mailboxes.&lt;br /&gt;
   &lt;br /&gt;
 local_user:&lt;br /&gt;
   debug_print = &amp;quot;R: local_user for $local_part@$domain&amp;quot;&lt;br /&gt;
   driver = accept&lt;br /&gt;
   domains = +local_domains&lt;br /&gt;
   require_files = /var/mail/${local_part}&lt;br /&gt;
   check_local_user&lt;br /&gt;
   # local_parts = ! root&lt;br /&gt;
   transport = LOCAL_DELIVERY&lt;br /&gt;
   cannot_route_message = Unknown user&lt;br /&gt;
&lt;br /&gt;
Everything else will get sent to the smarthost, (Even if the user exists, a mailbox file must also exist)&lt;br /&gt;
&lt;br /&gt;
Test with:&lt;br /&gt;
&lt;br /&gt;
  # touch /var/mail/bob &amp;amp;&amp;amp; chown bob:bob /var/mail/bob&lt;br /&gt;
  # service exim4 restart&lt;br /&gt;
  # id bob&lt;br /&gt;
  uid=1002(bob) gid=1002(bob) groups=1002(bob),27(sudo)&lt;br /&gt;
  # exim -bt bob@rt.example.com&lt;br /&gt;
  R: system_aliases for bob@rt.example.com&lt;br /&gt;
  R: userforward for bob@rt.example.com&lt;br /&gt;
  R: procmail for bob@rt.example.com&lt;br /&gt;
  R: maildrop for bob@rt.example.com&lt;br /&gt;
  R: lowuid_aliases for bob@rt.example.com (UID 1002)&lt;br /&gt;
  R: local_user for bob@rt.example.com&lt;br /&gt;
 bob@rt.example.com&lt;br /&gt;
  router = local_user, transport = mail_spool&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== 2. Optional: Reroute *@rt.example.com to *@example.com ====&lt;br /&gt;
&lt;br /&gt;
Bear in mind that Exim will be the MTA for everything on the local machine, not just RT. It can be annoying if local processes,&lt;br /&gt;
(for example cron) send mail to local users where it will either bounce, or sit in /var/mail and never be seen.&lt;br /&gt;
&lt;br /&gt;
To ensure local mail gets delivered to a user&#039;s actual mailbox (and save a bit of /etc/aliases hell), the following&lt;br /&gt;
will deliver any mail for local users to the main domain (and via the smarthost):&lt;br /&gt;
&lt;br /&gt;
 # /etc/exim4/conf.d/router/950_exim4-config_local_user_smarthost&lt;br /&gt;
 # force delivery of user@rt.example.com -&amp;gt; user@example.com:&lt;br /&gt;
 &lt;br /&gt;
 not_local_user:&lt;br /&gt;
   debug_print = &amp;quot;R: not_local_user for $local_part@$domain&amp;quot;&lt;br /&gt;
   driver = redirect&lt;br /&gt;
   domains = +local_domains&lt;br /&gt;
   data = ${local_part}@REWRITE_LOCAL_DOMAIN&lt;br /&gt;
   no_verify&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Test with:&lt;br /&gt;
&lt;br /&gt;
  # service exim4 restart&lt;br /&gt;
  # exim -bt bob@rt.example.com&lt;br /&gt;
   R: system_aliases for bob@rt.example.com&lt;br /&gt;
   R: userforward for bob@rt.example.com&lt;br /&gt;
   R: procmail for bob@rt.example.com&lt;br /&gt;
   R: maildrop for bob@rt.example.com&lt;br /&gt;
   R: lowuid_aliases for bob@rt.example.com (UID 1002)&lt;br /&gt;
   R: local_user for bob@rt.example.com&lt;br /&gt;
   R: not_local_user for bob@rt.example.com&lt;br /&gt;
   R: smarthost for bob@example.com&lt;br /&gt;
   bob@example.com&lt;br /&gt;
   &amp;lt;-- bob@support.lonap.net&lt;br /&gt;
   router = smarthost, transport = remote_smtp_smarthost&lt;br /&gt;
   host your-mail-server.example.com [x:x:x::x]&lt;br /&gt;
   host your-mail-server.example.com [x.x.x.x]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Incoming mail ===&lt;br /&gt;
&lt;br /&gt;
For incoming mail to RT, the simplest method is to add the queue aliases to /etc/aliases:&lt;br /&gt;
&lt;br /&gt;
On your mail server (if appropriate):&lt;br /&gt;
&lt;br /&gt;
 ### RT/Ticketer Addresses:&lt;br /&gt;
 sales:                  sales@rt.example.com&lt;br /&gt;
 sales-comment:          sales-comment@rt.example.com&lt;br /&gt;
 support:                support@rt.example.com&lt;br /&gt;
 support-comment:        support-comment@rt.example.com&lt;br /&gt;
 accounts:               accounts@rt.example.com&lt;br /&gt;
 accounts-comment:       accounts-comment@rt.example.com&lt;br /&gt;
&lt;br /&gt;
On the RT server:&lt;br /&gt;
&lt;br /&gt;
 # forwarders for RT queues:&lt;br /&gt;
 &lt;br /&gt;
 sales: &amp;quot;|/usr/bin/rt-mailgate --queue \&amp;quot;Sales\&amp;quot; --action correspond --url https://rt.example.com/rt&amp;quot;&lt;br /&gt;
 sales-comment: &amp;quot;|/usr/bin/rt-mailgate --queue \&amp;quot;Sales\&amp;quot; --action comment --url https://rt.example.com/rt&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 support: &amp;quot;|/usr/bin/rt-mailgate --queue \&amp;quot;Support\&amp;quot; --action correspond --url https://rt.example.com/rt&amp;quot;&lt;br /&gt;
 support-comment: &amp;quot;|/usr/bin/rt-mailgate --queue \&amp;quot;Support\&amp;quot; --action comment --url https://rt.example.com/rt&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 accounts: &amp;quot;|/usr/bin/rt-mailgate --queue \&amp;quot;Accounts\&amp;quot; --action correspond --url https://rt.example.com/rt&amp;quot;&lt;br /&gt;
 accounts-comment: &amp;quot;|/usr/bin/rt-mailgate --queue \&amp;quot;Accounts\&amp;quot; --action comment --url https://rt.example.com/rt&amp;quot;&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespond&amp;diff=27145</id>
		<title>AddWatchersOnCorrespond</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespond&amp;diff=27145"/>
		<updated>2023-05-03T11:47:08Z</updated>

		<summary type="html">&lt;p&gt;Robl: BUGFIX: add CreatorEmailAddr as lower case. Check for bounce addresses lower case.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= AddWatchersOnCorrespond =&lt;br /&gt;
&lt;br /&gt;
This RT [[Scrip]] will add the person making the correspondence as a [[Watcher]] to the ticket if they are not already a [[Watcher]], also, if the transaction originated from an email message, the script will scan the email headers and add all other recipients to the ticket as [[Watcher]]s (if they are not yet [[Watcher]]s). This can be used to complement the [[ParseNewMessageForTicketCcs]] [[SiteConfig]] option, which is part of the [[EmailInterface]] and does the same thing when tickets are created.&lt;br /&gt;
&lt;br /&gt;
In our RT setup, we have a group named &#039;&#039;&#039;general&#039;&#039;&#039; 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 &#039;&#039;&#039;general&#039;&#039;&#039; group, then the Scrip will add them as an [[AdminCC]] [[Watcher]] instead of a [[CC]] [[Watcher]]. It should be fairly easy for others who do not need this feature to remove or modify this [[Scrip]] accordingly.&lt;br /&gt;
&lt;br /&gt;
I wrote this [[Scrip]] to replace the patch we used to make to the RT Email Interface code, called [[ParseFollowupMessageForTicketCcs]]. Accordingly, it carries the same security warning about allowing basically &#039;&#039;&#039;anyone&#039;&#039;&#039; to add themselves to &#039;&#039;&#039;any&#039;&#039;&#039; ticket, simply by sending an appropriately formatted email. Some RT sites might not want this behaviour, but it is necessary for us.&lt;br /&gt;
&lt;br /&gt;
* [[AddWatchersOnCorrespondDomains]] is a modified version of this Scrip which is more restrictive by domain.&lt;br /&gt;
&lt;br /&gt;
Changelog&lt;br /&gt;
&lt;br /&gt;
2012-01-01: Fixed a bug in which the owner of the ticket would have been added as a Cc: for every reply he made. (HaimDimer)&lt;br /&gt;
&lt;br /&gt;
2022-12-06: Populate RealName from email header when creating new user. (RT inserts the user&#039;s email address in the &amp;quot;phrase&amp;quot; part of the From: header if RealName is empty. This fix prevents emails bouncing because a mail provider&#039;s anti-spoofing policy rejects mail with an email address in the &amp;quot;phrase&amp;quot; section of From:/Cc: headers. )&lt;br /&gt;
&lt;br /&gt;
2023-05-03: (RobL) BUGFIX: add CreatorEmailAddr as lower case. Check for bounce addresses lower case.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Description:&#039;&#039;&#039; [[AddWatchersOnCorrespond]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Condition:&#039;&#039;&#039; On Correspond&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; User Defined&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Template:&#039;&#039;&#039; Global template: Blank&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Stage:&#039;&#039;&#039; [[TransactionBatch]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom condition:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action preparation code:&#039;&#039;&#039; &amp;lt;code&amp;gt;return 1;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action cleanup code:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Get some info:&lt;br /&gt;
my $scrip = &#039;Scrip:AddWatchersOnCorrespond&#039;;&lt;br /&gt;
my $Transaction = $self-&amp;gt;TransactionObj;&lt;br /&gt;
my $EmailAddr = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;EmailAddress;&lt;br /&gt;
my $Queue = $self-&amp;gt;TicketObj-&amp;gt;QueueObj;&lt;br /&gt;
my $Ticket = $self-&amp;gt;TicketObj;&lt;br /&gt;
my $Id = $self-&amp;gt;TicketObj-&amp;gt;id;&lt;br /&gt;
&lt;br /&gt;
# Extract a list of people associated with this transaction:&lt;br /&gt;
#  - including the transaction creator, and if it is an email, the sender and recipients of that email&lt;br /&gt;
my %People;&lt;br /&gt;
my $CreatorEmailAddr = lc $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;EmailAddress;&lt;br /&gt;
my $CreatorRealName  = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;RealName;&lt;br /&gt;
$People{$CreatorEmailAddr}{RealName} = GetFullName($CreatorEmailAddr,$CreatorRealName);&lt;br /&gt;
&lt;br /&gt;
foreach my $h (qw(From To Cc)) {&lt;br /&gt;
    my $header = $Transaction-&amp;gt;Attachments-&amp;gt;First-&amp;gt;GetHeader($h);&lt;br /&gt;
    my @addr = Mail::Address-&amp;gt;parse($header);&lt;br /&gt;
    foreach my $addrobj (@addr) {&lt;br /&gt;
        my $addr = lc $RT::Nobody-&amp;gt;UserObj-&amp;gt;CanonicalizeEmailAddress($addrobj-&amp;gt;address);&lt;br /&gt;
        # Ignore the specific addresses for this queue:&lt;br /&gt;
        next if lc $Queue-&amp;gt;CorrespondAddress eq $addr;&lt;br /&gt;
        next if lc $Queue-&amp;gt;CommentAddress eq $addr;&lt;br /&gt;
        # Ignore any email address that looks like one for ANY of our queues:&lt;br /&gt;
        next if RT::EmailParser-&amp;gt;IsRTAddress($addr);&lt;br /&gt;
 &lt;br /&gt;
        # Get phrase from address header for full name: &amp;quot;Fred Bloggs&amp;quot; &amp;lt;fred@example.com&amp;gt; &lt;br /&gt;
        # (Extract Fred Bloggs to fullname.) Clean up. If nothing suitable then make something from email.&lt;br /&gt;
        my $fullname = GetFullName($addr,$addrobj-&amp;gt;phrase);&lt;br /&gt;
        $People{$addr}{RealName} = $fullname;&lt;br /&gt;
        $RT::Logger-&amp;gt;debug(&amp;quot;$scrip: Ticket #$Id correspondence contains header - $h: $addr $fullname&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Lookup the &#039;experts&#039; (general) group to use below:&lt;br /&gt;
my $Experts = RT::Group-&amp;gt;new($self-&amp;gt;CurrentUser);&lt;br /&gt;
$Experts-&amp;gt;LoadUserDefinedGroup(&#039;general&#039;);&lt;br /&gt;
&lt;br /&gt;
# Now check if each user is already watching the ticket or queue:&lt;br /&gt;
foreach my $addr (sort keys %People) {&lt;br /&gt;
&lt;br /&gt;
    next if ($addr =~ /^(postmaster|root|mailer-daemon)\@/);&lt;br /&gt;
&lt;br /&gt;
    my $User = RT::User-&amp;gt;new( $RT::SystemUser );&lt;br /&gt;
    $User-&amp;gt;LoadOrCreateByEmail(&lt;br /&gt;
        RealName     =&amp;gt; $People{$addr}{RealName},&lt;br /&gt;
        EmailAddress =&amp;gt; $addr,&lt;br /&gt;
        Comments     =&amp;gt; &amp;quot;Autocreated by $scrip&amp;quot;,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    my $Name = $User-&amp;gt;Name;&lt;br /&gt;
    my $Principal = $User-&amp;gt;PrincipalId;&lt;br /&gt;
    if ( not ($Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Requestor&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsOwner($User) )) {&lt;br /&gt;
        # If the user is a member of the experts group, then add them as an AdminCc, otherwise as a Cc:&lt;br /&gt;
        my $type = &#039;Cc&#039;;&lt;br /&gt;
        $type = &#039;AdminCc&#039; if $Experts-&amp;gt;HasMember($User-&amp;gt;PrincipalObj);&lt;br /&gt;
        # Add the new watcher now and check for errors:&lt;br /&gt;
        my ($ret, $msg) = $Ticket-&amp;gt;AddWatcher(Type  =&amp;gt; $type, PrincipalId =&amp;gt; $Principal);&lt;br /&gt;
        if ($ret) {&lt;br /&gt;
            $RT::Logger-&amp;gt;info(&amp;quot;$scrip: New $type watcher added to ticket #$Id: $addr $fullname (#$Principal)&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $RT::Logger-&amp;gt;error(&amp;quot;$scrip: Failed to add new $type watcher to ticket #$Id: $addr (#$Principal) - $msg&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetFullName {&lt;br /&gt;
&lt;br /&gt;
    # Get a nicely formatted name for RT RealName Field:&lt;br /&gt;
&lt;br /&gt;
    # If fullname is blank, make something up from the&lt;br /&gt;
    # local_part of the email address: &amp;quot;fred.bloggs@...&amp;quot; -&amp;gt; &amp;quot;Fred Bloggs&amp;quot;.&lt;br /&gt;
    # Do not allow fullname to contain &amp;quot;@&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
    my ($addr,$fullname) = @_;&lt;br /&gt;
    my ($local_part,$domain) = split(&#039;@&#039;, $addr);&lt;br /&gt;
&lt;br /&gt;
     if (($fullname eq &#039;&#039;) || ($fullname =~ /\@/)) {&lt;br /&gt;
      $fullname = $local_part;&lt;br /&gt;
      $fullname =~ s/[\._-]/ /g;&lt;br /&gt;
      $fullname =~ s/(\w+)/\u$1/g;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     $fullname =~ s/^[\&amp;quot;\&#039;]|[\&amp;quot;\&#039;]$//g; # strip leading/trailing &amp;quot; or &#039;&lt;br /&gt;
     $fullname =~ s/^\s+|\s+$//g; # strip leading/trailing spaces&lt;br /&gt;
&lt;br /&gt;
    return $fullname;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
return 1;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 # vim:ft=perl:&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Category:RT Action]]&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27144</id>
		<title>AddWatchersOnCorrespondDomains</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27144"/>
		<updated>2023-05-03T11:43:51Z</updated>

		<summary type="html">&lt;p&gt;Robl: BUGFIX: add CreatorEmailAddr as lower case. Check for bounce addresses lower case.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= AddWatchersOnCorrespondDomains =&lt;br /&gt;
&lt;br /&gt;
This RT Scrip is a modified version of [[AddWatchersOnCorrespond]] - AddWatchersOnCorrespond simply adds &#039;&#039;&#039;all&#039;&#039;&#039; recipients as watchers.&lt;br /&gt;
&lt;br /&gt;
This Scrip is the same, except it is more restrictive with the requestors added:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;AddWatchersOnCorrespondDomains&amp;lt;/code&amp;gt; will add the person making the correspondence as a [[Watcher]] to the ticket if they are not already a [[Watcher]].&lt;br /&gt;
&lt;br /&gt;
If the transaction originated from an email message, the scrip will scan the email headers and add other recipients to the ticket as [[Watcher]]s, only if:&lt;br /&gt;
&lt;br /&gt;
* They are not already a [[Watcher]], &#039;&#039;&#039;and&#039;&#039;&#039;:&lt;br /&gt;
* Their email address has the &#039;&#039;&#039;same domain&#039;&#039;&#039; (or from a subdomain) as another watcher &#039;&#039;&#039;already&#039;&#039;&#039; on the ticket.&lt;br /&gt;
&lt;br /&gt;
* e.g. New To: and Cc: recipients added by the customer will &#039;&#039;&#039;only&#039;&#039;&#039; be added as watchers if they are from the same domain as a requestor already on the ticket.&lt;br /&gt;
* (Third parties not already on the ticket will not be added.)&lt;br /&gt;
&lt;br /&gt;
* We prefer to add as &#039;&#039;&#039;Requestors&#039;&#039;&#039; rather than Cc to simplify ticket updates. (All &amp;quot;Replies to Requestors&amp;quot; always go to everyone on the ticket.) If you do not want this behaviour, change the line: &amp;lt;code&amp;gt;my $type = &#039;Requestor&#039;;&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;my $type = &#039;Cc&#039;;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* This accommodates the majority of our use cases:&lt;br /&gt;
* 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:&lt;br /&gt;
* After this executes, We also use [https://metacpan.org/pod/RT::Extension::NonWatcherRecipients RT-Extension-NonWatcherRecipients] to add a warning into the Admins email notification of any recipients on the email that are not on the ticket.&lt;br /&gt;
&lt;br /&gt;
* In our RT setup, we have a group named &#039;&#039;&#039;Staff&#039;&#039;&#039; 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 &#039;&#039;&#039;Staff&#039;&#039;&#039; 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.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;NOTE:&#039;&#039;&#039; If you &#039;&#039;&#039;remove&#039;&#039;&#039; 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 -&amp;gt; Modify who receives mail for ticket). They will not receive further email from the ticket. (This is known as a &amp;quot;Squelched&amp;quot; watcher in RT.)&lt;br /&gt;
&lt;br /&gt;
==== Changelog ====&lt;br /&gt;
&lt;br /&gt;
* 2022-12-07: (RobL) Created. Update to &#039;&#039;&#039;always populate&#039;&#039;&#039; Real Name field. See [https://forum.bestpractical.com/t/rt-autocreated-watcher-from-header-causing-bounces/37708/4 this forum post].&lt;br /&gt;
* 2023-05-03: (RobL) BUGFIX: add CreatorEmailAddr as lower case. Check for bounce addresses lower case.&lt;br /&gt;
&lt;br /&gt;
==== Bugs/Ideas/TODO ====&lt;br /&gt;
&lt;br /&gt;
# Add &amp;lt;code&amp;gt;never_auth&amp;lt;/code&amp;gt; list: Exclude certain domains where it is not safe to assume &amp;quot;same domain = same organisation&amp;quot; e.g. gmail.com, ymail.com, 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.)&lt;br /&gt;
# Allow for &#039;&#039;&#039;equivalent domains&#039;&#039;&#039;: Ability to specify for example, &amp;lt;code&amp;gt;example.com&amp;lt;/code&amp;gt; is the same as &amp;lt;code&amp;gt;example.co.uk&amp;lt;/code&amp;gt;&lt;br /&gt;
# 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., &amp;lt;code&amp;gt;fred@example.net&amp;lt;/code&amp;gt; is already a Requestor, and Cc:&#039;s &amp;lt;code&amp;gt;random@gmail.com&amp;lt;/code&amp;gt;. If &amp;lt;code&amp;gt;random@gmail.com&amp;lt;/code&amp;gt; is already a user in RT and is a member of the same customer group as &amp;lt;code&amp;gt;example.net&amp;lt;/code&amp;gt; then add them as a watcher.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Description:&#039;&#039;&#039; [[AddWatchersOnCorrespondDomains]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Condition:&#039;&#039;&#039; On Correspond&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; User Defined&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Template:&#039;&#039;&#039; Global template: Blank&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Stage:&#039;&#039;&#039; [[Normal]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom condition:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action preparation code:&#039;&#039;&#039; &amp;lt;code&amp;gt;return 1;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action cleanup code:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Scrip to automatically add Cc: from incoming emails to tickets, if the following conditions apply:&lt;br /&gt;
# 1. Sender is sending from a domain that is already a watcher on the ticket (Requestor/Cc/AdminCc)&lt;br /&gt;
# 2. New Cc is from the same domain, or a subdomain.&lt;br /&gt;
#&lt;br /&gt;
# This Scrip is based on AddWatchersOnCorrespond https://rt-wiki.bestpractical.com/wiki/AddWatchersOnCorrespond&lt;br /&gt;
# But does not add just any cc:&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Mary Jane &amp;lt;mjane@emea.isp.com&amp;gt;&lt;br /&gt;
#&lt;br /&gt;
# - If fred@isp.com (or any isp.com or *.isp.com) address is already a watcher on the ticket,&lt;br /&gt;
#   then mjane@emea.isp.com will be automatically added as a Cc watcher.&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Random Helpdesk &amp;lt;helpdesk@bigcolo.net&amp;gt;&lt;br /&gt;
# - helpdesk@bigcolo.net will NOT be added as a Cc: to the ticket if &amp;quot;bigcolo.net&amp;quot; is not already&lt;br /&gt;
#   a watcher on the ticket.&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
my %People;&lt;br /&gt;
&lt;br /&gt;
# Get some info:&lt;br /&gt;
my $scrip = &#039;Scrip:AddWatchersOnCorrespondDomains&#039;;&lt;br /&gt;
my $Transaction = $self-&amp;gt;TransactionObj;&lt;br /&gt;
my $Queue = $self-&amp;gt;TicketObj-&amp;gt;QueueObj;&lt;br /&gt;
my $Ticket = $self-&amp;gt;TicketObj;&lt;br /&gt;
my $Id = $self-&amp;gt;TicketObj-&amp;gt;id;&lt;br /&gt;
&lt;br /&gt;
# Load a list of all domains of people on the ticket already:&lt;br /&gt;
my @TicketDomains = GetTicketDomains();&lt;br /&gt;
&lt;br /&gt;
# Extract a list of people associated with this transaction:&lt;br /&gt;
#  - including the transaction creator, and if it is an email, the sender and recipients of that email&lt;br /&gt;
&lt;br /&gt;
my $CreatorEmailAddr = lc $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;EmailAddress;&lt;br /&gt;
my $CreatorRealName  = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;RealName;&lt;br /&gt;
$People{$CreatorEmailAddr}{RealName} = GetFullName($CreatorEmailAddr,$CreatorRealName);&lt;br /&gt;
&lt;br /&gt;
foreach my $h (qw(From To Cc)) {&lt;br /&gt;
    my $header = $Transaction-&amp;gt;Attachments-&amp;gt;First-&amp;gt;GetHeader($h);&lt;br /&gt;
    my @addr = Mail::Address-&amp;gt;parse($header);&lt;br /&gt;
    foreach my $addrobj (@addr) {&lt;br /&gt;
        my $addr = lc $RT::Nobody-&amp;gt;UserObj-&amp;gt;CanonicalizeEmailAddress($addrobj-&amp;gt;address);&lt;br /&gt;
        # Ignore the specific addresses for this queue:&lt;br /&gt;
        next if lc $Queue-&amp;gt;CorrespondAddress eq $addr;&lt;br /&gt;
        next if lc $Queue-&amp;gt;CommentAddress eq $addr;&lt;br /&gt;
        # Ignore any email address that looks like one for ANY of our queues:&lt;br /&gt;
        next if RT::EmailParser-&amp;gt;IsRTAddress($addr);&lt;br /&gt;
        my $fullname = GetFullName($addr,$addrobj-&amp;gt;phrase);&lt;br /&gt;
        $People{$addr}{RealName} = $fullname;&lt;br /&gt;
        $RT::Logger-&amp;gt;debug(&amp;quot;$scrip: Ticket #$Id correspondence contains header - $h: $addr $fullname&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Lookup the &#039;experts&#039; (staff) group to use below:&lt;br /&gt;
my $Experts = RT::Group-&amp;gt;new($self-&amp;gt;CurrentUser);&lt;br /&gt;
$Experts-&amp;gt;LoadUserDefinedGroup(&#039;Staff&#039;);&lt;br /&gt;
&lt;br /&gt;
# Now check if each user is already watching the ticket or queue:&lt;br /&gt;
&lt;br /&gt;
foreach my $addr (sort keys %People) {&lt;br /&gt;
&lt;br /&gt;
    next if ($addr =~ /^(postmaster|root|mailer-daemon)\@/);&lt;br /&gt;
&lt;br /&gt;
    my $User = RT::User-&amp;gt;new( $RT::SystemUser );&lt;br /&gt;
    $User-&amp;gt;LoadOrCreateByEmail(&lt;br /&gt;
        RealName     =&amp;gt; $People{$addr}{RealName},&lt;br /&gt;
        EmailAddress =&amp;gt; $addr,&lt;br /&gt;
        Comments     =&amp;gt; &amp;quot;Autocreated by $scrip&amp;quot;,&lt;br /&gt;
    );&lt;br /&gt;
    &lt;br /&gt;
    my $Name = $User-&amp;gt;Name;&lt;br /&gt;
    my $Principal = $User-&amp;gt;PrincipalId;&lt;br /&gt;
    &lt;br /&gt;
    if ( not ($Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Requestor&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsOwner($User) )) {&lt;br /&gt;
     &lt;br /&gt;
     # check if the new cc: person&#039;s domain matches a domain already on the ticket.&lt;br /&gt;
     # If so, add them as a new watcher:&lt;br /&gt;
     if (is_address_authdomain($addr,@TicketDomains)) {&lt;br /&gt;
        # If the user is a member of the experts group, then add them as an AdminCc, otherwise as a Cc:&lt;br /&gt;
        # my $type = &#039;Cc&#039;;&lt;br /&gt;
        my $type = &#039;Requestor&#039;;&lt;br /&gt;
        $type = &#039;AdminCc&#039; if $Experts-&amp;gt;HasMember($User-&amp;gt;PrincipalObj);&lt;br /&gt;
        # Add the new watcher now and check for errors:&lt;br /&gt;
        my ($ret, $msg) = $Ticket-&amp;gt;AddWatcher(Type  =&amp;gt; $type, PrincipalId =&amp;gt; $Principal);&lt;br /&gt;
        if ($ret) {&lt;br /&gt;
            $RT::Logger-&amp;gt;info(&amp;quot;$scrip: New $type watcher added to ticket #$Id: $addr (#$Principal)&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $RT::Logger-&amp;gt;error(&amp;quot;$scrip: Failed to add new $type watcher to ticket #$Id: $addr (#$Principal) - $msg&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetFullName {&lt;br /&gt;
&lt;br /&gt;
    # Get a nicely formatted name for RT RealName Field:&lt;br /&gt;
&lt;br /&gt;
    # If fullname is blank, make something up from the&lt;br /&gt;
    # local_part of the email address: &amp;quot;fred.bloggs@...&amp;quot; -&amp;gt; &amp;quot;Fred Bloggs&amp;quot;.&lt;br /&gt;
    # Do not allow fullname to contain &amp;quot;@&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
    my ($addr,$fullname) = @_;&lt;br /&gt;
    my ($local_part,$domain) = split(&#039;@&#039;, $addr);&lt;br /&gt;
&lt;br /&gt;
     if (($fullname eq &#039;&#039;) || ($fullname =~ /\@/)) {&lt;br /&gt;
      $fullname = $local_part;&lt;br /&gt;
      $fullname =~ s/[\._-]/ /g;&lt;br /&gt;
      $fullname =~ s/(\w+)/\u$1/g;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     $fullname =~ s/^[\&amp;quot;\&#039;]|[\&amp;quot;\&#039;]$//g; # strip leading/trailing &amp;quot; or &#039;&lt;br /&gt;
     $fullname =~ s/^\s+|\s+$//g; # strip leading/trailing spaces&lt;br /&gt;
&lt;br /&gt;
    return $fullname;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub is_address_authdomain {&lt;br /&gt;
&lt;br /&gt;
        # Is address in an authorised domain?&lt;br /&gt;
        # Also allow subdomain of existing domain&lt;br /&gt;
&lt;br /&gt;
        my $addr = shift;&lt;br /&gt;
        my @domains = @_;&lt;br /&gt;
        $addr = lc($addr);&lt;br /&gt;
&lt;br /&gt;
        my ($undef,$domain) = split(&#039;@&#039;,$addr);&lt;br /&gt;
        my $is_auth = 0;&lt;br /&gt;
&lt;br /&gt;
        foreach my $authdomain (@domains) {&lt;br /&gt;
&lt;br /&gt;
           # if ($domain eq $authdomain)      { $is_auth=1; last; };&lt;br /&gt;
           if ($domain =~ /(^|(\.?))$authdomain$/) { $is_auth=1; last; };&lt;br /&gt;
           if ($authdomain =~ /(^|(\.?))$domain$/) { $is_auth=1; last; };&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return($is_auth);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetTicketDomains {&lt;br /&gt;
&lt;br /&gt;
    # my $self = shift;&lt;br /&gt;
	&lt;br /&gt;
    # Get list of existing email addresses from the ticket, and push all the domains.&lt;br /&gt;
    # We&#039;ll check this later. Any cc: by an existing requestor will be allowed from &lt;br /&gt;
    # the same domain or a subdomain.&lt;br /&gt;
&lt;br /&gt;
    # For each Role, get RoleAddresses: &amp;quot;foo@bar.com, foo@baz.com&amp;quot; &lt;br /&gt;
    # Return a deduped domain list.&lt;br /&gt;
&lt;br /&gt;
    my %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
     foreach my $role (qw(Requestor Cc AdminCc)) {&lt;br /&gt;
&lt;br /&gt;
        my $roleaddresses = lc($self-&amp;gt;TicketObj-&amp;gt;RoleAddresses($role));&lt;br /&gt;
               foreach my $a (split(&#039;, &#039;, $roleaddresses)) {&lt;br /&gt;
                 my (undef,$domain) = split(&#039;@&#039;,$a);&lt;br /&gt;
                 next if ($allticketdomains{$domain});&lt;br /&gt;
                 $allticketdomains{$domain} = $domain;&lt;br /&gt;
                }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      my @ticketdomains = sort keys %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
      return (@ticketdomains);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
return 1;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 # vim:ft=perl:&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=MigrateMysql2PostgresqlV4&amp;diff=27134</id>
		<title>MigrateMysql2PostgresqlV4</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=MigrateMysql2PostgresqlV4&amp;diff=27134"/>
		<updated>2023-01-04T17:41:03Z</updated>

		<summary type="html">&lt;p&gt;Robl: chown -&amp;gt; chmod&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== What is this page ? ==&lt;br /&gt;
As I&#039;m currently migrating from RT v4.2.2 on Mysql to v4.2.2 on Postgresql and didn&#039;t find an easy tutorial about this, I&#039;ve decided to write this quick page describingthe steps I&#039;ve used.&lt;br /&gt;
&lt;br /&gt;
I tested these instructions while moving from a Debian6-based RT server on Mysql to a Debian7-based RT server on Postgresql: this means I do have an old server (the &amp;quot;source server&amp;quot; and a new server the &amp;quot;destination server&amp;quot;). Of course you&#039;ll have to adapt these steps to your environment.&lt;br /&gt;
&lt;br /&gt;
== Prepare the new server ==&lt;br /&gt;
=== Prepare your Postgresql server ===&lt;br /&gt;
Edit /etc/postgresql/9.1/main/pg_hba.conf, and add the following lines before the not-commented ones&lt;br /&gt;
 host    template1       rt_user                 127.0.0.1 255.255.255.255       password&lt;br /&gt;
 local   template1       rt_user                                                 password &lt;br /&gt;
 host    rt4             rt_user                 127.0.0.1 255.255.255.255       password&lt;br /&gt;
 local   rt4             rt_user                                                 password&lt;br /&gt;
 &lt;br /&gt;
Restart Postgres:&lt;br /&gt;
 service postgresql restart&lt;br /&gt;
&lt;br /&gt;
As user postgres, create the new role:&lt;br /&gt;
 su - postgres&lt;br /&gt;
 $ psql&lt;br /&gt;
 postgres=# CREATE USER rt_user WITH PASSWORD &#039;DBPASS&#039; CREATEDB NOCREATEUSER;&lt;br /&gt;
 postgres=# \q&lt;br /&gt;
&lt;br /&gt;
For newer versions of postgres, may need something like:&lt;br /&gt;
 su - postgres&lt;br /&gt;
 $ psql&lt;br /&gt;
 postgres=# create ROLE rt_user SUPERUSER LOGIN PASSWORD &#039;DBPASS&#039;;&lt;br /&gt;
 postgres=# createdb rt4&lt;br /&gt;
 postgres-# grant all privileges on database rt4 to rt_user&lt;br /&gt;
 postgres-# \q&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Install RT (from source in my case) ===&lt;br /&gt;
Install basic dependencies (compiling environment, perl libraries, apache, porstgresql server, ...).&lt;br /&gt;
&lt;br /&gt;
Configure you RT installation (in the source tree) and enable Postgresql, depending on your options, you can choose for instance:&lt;br /&gt;
 /path/to/extractedsources/configure --enable-graphviz --enable-gd --with-db-type=Pg --with-db-rt-pass=&amp;quot;DBPASS&amp;quot; --with-web-user=www-data --with-web-group=www-data&lt;br /&gt;
&lt;br /&gt;
Note that the default DB user is &amp;quot;rt_user&amp;quot; and the default dbname is &amp;quot;rt4&amp;quot;. Of course you&#039;ll have to change DBPASS by your password.&lt;br /&gt;
&lt;br /&gt;
Install missing dependencies:&lt;br /&gt;
 make fixdeps&lt;br /&gt;
&lt;br /&gt;
Check with &lt;br /&gt;
 make testdeps&lt;br /&gt;
&lt;br /&gt;
Install:&lt;br /&gt;
 make install&lt;br /&gt;
&lt;br /&gt;
&amp;lt;u&amp;gt;&#039;&#039;&#039;CAUTION: do NOT initialize the database as the script may ask (do NOT run &amp;quot;make initdb&amp;quot;)&#039;&#039;&#039;&amp;lt;/u&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
If you&#039;ve setup the database with initdb, you won&#039;t be able to import your cloned database. In this case, and in this case only, you can run:&lt;br /&gt;
 /opt/rt4/sbin/rt-setup-database --action drop --dba rt_user --dba-password DBPASS&lt;br /&gt;
&lt;br /&gt;
Install whatever extra RT extension you may need.&lt;br /&gt;
&lt;br /&gt;
=== Export your source RT database ===&lt;br /&gt;
If your RT version is older than v4.2.5, You&#039;ll have first to apply a patch so that your export contains the Scrips/Queues bindings (see http://issues.bestpractical.com/Ticket/Display.html?id=29949).&lt;br /&gt;
&lt;br /&gt;
The patch looks like the following for v4.2.2:&lt;br /&gt;
 &amp;lt;pre&amp;gt;&lt;br /&gt;
 diff -u lib/RT/Migrate/Serializer.pm.orig lib/RT/Migrate/Serializer.pm&lt;br /&gt;
 --- lib/RT/Migrate/Serializer.pm.orig   2014-05-31 08:56:52.108512591 +0200&lt;br /&gt;
 +++ lib/RT/Migrate/Serializer.pm        2014-05-31 08:59:02.994003819 +0200&lt;br /&gt;
 @@ -172,7 +172,7 @@&lt;br /&gt;
     $self-&amp;gt;PushCollections(qw(ACL));&lt;br /&gt;
 &lt;br /&gt;
     # Scrips&lt;br /&gt;
 -    $self-&amp;gt;PushCollections(qw(Scrips ScripActions ScripConditions Templates));&lt;br /&gt;
 +    $self-&amp;gt;PushCollections(qw(Scrips ObjectScrips ScripActions ScripConditions Templates));&lt;br /&gt;
 &lt;br /&gt;
     # Attributes&lt;br /&gt;
     $self-&amp;gt;PushCollections(qw(Attributes));&lt;br /&gt;
 &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Users should not update tickets in RT during the migration.&lt;br /&gt;
&lt;br /&gt;
It is recommended to temporarily shut down the MTA to prevent new messages arriving during the migration, &lt;br /&gt;
for example, if your RT server is running exim4 MTA (Debian default):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;service exim4 stop&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will cause incoming messages to be queued on remote servers.&lt;br /&gt;
&lt;br /&gt;
Then, on your source server, export your database using rt-serializer:&lt;br /&gt;
 sbin/rt-serializer --directory /path/to/export/rt-serializer-data --clone &lt;br /&gt;
&lt;br /&gt;
Then tar the resulting directory, copy it to the new server and untar the archive.&lt;br /&gt;
&lt;br /&gt;
=== Import your RT database ===&lt;br /&gt;
Prepare your destination database:&lt;br /&gt;
 sbin/rt-setup-database --action create,schema,acl --dba rt_user --dba-password  DBPASS&lt;br /&gt;
&lt;br /&gt;
If your RT version is older than v4.2.5, You&#039;ll have first to apply a patch so that rt-importer can import the Scrips/Queues bindings (see http://issues.bestpractical.com/Ticket/Display.html?id=29949).&lt;br /&gt;
&lt;br /&gt;
The patch looks like the following for v4.2.2:&lt;br /&gt;
 &amp;lt;pre&amp;gt;&lt;br /&gt;
 diff -u lib/RT.pm.orig lib/RT.pm&lt;br /&gt;
 --- lib/RT.pm.orig      2014-06-02 17:00:14.879786262 +0200&lt;br /&gt;
 +++ lib/RT.pm   2014-06-02 17:00:40.560077022 +0200&lt;br /&gt;
 @@ -524,6 +524,7 @@&lt;br /&gt;
         RT::ScripAction&lt;br /&gt;
         RT::ScripCondition&lt;br /&gt;
         RT::Scrip&lt;br /&gt;
 +       RT::ObjectScrip&lt;br /&gt;
         RT::Group&lt;br /&gt;
         RT::GroupMember&lt;br /&gt;
         RT::CustomField&lt;br /&gt;
 &amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Import the Database:&lt;br /&gt;
 sbin/rt-importer /path/to/exported/rt-serializer-data&lt;br /&gt;
&lt;br /&gt;
We now need to fix the Postgresql sequence numbers as they are not updated by rt-importer, see http://issues.bestpractical.com/Ticket/Display.html?id=29158. In order to automatically fix your sequences, you can create the following script as &amp;quot;Update-Pg-Sequences.sh&amp;quot;:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/bash&lt;br /&gt;
# Request-Tracker mygration to Postgresql Companion script&lt;br /&gt;
#-------------------------------------------------------&lt;br /&gt;
# When migrating to postgresql, rt-importer doesn&#039;t update the&lt;br /&gt;
# last_value of sequences.&lt;br /&gt;
#&lt;br /&gt;
# This script is a quick and dirty fix that updates the Sequences&lt;br /&gt;
# last value from the MAX_id used in each table&lt;br /&gt;
#&lt;br /&gt;
# This script must be run as user postgres:&lt;br /&gt;
# sudo -u postgres /path/to/Update-Pg-Sequences.sh&lt;br /&gt;
#set -x&lt;br /&gt;
  DBNAME=&amp;quot;rt4&amp;quot;&lt;br /&gt;
  cd /tmp&lt;br /&gt;
  # First list all sequences from DB&lt;br /&gt;
LIST=&amp;quot;$(psql -t -d $DBNAME -c &#039;select sequence_name from information_schema.sequences;&#039;)&amp;quot;&lt;br /&gt;
declare -A before_seq&lt;br /&gt;
declare -A after_seq&lt;br /&gt;
# For each sequence, read the last value, then update to the MAX id, and&lt;br /&gt;
# read again the last value&lt;br /&gt;
for i in $LIST&lt;br /&gt;
do&lt;br /&gt;
# Let&#039;s parse the table name and columnname that corresponds to the sequence&lt;br /&gt;
tablename=$(echo $i | cut -d&amp;quot;_&amp;quot; -f1)&lt;br /&gt;
columnname=$(echo $i | cut -d&amp;quot;_&amp;quot; -f2)&lt;br /&gt;
# Read the initial sequence last_value&lt;br /&gt;
 sql_lastval=&amp;quot;select last_value from $i&amp;quot;&lt;br /&gt;
 before_seq[&amp;quot;$i&amp;quot;]=$(psql -t -d $DBNAME -c &amp;quot;$sql_lastval&amp;quot; | tr -d &#039; &#039;)&lt;br /&gt;
# Setting the sequence to the MAX of id(s)&lt;br /&gt;
 sql=&amp;quot;select setval(&#039;$i&#039;,max($columnname)) from $tablename;&amp;quot;&lt;br /&gt;
 cmd=$(psql -t -d $DBNAME -c &amp;quot;$sql&amp;quot;)&lt;br /&gt;
# Let&#039;s read again the sequence last_value&lt;br /&gt;
# Yes I know this last step is not required as we already have the value in cmd&lt;br /&gt;
# but I like to double check the results ;-)&lt;br /&gt;
after_seq[&amp;quot;$i&amp;quot;]=$(psql -t -d $DBNAME -c &amp;quot;$sql_lastval&amp;quot; | tr -d &#039; &#039;)&lt;br /&gt;
 echo &amp;quot;Sequence &#039;$i&#039;, before=${before_seq[$i]}, after=${after_seq[$i]}&amp;quot;&lt;br /&gt;
done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Make it executable:&lt;br /&gt;
 chmod 777 /path/to/Update-Pg-Sequences.sh&lt;br /&gt;
&lt;br /&gt;
Then run it:&lt;br /&gt;
 sudo -u postgres /path/to/Update-Pg-Sequences.sh&lt;br /&gt;
&lt;br /&gt;
=== Retrieve your old parameters ===&lt;br /&gt;
Now is time to complete your configuration file RT_SiteConfig.pm with your parameters from the source server.&lt;br /&gt;
&lt;br /&gt;
Then setup your Webserver as required, and start it.&lt;br /&gt;
&lt;br /&gt;
You can setup your mailgate as well as your crontab entries.&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget any customization you may have done such as local html pages.&lt;br /&gt;
&lt;br /&gt;
=== External Storage ===&lt;br /&gt;
&lt;br /&gt;
If you use ExternalStorage  /sbin/rt-externalize-attachments to store attachments to disk instead of the database,&lt;br /&gt;
you need to copy these files to the new server. They may be stored in, for example /opt/rt5/var/attachments&lt;br /&gt;
(or whatever path configured in RT_SiteConfig.pm ExternalStorage)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
 # tar zcvf rt_attachments.tar.gz /opt/rt5/var/attachments&lt;br /&gt;
 # scp rt_attachments.tar.gz root@your-new-server:/opt/rt5/var/attachments&lt;br /&gt;
 # ...&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Perl Modules / Debian Buster ===&lt;br /&gt;
&lt;br /&gt;
(2020 update): When migrating to postgresql 13, the stock perl DBD::Pg module shipped in Debian buster&lt;br /&gt;
package didn&#039;t work properly. (For example deleting due dates to an empty value to unset it, crashed RT with an error.)&lt;br /&gt;
&lt;br /&gt;
This is only an issue if you are using a newer version of postgresql instead of the shipped Debian &lt;br /&gt;
package version. (postgresql-11)&lt;br /&gt;
&lt;br /&gt;
To resolve this, I had to remove the libdbd-pg-perl package and just install the newest DBD::Pg from CPAN.&lt;br /&gt;
(needs to be at least 3.14.x)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
 # apt-get remove libdbd-pg-perl&lt;br /&gt;
 # apt-get install cpanminus build-essential libpq-dev&lt;br /&gt;
 # cpanm DBD::Pg&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(DBD::Pg 3.14.2 is currently in Debian testing, so hopefully this issue will be resolved in the next Debian release.)&lt;br /&gt;
&lt;br /&gt;
=== FullText indexing ===&lt;br /&gt;
You may now want to setup FullText indexes:&lt;br /&gt;
 sbin/rt-setup-fulltext-index --dba rt_user --dba-password DBPASS&lt;br /&gt;
 &lt;br /&gt;
Then add the proposed setup lines to your /opt/rt4/etc/RT_SiteConfig.pm file, for instance:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
 Set( %FullTextSearch,&lt;br /&gt;
    Enable     =&amp;gt; 1,&lt;br /&gt;
    Indexed    =&amp;gt; 1,&lt;br /&gt;
    Column     =&amp;gt; &#039;ContentIndex&#039;,&lt;br /&gt;
    Table      =&amp;gt; &#039;Attachments&#039;,&lt;br /&gt;
 );&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Now run a first indexing&lt;br /&gt;
 ./sbin/rt-fulltext-indexer -–all &lt;br /&gt;
&lt;br /&gt;
Then make it a cron job:&lt;br /&gt;
 ln -s /opt/rt4/sbin/rt-fulltext-indexer /etc/cron.hourly&lt;br /&gt;
&lt;br /&gt;
Your RT server is now migrated to Postgresql.&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespond&amp;diff=27128</id>
		<title>AddWatchersOnCorrespond</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespond&amp;diff=27128"/>
		<updated>2022-12-16T10:33:08Z</updated>

		<summary type="html">&lt;p&gt;Robl: add category&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= AddWatchersOnCorrespond =&lt;br /&gt;
&lt;br /&gt;
This RT [[Scrip]] will add the person making the correspondence as a [[Watcher]] to the ticket if they are not already a [[Watcher]], also, if the transaction originated from an email message, the script will scan the email headers and add all other recipients to the ticket as [[Watcher]]s (if they are not yet [[Watcher]]s). This can be used to complement the [[ParseNewMessageForTicketCcs]] [[SiteConfig]] option, which is part of the [[EmailInterface]] and does the same thing when tickets are created.&lt;br /&gt;
&lt;br /&gt;
In our RT setup, we have a group named &#039;&#039;&#039;general&#039;&#039;&#039; 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 &#039;&#039;&#039;general&#039;&#039;&#039; group, then the Scrip will add them as an [[AdminCC]] [[Watcher]] instead of a [[CC]] [[Watcher]]. It should be fairly easy for others who do not need this feature to remove or modify this [[Scrip]] accordingly.&lt;br /&gt;
&lt;br /&gt;
I wrote this [[Scrip]] to replace the patch we used to make to the RT Email Interface code, called [[ParseFollowupMessageForTicketCcs]]. Accordingly, it carries the same security warning about allowing basically &#039;&#039;&#039;anyone&#039;&#039;&#039; to add themselves to &#039;&#039;&#039;any&#039;&#039;&#039; ticket, simply by sending an appropriately formatted email. Some RT sites might not want this behaviour, but it is necessary for us.&lt;br /&gt;
&lt;br /&gt;
* [[AddWatchersOnCorrespondDomains]] is a modified version of this Scrip which is more restrictive by domain.&lt;br /&gt;
&lt;br /&gt;
Changelog&lt;br /&gt;
&lt;br /&gt;
2012-01-01: Fixed a bug in which the owner of the ticket would have been added as a Cc: for every reply he made. (HaimDimer)&lt;br /&gt;
&lt;br /&gt;
2022-12-06: Populate RealName from email header when creating new user. (RT inserts the user&#039;s email address in the &amp;quot;phrase&amp;quot; part of the From: header if RealName is empty. This fix prevents emails bouncing because a mail provider&#039;s anti-spoofing policy rejects mail with an email address in the &amp;quot;phrase&amp;quot; section of From:/Cc: headers. )&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Description:&#039;&#039;&#039; [[AddWatchersOnCorrespond]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Condition:&#039;&#039;&#039; On Correspond&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; User Defined&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Template:&#039;&#039;&#039; Global template: Blank&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Stage:&#039;&#039;&#039; [[TransactionBatch]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom condition:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action preparation code:&#039;&#039;&#039; &amp;lt;code&amp;gt;return 1;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action cleanup code:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Get some info:&lt;br /&gt;
my $scrip = &#039;Scrip:AddWatchersOnCorrespond&#039;;&lt;br /&gt;
my $Transaction = $self-&amp;gt;TransactionObj;&lt;br /&gt;
my $EmailAddr = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;EmailAddress;&lt;br /&gt;
my $Queue = $self-&amp;gt;TicketObj-&amp;gt;QueueObj;&lt;br /&gt;
my $Ticket = $self-&amp;gt;TicketObj;&lt;br /&gt;
my $Id = $self-&amp;gt;TicketObj-&amp;gt;id;&lt;br /&gt;
&lt;br /&gt;
# Extract a list of people associated with this transaction:&lt;br /&gt;
#  - including the transaction creator, and if it is an email, the sender and recipients of that email&lt;br /&gt;
my %People;&lt;br /&gt;
my $CreatorEmailAddr = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;EmailAddress;&lt;br /&gt;
my $CreatorRealName  = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;RealName;&lt;br /&gt;
$People{$CreatorEmailAddr}{RealName} = GetFullName($CreatorEmailAddr,$CreatorRealName);&lt;br /&gt;
&lt;br /&gt;
foreach my $h (qw(From To Cc)) {&lt;br /&gt;
    my $header = $Transaction-&amp;gt;Attachments-&amp;gt;First-&amp;gt;GetHeader($h);&lt;br /&gt;
    my @addr = Mail::Address-&amp;gt;parse($header);&lt;br /&gt;
    foreach my $addrobj (@addr) {&lt;br /&gt;
        my $addr = lc $RT::Nobody-&amp;gt;UserObj-&amp;gt;CanonicalizeEmailAddress($addrobj-&amp;gt;address);&lt;br /&gt;
        # Ignore the specific addresses for this queue:&lt;br /&gt;
        next if lc $Queue-&amp;gt;CorrespondAddress eq $addr;&lt;br /&gt;
        next if lc $Queue-&amp;gt;CommentAddress eq $addr;&lt;br /&gt;
        # Ignore any email address that looks like one for ANY of our queues:&lt;br /&gt;
        next if RT::EmailParser-&amp;gt;IsRTAddress($addr);&lt;br /&gt;
 &lt;br /&gt;
        # Get phrase from address header for full name: &amp;quot;Fred Bloggs&amp;quot; &amp;lt;fred@example.com&amp;gt; &lt;br /&gt;
        # (Extract Fred Bloggs to fullname.) Clean up. If nothing suitable then make something from email.&lt;br /&gt;
        my $fullname = GetFullName($addr,$addrobj-&amp;gt;phrase);&lt;br /&gt;
        $People{$addr}{RealName} = $fullname;&lt;br /&gt;
        $RT::Logger-&amp;gt;debug(&amp;quot;$scrip: Ticket #$Id correspondence contains header - $h: $addr $fullname&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Lookup the &#039;experts&#039; (general) group to use below:&lt;br /&gt;
my $Experts = RT::Group-&amp;gt;new($self-&amp;gt;CurrentUser);&lt;br /&gt;
$Experts-&amp;gt;LoadUserDefinedGroup(&#039;general&#039;);&lt;br /&gt;
&lt;br /&gt;
# Now check if each user is already watching the ticket or queue:&lt;br /&gt;
foreach my $addr (sort keys %People) {&lt;br /&gt;
&lt;br /&gt;
    next if ($addr =~ /^(postmaster|root|Mailer-Daemon)\@/);&lt;br /&gt;
&lt;br /&gt;
    my $User = RT::User-&amp;gt;new( $RT::SystemUser );&lt;br /&gt;
    $User-&amp;gt;LoadOrCreateByEmail(&lt;br /&gt;
        RealName     =&amp;gt; $People{$addr}{RealName},&lt;br /&gt;
        EmailAddress =&amp;gt; $addr,&lt;br /&gt;
        Comments     =&amp;gt; &amp;quot;Autocreated by $scrip&amp;quot;,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    my $Name = $User-&amp;gt;Name;&lt;br /&gt;
    my $Principal = $User-&amp;gt;PrincipalId;&lt;br /&gt;
    if ( not ($Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Requestor&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsOwner($User) )) {&lt;br /&gt;
        # If the user is a member of the experts group, then add them as an AdminCc, otherwise as a Cc:&lt;br /&gt;
        my $type = &#039;Cc&#039;;&lt;br /&gt;
        $type = &#039;AdminCc&#039; if $Experts-&amp;gt;HasMember($User-&amp;gt;PrincipalObj);&lt;br /&gt;
        # Add the new watcher now and check for errors:&lt;br /&gt;
        my ($ret, $msg) = $Ticket-&amp;gt;AddWatcher(Type  =&amp;gt; $type, PrincipalId =&amp;gt; $Principal);&lt;br /&gt;
        if ($ret) {&lt;br /&gt;
            $RT::Logger-&amp;gt;info(&amp;quot;$scrip: New $type watcher added to ticket #$Id: $addr $fullname (#$Principal)&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $RT::Logger-&amp;gt;error(&amp;quot;$scrip: Failed to add new $type watcher to ticket #$Id: $addr (#$Principal) - $msg&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetFullName {&lt;br /&gt;
&lt;br /&gt;
    # Get a nicely formatted name for RT RealName Field:&lt;br /&gt;
&lt;br /&gt;
    # If fullname is blank, make something up from the&lt;br /&gt;
    # local_part of the email address: &amp;quot;fred.bloggs@...&amp;quot; -&amp;gt; &amp;quot;Fred Bloggs&amp;quot;.&lt;br /&gt;
    # Do not allow fullname to contain &amp;quot;@&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
    my ($addr,$fullname) = @_;&lt;br /&gt;
    my ($local_part,$domain) = split(&#039;@&#039;, $addr);&lt;br /&gt;
&lt;br /&gt;
     if (($fullname eq &#039;&#039;) || ($fullname =~ /\@/)) {&lt;br /&gt;
      $fullname = $local_part;&lt;br /&gt;
      $fullname =~ s/[\._-]/ /g;&lt;br /&gt;
      $fullname =~ s/(\w+)/\u$1/g;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     $fullname =~ s/^[\&amp;quot;\&#039;]|[\&amp;quot;\&#039;]$//g; # strip leading/trailing &amp;quot; or &#039;&lt;br /&gt;
     $fullname =~ s/^\s+|\s+$//g; # strip leading/trailing spaces&lt;br /&gt;
&lt;br /&gt;
    return $fullname;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
return 1;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 # vim:ft=perl:&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
[[Category:RT Action]]&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27127</id>
		<title>AddWatchersOnCorrespondDomains</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27127"/>
		<updated>2022-12-08T14:33:38Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Bugs/Ideas/TODO */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= AddWatchersOnCorrespondDomains =&lt;br /&gt;
&lt;br /&gt;
This RT Scrip is a modified version of [[AddWatchersOnCorrespond]] - AddWatchersOnCorrespond simply adds &#039;&#039;&#039;all&#039;&#039;&#039; recipients as watchers.&lt;br /&gt;
&lt;br /&gt;
This Scrip is the same, except it is more restrictive with the requestors added:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;AddWatchersOnCorrespondDomains&amp;lt;/code&amp;gt; will add the person making the correspondence as a [[Watcher]] to the ticket if they are not already a [[Watcher]].&lt;br /&gt;
&lt;br /&gt;
If the transaction originated from an email message, the scrip will scan the email headers and add other recipients to the ticket as [[Watcher]]s, only if:&lt;br /&gt;
&lt;br /&gt;
* They are not already a [[Watcher]], &#039;&#039;&#039;and&#039;&#039;&#039;:&lt;br /&gt;
* Their email address has the &#039;&#039;&#039;same domain&#039;&#039;&#039; (or from a subdomain) as another watcher &#039;&#039;&#039;already&#039;&#039;&#039; on the ticket.&lt;br /&gt;
&lt;br /&gt;
* e.g. New To: and Cc: recipients added by the customer will &#039;&#039;&#039;only&#039;&#039;&#039; be added as watchers if they are from the same domain as a requestor already on the ticket.&lt;br /&gt;
* (Third parties not already on the ticket will not be added.)&lt;br /&gt;
&lt;br /&gt;
* We prefer to add as &#039;&#039;&#039;Requestors&#039;&#039;&#039; rather than Cc to simplify ticket updates. (All &amp;quot;Replies to Requestors&amp;quot; always go to everyone on the ticket.) If you do not want this behaviour, change the line: &amp;lt;code&amp;gt;my $type = &#039;Requestor&#039;;&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;my $type = &#039;Cc&#039;;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* This accommodates the majority of our use cases:&lt;br /&gt;
* 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:&lt;br /&gt;
* After this executes, We also use [https://metacpan.org/pod/RT::Extension::NonWatcherRecipients RT-Extension-NonWatcherRecipients] to add a warning into the Admins email notification of any recipients on the email that are not on the ticket.&lt;br /&gt;
&lt;br /&gt;
* In our RT setup, we have a group named &#039;&#039;&#039;Staff&#039;&#039;&#039; 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 &#039;&#039;&#039;Staff&#039;&#039;&#039; 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.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;NOTE:&#039;&#039;&#039; If you &#039;&#039;&#039;remove&#039;&#039;&#039; 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 -&amp;gt; Modify who receives mail for ticket). They will not receive further email from the ticket. (This is known as a &amp;quot;Squelched&amp;quot; watcher in RT.)&lt;br /&gt;
&lt;br /&gt;
==== Changelog ====&lt;br /&gt;
&lt;br /&gt;
* 2022-12-07: (RobL) Created. Update to &#039;&#039;&#039;always populate&#039;&#039;&#039; Real Name field. See [https://forum.bestpractical.com/t/rt-autocreated-watcher-from-header-causing-bounces/37708/4 this forum post].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Bugs/Ideas/TODO ====&lt;br /&gt;
&lt;br /&gt;
# Add &amp;lt;code&amp;gt;never_auth&amp;lt;/code&amp;gt; list: Exclude certain domains where it is not safe to assume &amp;quot;same domain = same organisation&amp;quot; e.g. gmail.com, ymail.com, 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.)&lt;br /&gt;
# Allow for &#039;&#039;&#039;equivalent domains&#039;&#039;&#039;: Ability to specify for example, &amp;lt;code&amp;gt;example.com&amp;lt;/code&amp;gt; is the same as &amp;lt;code&amp;gt;example.co.uk&amp;lt;/code&amp;gt;&lt;br /&gt;
# 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., &amp;lt;code&amp;gt;fred@example.net&amp;lt;/code&amp;gt; is already a Requestor, and Cc:&#039;s &amp;lt;code&amp;gt;random@gmail.com&amp;lt;/code&amp;gt;. If &amp;lt;code&amp;gt;random@gmail.com&amp;lt;/code&amp;gt; is already a user in RT and is a member of the same customer group as &amp;lt;code&amp;gt;example.net&amp;lt;/code&amp;gt; then add them as a watcher.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Description:&#039;&#039;&#039; [[AddWatchersOnCorrespondDomains]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Condition:&#039;&#039;&#039; On Correspond&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; User Defined&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Template:&#039;&#039;&#039; Global template: Blank&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Stage:&#039;&#039;&#039; [[Normal]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom condition:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action preparation code:&#039;&#039;&#039; &amp;lt;code&amp;gt;return 1;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action cleanup code:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Scrip to automatically add Cc: from incoming emails to tickets, if the following conditions apply:&lt;br /&gt;
# 1. Sender is sending from a domain that is already a watcher on the ticket (Requestor/Cc/AdminCc)&lt;br /&gt;
# 2. New Cc is from the same domain, or a subdomain.&lt;br /&gt;
#&lt;br /&gt;
# This Scrip is based on AddWatchersOnCorrespond https://rt-wiki.bestpractical.com/wiki/AddWatchersOnCorrespond&lt;br /&gt;
# But does not add just any cc:&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Mary Jane &amp;lt;mjane@emea.isp.com&amp;gt;&lt;br /&gt;
#&lt;br /&gt;
# - If fred@isp.com (or any isp.com or *.isp.com) address is already a watcher on the ticket,&lt;br /&gt;
#   then mjane@emea.isp.com will be automatically added as a Cc watcher.&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Random Helpdesk &amp;lt;helpdesk@bigcolo.net&amp;gt;&lt;br /&gt;
# - helpdesk@bigcolo.net will NOT be added as a Cc: to the ticket if &amp;quot;bigcolo.net&amp;quot; is not already&lt;br /&gt;
#   a watcher on the ticket.&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
my %People;&lt;br /&gt;
&lt;br /&gt;
# Get some info:&lt;br /&gt;
my $scrip = &#039;Scrip:AddWatchersOnCorrespondDomains&#039;;&lt;br /&gt;
my $Transaction = $self-&amp;gt;TransactionObj;&lt;br /&gt;
my $Queue = $self-&amp;gt;TicketObj-&amp;gt;QueueObj;&lt;br /&gt;
my $Ticket = $self-&amp;gt;TicketObj;&lt;br /&gt;
my $Id = $self-&amp;gt;TicketObj-&amp;gt;id;&lt;br /&gt;
&lt;br /&gt;
# Load a list of all domains of people on the ticket already:&lt;br /&gt;
my @TicketDomains = GetTicketDomains();&lt;br /&gt;
&lt;br /&gt;
# Extract a list of people associated with this transaction:&lt;br /&gt;
#  - including the transaction creator, and if it is an email, the sender and recipients of that email&lt;br /&gt;
&lt;br /&gt;
my $CreatorEmailAddr = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;EmailAddress;&lt;br /&gt;
my $CreatorRealName  = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;RealName;&lt;br /&gt;
$People{$CreatorEmailAddr}{RealName} = GetFullName($CreatorEmailAddr,$CreatorRealName);&lt;br /&gt;
&lt;br /&gt;
foreach my $h (qw(From To Cc)) {&lt;br /&gt;
    my $header = $Transaction-&amp;gt;Attachments-&amp;gt;First-&amp;gt;GetHeader($h);&lt;br /&gt;
    my @addr = Mail::Address-&amp;gt;parse($header);&lt;br /&gt;
    foreach my $addrobj (@addr) {&lt;br /&gt;
        my $addr = lc $RT::Nobody-&amp;gt;UserObj-&amp;gt;CanonicalizeEmailAddress($addrobj-&amp;gt;address);&lt;br /&gt;
        # Ignore the specific addresses for this queue:&lt;br /&gt;
        next if lc $Queue-&amp;gt;CorrespondAddress eq $addr;&lt;br /&gt;
        next if lc $Queue-&amp;gt;CommentAddress eq $addr;&lt;br /&gt;
        # Ignore any email address that looks like one for ANY of our queues:&lt;br /&gt;
        next if RT::EmailParser-&amp;gt;IsRTAddress($addr);&lt;br /&gt;
        my $fullname = GetFullName($addr,$addrobj-&amp;gt;phrase);&lt;br /&gt;
        $People{$addr}{RealName} = $fullname;&lt;br /&gt;
        $RT::Logger-&amp;gt;debug(&amp;quot;$scrip: Ticket #$Id correspondence contains header - $h: $addr $fullname&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Lookup the &#039;experts&#039; (staff) group to use below:&lt;br /&gt;
my $Experts = RT::Group-&amp;gt;new($self-&amp;gt;CurrentUser);&lt;br /&gt;
$Experts-&amp;gt;LoadUserDefinedGroup(&#039;Staff&#039;);&lt;br /&gt;
&lt;br /&gt;
# Now check if each user is already watching the ticket or queue:&lt;br /&gt;
&lt;br /&gt;
foreach my $addr (sort keys %People) {&lt;br /&gt;
&lt;br /&gt;
    next if ($addr =~ /^(postmaster|root|Mailer-Daemon)\@/);&lt;br /&gt;
&lt;br /&gt;
    my $User = RT::User-&amp;gt;new( $RT::SystemUser );&lt;br /&gt;
    $User-&amp;gt;LoadOrCreateByEmail(&lt;br /&gt;
        RealName     =&amp;gt; $People{$addr}{RealName},&lt;br /&gt;
        EmailAddress =&amp;gt; $addr,&lt;br /&gt;
        Comments     =&amp;gt; &amp;quot;Autocreated by $scrip&amp;quot;,&lt;br /&gt;
    );&lt;br /&gt;
    &lt;br /&gt;
    my $Name = $User-&amp;gt;Name;&lt;br /&gt;
    my $Principal = $User-&amp;gt;PrincipalId;&lt;br /&gt;
    &lt;br /&gt;
    if ( not ($Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Requestor&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsOwner($User) )) {&lt;br /&gt;
     &lt;br /&gt;
     # check if the new cc: person&#039;s domain matches a domain already on the ticket.&lt;br /&gt;
     # If so, add them as a new watcher:&lt;br /&gt;
     if (is_address_authdomain($addr,@TicketDomains)) {&lt;br /&gt;
        # If the user is a member of the experts group, then add them as an AdminCc, otherwise as a Cc:&lt;br /&gt;
        # my $type = &#039;Cc&#039;;&lt;br /&gt;
        my $type = &#039;Requestor&#039;;&lt;br /&gt;
        $type = &#039;AdminCc&#039; if $Experts-&amp;gt;HasMember($User-&amp;gt;PrincipalObj);&lt;br /&gt;
        # Add the new watcher now and check for errors:&lt;br /&gt;
        my ($ret, $msg) = $Ticket-&amp;gt;AddWatcher(Type  =&amp;gt; $type, PrincipalId =&amp;gt; $Principal);&lt;br /&gt;
        if ($ret) {&lt;br /&gt;
            $RT::Logger-&amp;gt;info(&amp;quot;$scrip: New $type watcher added to ticket #$Id: $addr (#$Principal)&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $RT::Logger-&amp;gt;error(&amp;quot;$scrip: Failed to add new $type watcher to ticket #$Id: $addr (#$Principal) - $msg&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetFullName {&lt;br /&gt;
&lt;br /&gt;
    # Get a nicely formatted name for RT RealName Field:&lt;br /&gt;
&lt;br /&gt;
    # If fullname is blank, make something up from the&lt;br /&gt;
    # local_part of the email address: &amp;quot;fred.bloggs@...&amp;quot; -&amp;gt; &amp;quot;Fred Bloggs&amp;quot;.&lt;br /&gt;
    # Do not allow fullname to contain &amp;quot;@&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
    my ($addr,$fullname) = @_;&lt;br /&gt;
    my ($local_part,$domain) = split(&#039;@&#039;, $addr);&lt;br /&gt;
&lt;br /&gt;
     if (($fullname eq &#039;&#039;) || ($fullname =~ /\@/)) {&lt;br /&gt;
      $fullname = $local_part;&lt;br /&gt;
      $fullname =~ s/[\._-]/ /g;&lt;br /&gt;
      $fullname =~ s/(\w+)/\u$1/g;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     $fullname =~ s/^[\&amp;quot;\&#039;]|[\&amp;quot;\&#039;]$//g; # strip leading/trailing &amp;quot; or &#039;&lt;br /&gt;
     $fullname =~ s/^\s+|\s+$//g; # strip leading/trailing spaces&lt;br /&gt;
&lt;br /&gt;
    return $fullname;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub is_address_authdomain {&lt;br /&gt;
&lt;br /&gt;
        # Is address in an authorised domain?&lt;br /&gt;
        # Also allow subdomain of existing domain&lt;br /&gt;
&lt;br /&gt;
        my $addr = shift;&lt;br /&gt;
        my @domains = @_;&lt;br /&gt;
        $addr = lc($addr);&lt;br /&gt;
&lt;br /&gt;
        my ($undef,$domain) = split(&#039;@&#039;,$addr);&lt;br /&gt;
        my $is_auth = 0;&lt;br /&gt;
&lt;br /&gt;
        foreach my $authdomain (@domains) {&lt;br /&gt;
&lt;br /&gt;
           # if ($domain eq $authdomain)      { $is_auth=1; last; };&lt;br /&gt;
           if ($domain =~ /(^|(\.?))$authdomain$/) { $is_auth=1; last; };&lt;br /&gt;
           if ($authdomain =~ /(^|(\.?))$domain$/) { $is_auth=1; last; };&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return($is_auth);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetTicketDomains {&lt;br /&gt;
&lt;br /&gt;
    # my $self = shift;&lt;br /&gt;
	&lt;br /&gt;
    # Get list of existing email addresses from the ticket, and push all the domains.&lt;br /&gt;
    # We&#039;ll check this later. Any cc: by an existing requestor will be allowed from &lt;br /&gt;
    # the same domain or a subdomain.&lt;br /&gt;
&lt;br /&gt;
    # For each Role, get RoleAddresses: &amp;quot;foo@bar.com, foo@baz.com&amp;quot; &lt;br /&gt;
    # Return a deduped domain list.&lt;br /&gt;
&lt;br /&gt;
    my %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
     foreach my $role (qw(Requestor Cc AdminCc)) {&lt;br /&gt;
&lt;br /&gt;
        my $roleaddresses = lc($self-&amp;gt;TicketObj-&amp;gt;RoleAddresses($role));&lt;br /&gt;
               foreach my $a (split(&#039;, &#039;, $roleaddresses)) {&lt;br /&gt;
                 my (undef,$domain) = split(&#039;@&#039;,$a);&lt;br /&gt;
                 next if ($allticketdomains{$domain});&lt;br /&gt;
                 $allticketdomains{$domain} = $domain;&lt;br /&gt;
                }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      my @ticketdomains = sort keys %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
      return (@ticketdomains);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
return 1;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 # vim:ft=perl:&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27126</id>
		<title>AddWatchersOnCorrespondDomains</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27126"/>
		<updated>2022-12-08T14:32:16Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* Bugs/Ideas/TODO */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= AddWatchersOnCorrespondDomains =&lt;br /&gt;
&lt;br /&gt;
This RT Scrip is a modified version of [[AddWatchersOnCorrespond]] - AddWatchersOnCorrespond simply adds &#039;&#039;&#039;all&#039;&#039;&#039; recipients as watchers.&lt;br /&gt;
&lt;br /&gt;
This Scrip is the same, except it is more restrictive with the requestors added:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;AddWatchersOnCorrespondDomains&amp;lt;/code&amp;gt; will add the person making the correspondence as a [[Watcher]] to the ticket if they are not already a [[Watcher]].&lt;br /&gt;
&lt;br /&gt;
If the transaction originated from an email message, the scrip will scan the email headers and add other recipients to the ticket as [[Watcher]]s, only if:&lt;br /&gt;
&lt;br /&gt;
* They are not already a [[Watcher]], &#039;&#039;&#039;and&#039;&#039;&#039;:&lt;br /&gt;
* Their email address has the &#039;&#039;&#039;same domain&#039;&#039;&#039; (or from a subdomain) as another watcher &#039;&#039;&#039;already&#039;&#039;&#039; on the ticket.&lt;br /&gt;
&lt;br /&gt;
* e.g. New To: and Cc: recipients added by the customer will &#039;&#039;&#039;only&#039;&#039;&#039; be added as watchers if they are from the same domain as a requestor already on the ticket.&lt;br /&gt;
* (Third parties not already on the ticket will not be added.)&lt;br /&gt;
&lt;br /&gt;
* We prefer to add as &#039;&#039;&#039;Requestors&#039;&#039;&#039; rather than Cc to simplify ticket updates. (All &amp;quot;Replies to Requestors&amp;quot; always go to everyone on the ticket.) If you do not want this behaviour, change the line: &amp;lt;code&amp;gt;my $type = &#039;Requestor&#039;;&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;my $type = &#039;Cc&#039;;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* This accommodates the majority of our use cases:&lt;br /&gt;
* 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:&lt;br /&gt;
* After this executes, We also use [https://metacpan.org/pod/RT::Extension::NonWatcherRecipients RT-Extension-NonWatcherRecipients] to add a warning into the Admins email notification of any recipients on the email that are not on the ticket.&lt;br /&gt;
&lt;br /&gt;
* In our RT setup, we have a group named &#039;&#039;&#039;Staff&#039;&#039;&#039; 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 &#039;&#039;&#039;Staff&#039;&#039;&#039; 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.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;NOTE:&#039;&#039;&#039; If you &#039;&#039;&#039;remove&#039;&#039;&#039; 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 -&amp;gt; Modify who receives mail for ticket). They will not receive further email from the ticket. (This is known as a &amp;quot;Squelched&amp;quot; watcher in RT.)&lt;br /&gt;
&lt;br /&gt;
==== Changelog ====&lt;br /&gt;
&lt;br /&gt;
* 2022-12-07: (RobL) Created. Update to &#039;&#039;&#039;always populate&#039;&#039;&#039; Real Name field. See [https://forum.bestpractical.com/t/rt-autocreated-watcher-from-header-causing-bounces/37708/4 this forum post].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Bugs/Ideas/TODO ====&lt;br /&gt;
&lt;br /&gt;
# Add &amp;lt;code&amp;gt;never_auth&amp;lt;/code&amp;gt; list: Exclude certain domains where it is not safe to assume &amp;quot;same domain = same organisation&amp;quot; e.g. gmail.com, ymail.com, 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.)&lt;br /&gt;
# Allow for &#039;&#039;&#039;equivalent domains&#039;&#039;&#039;: Ability to specify for example, &amp;lt;code&amp;gt;example.com&amp;lt;/code&amp;gt; is the same as &amp;lt;/code&amp;gt;example.co.uk&amp;lt;/code&amp;gt;&lt;br /&gt;
# 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., &amp;lt;code&amp;gt;fred@example.net&amp;lt;/code&amp;gt; is already a Requestor, and Cc:&#039;s &amp;lt;code&amp;gt;random@gmail.com&amp;lt;/code&amp;gt;. If &amp;lt;code&amp;gt;random@gmail.com&amp;lt;/code&amp;gt; is already a user in RT and is a member of the same customer group as &amp;lt;code&amp;gt;example.net&amp;lt;/code&amp;gt; then add them as a watcher.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Description:&#039;&#039;&#039; [[AddWatchersOnCorrespondDomains]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Condition:&#039;&#039;&#039; On Correspond&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; User Defined&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Template:&#039;&#039;&#039; Global template: Blank&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Stage:&#039;&#039;&#039; [[Normal]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom condition:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action preparation code:&#039;&#039;&#039; &amp;lt;code&amp;gt;return 1;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action cleanup code:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Scrip to automatically add Cc: from incoming emails to tickets, if the following conditions apply:&lt;br /&gt;
# 1. Sender is sending from a domain that is already a watcher on the ticket (Requestor/Cc/AdminCc)&lt;br /&gt;
# 2. New Cc is from the same domain, or a subdomain.&lt;br /&gt;
#&lt;br /&gt;
# This Scrip is based on AddWatchersOnCorrespond https://rt-wiki.bestpractical.com/wiki/AddWatchersOnCorrespond&lt;br /&gt;
# But does not add just any cc:&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Mary Jane &amp;lt;mjane@emea.isp.com&amp;gt;&lt;br /&gt;
#&lt;br /&gt;
# - If fred@isp.com (or any isp.com or *.isp.com) address is already a watcher on the ticket,&lt;br /&gt;
#   then mjane@emea.isp.com will be automatically added as a Cc watcher.&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Random Helpdesk &amp;lt;helpdesk@bigcolo.net&amp;gt;&lt;br /&gt;
# - helpdesk@bigcolo.net will NOT be added as a Cc: to the ticket if &amp;quot;bigcolo.net&amp;quot; is not already&lt;br /&gt;
#   a watcher on the ticket.&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
my %People;&lt;br /&gt;
&lt;br /&gt;
# Get some info:&lt;br /&gt;
my $scrip = &#039;Scrip:AddWatchersOnCorrespondDomains&#039;;&lt;br /&gt;
my $Transaction = $self-&amp;gt;TransactionObj;&lt;br /&gt;
my $Queue = $self-&amp;gt;TicketObj-&amp;gt;QueueObj;&lt;br /&gt;
my $Ticket = $self-&amp;gt;TicketObj;&lt;br /&gt;
my $Id = $self-&amp;gt;TicketObj-&amp;gt;id;&lt;br /&gt;
&lt;br /&gt;
# Load a list of all domains of people on the ticket already:&lt;br /&gt;
my @TicketDomains = GetTicketDomains();&lt;br /&gt;
&lt;br /&gt;
# Extract a list of people associated with this transaction:&lt;br /&gt;
#  - including the transaction creator, and if it is an email, the sender and recipients of that email&lt;br /&gt;
&lt;br /&gt;
my $CreatorEmailAddr = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;EmailAddress;&lt;br /&gt;
my $CreatorRealName  = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;RealName;&lt;br /&gt;
$People{$CreatorEmailAddr}{RealName} = GetFullName($CreatorEmailAddr,$CreatorRealName);&lt;br /&gt;
&lt;br /&gt;
foreach my $h (qw(From To Cc)) {&lt;br /&gt;
    my $header = $Transaction-&amp;gt;Attachments-&amp;gt;First-&amp;gt;GetHeader($h);&lt;br /&gt;
    my @addr = Mail::Address-&amp;gt;parse($header);&lt;br /&gt;
    foreach my $addrobj (@addr) {&lt;br /&gt;
        my $addr = lc $RT::Nobody-&amp;gt;UserObj-&amp;gt;CanonicalizeEmailAddress($addrobj-&amp;gt;address);&lt;br /&gt;
        # Ignore the specific addresses for this queue:&lt;br /&gt;
        next if lc $Queue-&amp;gt;CorrespondAddress eq $addr;&lt;br /&gt;
        next if lc $Queue-&amp;gt;CommentAddress eq $addr;&lt;br /&gt;
        # Ignore any email address that looks like one for ANY of our queues:&lt;br /&gt;
        next if RT::EmailParser-&amp;gt;IsRTAddress($addr);&lt;br /&gt;
        my $fullname = GetFullName($addr,$addrobj-&amp;gt;phrase);&lt;br /&gt;
        $People{$addr}{RealName} = $fullname;&lt;br /&gt;
        $RT::Logger-&amp;gt;debug(&amp;quot;$scrip: Ticket #$Id correspondence contains header - $h: $addr $fullname&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Lookup the &#039;experts&#039; (staff) group to use below:&lt;br /&gt;
my $Experts = RT::Group-&amp;gt;new($self-&amp;gt;CurrentUser);&lt;br /&gt;
$Experts-&amp;gt;LoadUserDefinedGroup(&#039;Staff&#039;);&lt;br /&gt;
&lt;br /&gt;
# Now check if each user is already watching the ticket or queue:&lt;br /&gt;
&lt;br /&gt;
foreach my $addr (sort keys %People) {&lt;br /&gt;
&lt;br /&gt;
    next if ($addr =~ /^(postmaster|root|Mailer-Daemon)\@/);&lt;br /&gt;
&lt;br /&gt;
    my $User = RT::User-&amp;gt;new( $RT::SystemUser );&lt;br /&gt;
    $User-&amp;gt;LoadOrCreateByEmail(&lt;br /&gt;
        RealName     =&amp;gt; $People{$addr}{RealName},&lt;br /&gt;
        EmailAddress =&amp;gt; $addr,&lt;br /&gt;
        Comments     =&amp;gt; &amp;quot;Autocreated by $scrip&amp;quot;,&lt;br /&gt;
    );&lt;br /&gt;
    &lt;br /&gt;
    my $Name = $User-&amp;gt;Name;&lt;br /&gt;
    my $Principal = $User-&amp;gt;PrincipalId;&lt;br /&gt;
    &lt;br /&gt;
    if ( not ($Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Requestor&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsOwner($User) )) {&lt;br /&gt;
     &lt;br /&gt;
     # check if the new cc: person&#039;s domain matches a domain already on the ticket.&lt;br /&gt;
     # If so, add them as a new watcher:&lt;br /&gt;
     if (is_address_authdomain($addr,@TicketDomains)) {&lt;br /&gt;
        # If the user is a member of the experts group, then add them as an AdminCc, otherwise as a Cc:&lt;br /&gt;
        # my $type = &#039;Cc&#039;;&lt;br /&gt;
        my $type = &#039;Requestor&#039;;&lt;br /&gt;
        $type = &#039;AdminCc&#039; if $Experts-&amp;gt;HasMember($User-&amp;gt;PrincipalObj);&lt;br /&gt;
        # Add the new watcher now and check for errors:&lt;br /&gt;
        my ($ret, $msg) = $Ticket-&amp;gt;AddWatcher(Type  =&amp;gt; $type, PrincipalId =&amp;gt; $Principal);&lt;br /&gt;
        if ($ret) {&lt;br /&gt;
            $RT::Logger-&amp;gt;info(&amp;quot;$scrip: New $type watcher added to ticket #$Id: $addr (#$Principal)&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $RT::Logger-&amp;gt;error(&amp;quot;$scrip: Failed to add new $type watcher to ticket #$Id: $addr (#$Principal) - $msg&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetFullName {&lt;br /&gt;
&lt;br /&gt;
    # Get a nicely formatted name for RT RealName Field:&lt;br /&gt;
&lt;br /&gt;
    # If fullname is blank, make something up from the&lt;br /&gt;
    # local_part of the email address: &amp;quot;fred.bloggs@...&amp;quot; -&amp;gt; &amp;quot;Fred Bloggs&amp;quot;.&lt;br /&gt;
    # Do not allow fullname to contain &amp;quot;@&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
    my ($addr,$fullname) = @_;&lt;br /&gt;
    my ($local_part,$domain) = split(&#039;@&#039;, $addr);&lt;br /&gt;
&lt;br /&gt;
     if (($fullname eq &#039;&#039;) || ($fullname =~ /\@/)) {&lt;br /&gt;
      $fullname = $local_part;&lt;br /&gt;
      $fullname =~ s/[\._-]/ /g;&lt;br /&gt;
      $fullname =~ s/(\w+)/\u$1/g;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     $fullname =~ s/^[\&amp;quot;\&#039;]|[\&amp;quot;\&#039;]$//g; # strip leading/trailing &amp;quot; or &#039;&lt;br /&gt;
     $fullname =~ s/^\s+|\s+$//g; # strip leading/trailing spaces&lt;br /&gt;
&lt;br /&gt;
    return $fullname;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub is_address_authdomain {&lt;br /&gt;
&lt;br /&gt;
        # Is address in an authorised domain?&lt;br /&gt;
        # Also allow subdomain of existing domain&lt;br /&gt;
&lt;br /&gt;
        my $addr = shift;&lt;br /&gt;
        my @domains = @_;&lt;br /&gt;
        $addr = lc($addr);&lt;br /&gt;
&lt;br /&gt;
        my ($undef,$domain) = split(&#039;@&#039;,$addr);&lt;br /&gt;
        my $is_auth = 0;&lt;br /&gt;
&lt;br /&gt;
        foreach my $authdomain (@domains) {&lt;br /&gt;
&lt;br /&gt;
           # if ($domain eq $authdomain)      { $is_auth=1; last; };&lt;br /&gt;
           if ($domain =~ /(^|(\.?))$authdomain$/) { $is_auth=1; last; };&lt;br /&gt;
           if ($authdomain =~ /(^|(\.?))$domain$/) { $is_auth=1; last; };&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return($is_auth);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetTicketDomains {&lt;br /&gt;
&lt;br /&gt;
    # my $self = shift;&lt;br /&gt;
	&lt;br /&gt;
    # Get list of existing email addresses from the ticket, and push all the domains.&lt;br /&gt;
    # We&#039;ll check this later. Any cc: by an existing requestor will be allowed from &lt;br /&gt;
    # the same domain or a subdomain.&lt;br /&gt;
&lt;br /&gt;
    # For each Role, get RoleAddresses: &amp;quot;foo@bar.com, foo@baz.com&amp;quot; &lt;br /&gt;
    # Return a deduped domain list.&lt;br /&gt;
&lt;br /&gt;
    my %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
     foreach my $role (qw(Requestor Cc AdminCc)) {&lt;br /&gt;
&lt;br /&gt;
        my $roleaddresses = lc($self-&amp;gt;TicketObj-&amp;gt;RoleAddresses($role));&lt;br /&gt;
               foreach my $a (split(&#039;, &#039;, $roleaddresses)) {&lt;br /&gt;
                 my (undef,$domain) = split(&#039;@&#039;,$a);&lt;br /&gt;
                 next if ($allticketdomains{$domain});&lt;br /&gt;
                 $allticketdomains{$domain} = $domain;&lt;br /&gt;
                }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      my @ticketdomains = sort keys %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
      return (@ticketdomains);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
return 1;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 # vim:ft=perl:&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27125</id>
		<title>AddWatchersOnCorrespondDomains</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27125"/>
		<updated>2022-12-08T14:28:41Z</updated>

		<summary type="html">&lt;p&gt;Robl: Bugs/Ideas/TODO&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= AddWatchersOnCorrespondDomains =&lt;br /&gt;
&lt;br /&gt;
This RT Scrip is a modified version of [[AddWatchersOnCorrespond]] - AddWatchersOnCorrespond simply adds &#039;&#039;&#039;all&#039;&#039;&#039; recipients as watchers.&lt;br /&gt;
&lt;br /&gt;
This Scrip is the same, except it is more restrictive with the requestors added:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;AddWatchersOnCorrespondDomains&amp;lt;/code&amp;gt; will add the person making the correspondence as a [[Watcher]] to the ticket if they are not already a [[Watcher]].&lt;br /&gt;
&lt;br /&gt;
If the transaction originated from an email message, the scrip will scan the email headers and add other recipients to the ticket as [[Watcher]]s, only if:&lt;br /&gt;
&lt;br /&gt;
* They are not already a [[Watcher]], &#039;&#039;&#039;and&#039;&#039;&#039;:&lt;br /&gt;
* Their email address has the &#039;&#039;&#039;same domain&#039;&#039;&#039; (or from a subdomain) as another watcher &#039;&#039;&#039;already&#039;&#039;&#039; on the ticket.&lt;br /&gt;
&lt;br /&gt;
* e.g. New To: and Cc: recipients added by the customer will &#039;&#039;&#039;only&#039;&#039;&#039; be added as watchers if they are from the same domain as a requestor already on the ticket.&lt;br /&gt;
* (Third parties not already on the ticket will not be added.)&lt;br /&gt;
&lt;br /&gt;
* We prefer to add as &#039;&#039;&#039;Requestors&#039;&#039;&#039; rather than Cc to simplify ticket updates. (All &amp;quot;Replies to Requestors&amp;quot; always go to everyone on the ticket.) If you do not want this behaviour, change the line: &amp;lt;code&amp;gt;my $type = &#039;Requestor&#039;;&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;my $type = &#039;Cc&#039;;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* This accommodates the majority of our use cases:&lt;br /&gt;
* 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:&lt;br /&gt;
* After this executes, We also use [https://metacpan.org/pod/RT::Extension::NonWatcherRecipients RT-Extension-NonWatcherRecipients] to add a warning into the Admins email notification of any recipients on the email that are not on the ticket.&lt;br /&gt;
&lt;br /&gt;
* In our RT setup, we have a group named &#039;&#039;&#039;Staff&#039;&#039;&#039; 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 &#039;&#039;&#039;Staff&#039;&#039;&#039; 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.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;NOTE:&#039;&#039;&#039; If you &#039;&#039;&#039;remove&#039;&#039;&#039; 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 -&amp;gt; Modify who receives mail for ticket). They will not receive further email from the ticket. (This is known as a &amp;quot;Squelched&amp;quot; watcher in RT.)&lt;br /&gt;
&lt;br /&gt;
==== Changelog ====&lt;br /&gt;
&lt;br /&gt;
* 2022-12-07: (RobL) Created. Update to &#039;&#039;&#039;always populate&#039;&#039;&#039; Real Name field. See [https://forum.bestpractical.com/t/rt-autocreated-watcher-from-header-causing-bounces/37708/4 this forum post].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Bugs/Ideas/TODO ====&lt;br /&gt;
&lt;br /&gt;
# Add &amp;lt;code&amp;gt;never_auth&amp;lt;/code&amp;gt; list: Exclude certain domains where it is not safe to assume &amp;quot;same domain = same organisation&amp;quot; e.g. gmail.com, ymail.com, hotmail etc, and where multiple organisations use the same domain but are not related. (e.g., due to mergers/acquisitions, two customers use the same domain and brand, but are not related.)&lt;br /&gt;
# Allow for &#039;&#039;&#039;equivalent domains&#039;&#039;&#039;: Ability to specify for example, &amp;lt;code&amp;gt;example.com&amp;lt;/code&amp;gt; is the same as &amp;lt;/code&amp;gt;example.co.uk&amp;lt;/code&amp;gt;&lt;br /&gt;
# 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., &amp;lt;code&amp;gt;fred@example.net&amp;lt;/code&amp;gt; is already a Requestor, and Cc:&#039;s &amp;lt;code&amp;gt;random@gmail.com&amp;lt;/code&amp;gt;. If &amp;lt;code&amp;gt;random@gmail.com&amp;lt;/code&amp;gt; is already a user in RT and is a member of the same customer group as &amp;lt;code&amp;gt;example.net&amp;lt;/code&amp;gt; then add them as a watcher.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Description:&#039;&#039;&#039; [[AddWatchersOnCorrespondDomains]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Condition:&#039;&#039;&#039; On Correspond&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; User Defined&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Template:&#039;&#039;&#039; Global template: Blank&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Stage:&#039;&#039;&#039; [[Normal]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom condition:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action preparation code:&#039;&#039;&#039; &amp;lt;code&amp;gt;return 1;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action cleanup code:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Scrip to automatically add Cc: from incoming emails to tickets, if the following conditions apply:&lt;br /&gt;
# 1. Sender is sending from a domain that is already a watcher on the ticket (Requestor/Cc/AdminCc)&lt;br /&gt;
# 2. New Cc is from the same domain, or a subdomain.&lt;br /&gt;
#&lt;br /&gt;
# This Scrip is based on AddWatchersOnCorrespond https://rt-wiki.bestpractical.com/wiki/AddWatchersOnCorrespond&lt;br /&gt;
# But does not add just any cc:&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Mary Jane &amp;lt;mjane@emea.isp.com&amp;gt;&lt;br /&gt;
#&lt;br /&gt;
# - If fred@isp.com (or any isp.com or *.isp.com) address is already a watcher on the ticket,&lt;br /&gt;
#   then mjane@emea.isp.com will be automatically added as a Cc watcher.&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Random Helpdesk &amp;lt;helpdesk@bigcolo.net&amp;gt;&lt;br /&gt;
# - helpdesk@bigcolo.net will NOT be added as a Cc: to the ticket if &amp;quot;bigcolo.net&amp;quot; is not already&lt;br /&gt;
#   a watcher on the ticket.&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
my %People;&lt;br /&gt;
&lt;br /&gt;
# Get some info:&lt;br /&gt;
my $scrip = &#039;Scrip:AddWatchersOnCorrespondDomains&#039;;&lt;br /&gt;
my $Transaction = $self-&amp;gt;TransactionObj;&lt;br /&gt;
my $Queue = $self-&amp;gt;TicketObj-&amp;gt;QueueObj;&lt;br /&gt;
my $Ticket = $self-&amp;gt;TicketObj;&lt;br /&gt;
my $Id = $self-&amp;gt;TicketObj-&amp;gt;id;&lt;br /&gt;
&lt;br /&gt;
# Load a list of all domains of people on the ticket already:&lt;br /&gt;
my @TicketDomains = GetTicketDomains();&lt;br /&gt;
&lt;br /&gt;
# Extract a list of people associated with this transaction:&lt;br /&gt;
#  - including the transaction creator, and if it is an email, the sender and recipients of that email&lt;br /&gt;
&lt;br /&gt;
my $CreatorEmailAddr = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;EmailAddress;&lt;br /&gt;
my $CreatorRealName  = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;RealName;&lt;br /&gt;
$People{$CreatorEmailAddr}{RealName} = GetFullName($CreatorEmailAddr,$CreatorRealName);&lt;br /&gt;
&lt;br /&gt;
foreach my $h (qw(From To Cc)) {&lt;br /&gt;
    my $header = $Transaction-&amp;gt;Attachments-&amp;gt;First-&amp;gt;GetHeader($h);&lt;br /&gt;
    my @addr = Mail::Address-&amp;gt;parse($header);&lt;br /&gt;
    foreach my $addrobj (@addr) {&lt;br /&gt;
        my $addr = lc $RT::Nobody-&amp;gt;UserObj-&amp;gt;CanonicalizeEmailAddress($addrobj-&amp;gt;address);&lt;br /&gt;
        # Ignore the specific addresses for this queue:&lt;br /&gt;
        next if lc $Queue-&amp;gt;CorrespondAddress eq $addr;&lt;br /&gt;
        next if lc $Queue-&amp;gt;CommentAddress eq $addr;&lt;br /&gt;
        # Ignore any email address that looks like one for ANY of our queues:&lt;br /&gt;
        next if RT::EmailParser-&amp;gt;IsRTAddress($addr);&lt;br /&gt;
        my $fullname = GetFullName($addr,$addrobj-&amp;gt;phrase);&lt;br /&gt;
        $People{$addr}{RealName} = $fullname;&lt;br /&gt;
        $RT::Logger-&amp;gt;debug(&amp;quot;$scrip: Ticket #$Id correspondence contains header - $h: $addr $fullname&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Lookup the &#039;experts&#039; (staff) group to use below:&lt;br /&gt;
my $Experts = RT::Group-&amp;gt;new($self-&amp;gt;CurrentUser);&lt;br /&gt;
$Experts-&amp;gt;LoadUserDefinedGroup(&#039;Staff&#039;);&lt;br /&gt;
&lt;br /&gt;
# Now check if each user is already watching the ticket or queue:&lt;br /&gt;
&lt;br /&gt;
foreach my $addr (sort keys %People) {&lt;br /&gt;
&lt;br /&gt;
    next if ($addr =~ /^(postmaster|root|Mailer-Daemon)\@/);&lt;br /&gt;
&lt;br /&gt;
    my $User = RT::User-&amp;gt;new( $RT::SystemUser );&lt;br /&gt;
    $User-&amp;gt;LoadOrCreateByEmail(&lt;br /&gt;
        RealName     =&amp;gt; $People{$addr}{RealName},&lt;br /&gt;
        EmailAddress =&amp;gt; $addr,&lt;br /&gt;
        Comments     =&amp;gt; &amp;quot;Autocreated by $scrip&amp;quot;,&lt;br /&gt;
    );&lt;br /&gt;
    &lt;br /&gt;
    my $Name = $User-&amp;gt;Name;&lt;br /&gt;
    my $Principal = $User-&amp;gt;PrincipalId;&lt;br /&gt;
    &lt;br /&gt;
    if ( not ($Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Requestor&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsOwner($User) )) {&lt;br /&gt;
     &lt;br /&gt;
     # check if the new cc: person&#039;s domain matches a domain already on the ticket.&lt;br /&gt;
     # If so, add them as a new watcher:&lt;br /&gt;
     if (is_address_authdomain($addr,@TicketDomains)) {&lt;br /&gt;
        # If the user is a member of the experts group, then add them as an AdminCc, otherwise as a Cc:&lt;br /&gt;
        # my $type = &#039;Cc&#039;;&lt;br /&gt;
        my $type = &#039;Requestor&#039;;&lt;br /&gt;
        $type = &#039;AdminCc&#039; if $Experts-&amp;gt;HasMember($User-&amp;gt;PrincipalObj);&lt;br /&gt;
        # Add the new watcher now and check for errors:&lt;br /&gt;
        my ($ret, $msg) = $Ticket-&amp;gt;AddWatcher(Type  =&amp;gt; $type, PrincipalId =&amp;gt; $Principal);&lt;br /&gt;
        if ($ret) {&lt;br /&gt;
            $RT::Logger-&amp;gt;info(&amp;quot;$scrip: New $type watcher added to ticket #$Id: $addr (#$Principal)&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $RT::Logger-&amp;gt;error(&amp;quot;$scrip: Failed to add new $type watcher to ticket #$Id: $addr (#$Principal) - $msg&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetFullName {&lt;br /&gt;
&lt;br /&gt;
    # Get a nicely formatted name for RT RealName Field:&lt;br /&gt;
&lt;br /&gt;
    # If fullname is blank, make something up from the&lt;br /&gt;
    # local_part of the email address: &amp;quot;fred.bloggs@...&amp;quot; -&amp;gt; &amp;quot;Fred Bloggs&amp;quot;.&lt;br /&gt;
    # Do not allow fullname to contain &amp;quot;@&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
    my ($addr,$fullname) = @_;&lt;br /&gt;
    my ($local_part,$domain) = split(&#039;@&#039;, $addr);&lt;br /&gt;
&lt;br /&gt;
     if (($fullname eq &#039;&#039;) || ($fullname =~ /\@/)) {&lt;br /&gt;
      $fullname = $local_part;&lt;br /&gt;
      $fullname =~ s/[\._-]/ /g;&lt;br /&gt;
      $fullname =~ s/(\w+)/\u$1/g;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     $fullname =~ s/^[\&amp;quot;\&#039;]|[\&amp;quot;\&#039;]$//g; # strip leading/trailing &amp;quot; or &#039;&lt;br /&gt;
     $fullname =~ s/^\s+|\s+$//g; # strip leading/trailing spaces&lt;br /&gt;
&lt;br /&gt;
    return $fullname;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub is_address_authdomain {&lt;br /&gt;
&lt;br /&gt;
        # Is address in an authorised domain?&lt;br /&gt;
        # Also allow subdomain of existing domain&lt;br /&gt;
&lt;br /&gt;
        my $addr = shift;&lt;br /&gt;
        my @domains = @_;&lt;br /&gt;
        $addr = lc($addr);&lt;br /&gt;
&lt;br /&gt;
        my ($undef,$domain) = split(&#039;@&#039;,$addr);&lt;br /&gt;
        my $is_auth = 0;&lt;br /&gt;
&lt;br /&gt;
        foreach my $authdomain (@domains) {&lt;br /&gt;
&lt;br /&gt;
           # if ($domain eq $authdomain)      { $is_auth=1; last; };&lt;br /&gt;
           if ($domain =~ /(^|(\.?))$authdomain$/) { $is_auth=1; last; };&lt;br /&gt;
           if ($authdomain =~ /(^|(\.?))$domain$/) { $is_auth=1; last; };&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return($is_auth);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetTicketDomains {&lt;br /&gt;
&lt;br /&gt;
    # my $self = shift;&lt;br /&gt;
	&lt;br /&gt;
    # Get list of existing email addresses from the ticket, and push all the domains.&lt;br /&gt;
    # We&#039;ll check this later. Any cc: by an existing requestor will be allowed from &lt;br /&gt;
    # the same domain or a subdomain.&lt;br /&gt;
&lt;br /&gt;
    # For each Role, get RoleAddresses: &amp;quot;foo@bar.com, foo@baz.com&amp;quot; &lt;br /&gt;
    # Return a deduped domain list.&lt;br /&gt;
&lt;br /&gt;
    my %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
     foreach my $role (qw(Requestor Cc AdminCc)) {&lt;br /&gt;
&lt;br /&gt;
        my $roleaddresses = lc($self-&amp;gt;TicketObj-&amp;gt;RoleAddresses($role));&lt;br /&gt;
               foreach my $a (split(&#039;, &#039;, $roleaddresses)) {&lt;br /&gt;
                 my (undef,$domain) = split(&#039;@&#039;,$a);&lt;br /&gt;
                 next if ($allticketdomains{$domain});&lt;br /&gt;
                 $allticketdomains{$domain} = $domain;&lt;br /&gt;
                }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      my @ticketdomains = sort keys %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
      return (@ticketdomains);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
return 1;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 # vim:ft=perl:&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27124</id>
		<title>AddWatchersOnCorrespondDomains</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27124"/>
		<updated>2022-12-07T19:52:10Z</updated>

		<summary type="html">&lt;p&gt;Robl: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= AddWatchersOnCorrespondDomains =&lt;br /&gt;
&lt;br /&gt;
This RT Scrip is a modified version of [[AddWatchersOnCorrespond]] - AddWatchersOnCorrespond simply adds &#039;&#039;&#039;all&#039;&#039;&#039; recipients as watchers.&lt;br /&gt;
&lt;br /&gt;
This Scrip is the same, except it is more restrictive with the requestors added:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;AddWatchersOnCorrespondDomains&amp;lt;/code&amp;gt; will add the person making the correspondence as a [[Watcher]] to the ticket if they are not already a [[Watcher]].&lt;br /&gt;
&lt;br /&gt;
If the transaction originated from an email message, the scrip will scan the email headers and add other recipients to the ticket as [[Watcher]]s, only if:&lt;br /&gt;
&lt;br /&gt;
* They are not already a [[Watcher]], &#039;&#039;&#039;and&#039;&#039;&#039;:&lt;br /&gt;
* Their email address has the &#039;&#039;&#039;same domain&#039;&#039;&#039; (or from a subdomain) as another watcher &#039;&#039;&#039;already&#039;&#039;&#039; on the ticket.&lt;br /&gt;
&lt;br /&gt;
* e.g. New To: and Cc: recipients added by the customer will &#039;&#039;&#039;only&#039;&#039;&#039; be added as watchers if they are from the same domain as a requestor already on the ticket.&lt;br /&gt;
* (Third parties not already on the ticket will not be added.)&lt;br /&gt;
&lt;br /&gt;
* We prefer to add as &#039;&#039;&#039;Requestors&#039;&#039;&#039; rather than Cc to simplify ticket updates. (All &amp;quot;Replies to Requestors&amp;quot; always go to everyone on the ticket.) If you do not want this behaviour, change the line: &amp;lt;code&amp;gt;my $type = &#039;Requestor&#039;;&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;my $type = &#039;Cc&#039;;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* This accommodates the majority of our use cases:&lt;br /&gt;
* 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:&lt;br /&gt;
* After this executes, We also use [https://metacpan.org/pod/RT::Extension::NonWatcherRecipients RT-Extension-NonWatcherRecipients] to add a warning into the Admins email notification of any recipients on the email that are not on the ticket.&lt;br /&gt;
&lt;br /&gt;
* In our RT setup, we have a group named &#039;&#039;&#039;Staff&#039;&#039;&#039; 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 &#039;&#039;&#039;Staff&#039;&#039;&#039; 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.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;NOTE:&#039;&#039;&#039; If you &#039;&#039;&#039;remove&#039;&#039;&#039; 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 -&amp;gt; Modify who receives mail for ticket). They will not receive further email from the ticket. (This is known as a &amp;quot;Squelched&amp;quot; watcher in RT.)&lt;br /&gt;
&lt;br /&gt;
==== Changelog ====&lt;br /&gt;
&lt;br /&gt;
* 2022-12-07: (RobL) Created. Update to &#039;&#039;&#039;always populate&#039;&#039;&#039; Real Name field. See [https://forum.bestpractical.com/t/rt-autocreated-watcher-from-header-causing-bounces/37708/4 this forum post].&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Description:&#039;&#039;&#039; [[AddWatchersOnCorrespondDomains]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Condition:&#039;&#039;&#039; On Correspond&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; User Defined&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Template:&#039;&#039;&#039; Global template: Blank&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Stage:&#039;&#039;&#039; [[Normal]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom condition:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action preparation code:&#039;&#039;&#039; &amp;lt;code&amp;gt;return 1;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action cleanup code:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Scrip to automatically add Cc: from incoming emails to tickets, if the following conditions apply:&lt;br /&gt;
# 1. Sender is sending from a domain that is already a watcher on the ticket (Requestor/Cc/AdminCc)&lt;br /&gt;
# 2. New Cc is from the same domain, or a subdomain.&lt;br /&gt;
#&lt;br /&gt;
# This Scrip is based on AddWatchersOnCorrespond https://rt-wiki.bestpractical.com/wiki/AddWatchersOnCorrespond&lt;br /&gt;
# But does not add just any cc:&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Mary Jane &amp;lt;mjane@emea.isp.com&amp;gt;&lt;br /&gt;
#&lt;br /&gt;
# - If fred@isp.com (or any isp.com or *.isp.com) address is already a watcher on the ticket,&lt;br /&gt;
#   then mjane@emea.isp.com will be automatically added as a Cc watcher.&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Random Helpdesk &amp;lt;helpdesk@bigcolo.net&amp;gt;&lt;br /&gt;
# - helpdesk@bigcolo.net will NOT be added as a Cc: to the ticket if &amp;quot;bigcolo.net&amp;quot; is not already&lt;br /&gt;
#   a watcher on the ticket.&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
my %People;&lt;br /&gt;
&lt;br /&gt;
# Get some info:&lt;br /&gt;
my $scrip = &#039;Scrip:AddWatchersOnCorrespondDomains&#039;;&lt;br /&gt;
my $Transaction = $self-&amp;gt;TransactionObj;&lt;br /&gt;
my $Queue = $self-&amp;gt;TicketObj-&amp;gt;QueueObj;&lt;br /&gt;
my $Ticket = $self-&amp;gt;TicketObj;&lt;br /&gt;
my $Id = $self-&amp;gt;TicketObj-&amp;gt;id;&lt;br /&gt;
&lt;br /&gt;
# Load a list of all domains of people on the ticket already:&lt;br /&gt;
my @TicketDomains = GetTicketDomains();&lt;br /&gt;
&lt;br /&gt;
# Extract a list of people associated with this transaction:&lt;br /&gt;
#  - including the transaction creator, and if it is an email, the sender and recipients of that email&lt;br /&gt;
&lt;br /&gt;
my $CreatorEmailAddr = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;EmailAddress;&lt;br /&gt;
my $CreatorRealName  = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;RealName;&lt;br /&gt;
$People{$CreatorEmailAddr}{RealName} = GetFullName($CreatorEmailAddr,$CreatorRealName);&lt;br /&gt;
&lt;br /&gt;
foreach my $h (qw(From To Cc)) {&lt;br /&gt;
    my $header = $Transaction-&amp;gt;Attachments-&amp;gt;First-&amp;gt;GetHeader($h);&lt;br /&gt;
    my @addr = Mail::Address-&amp;gt;parse($header);&lt;br /&gt;
    foreach my $addrobj (@addr) {&lt;br /&gt;
        my $addr = lc $RT::Nobody-&amp;gt;UserObj-&amp;gt;CanonicalizeEmailAddress($addrobj-&amp;gt;address);&lt;br /&gt;
        # Ignore the specific addresses for this queue:&lt;br /&gt;
        next if lc $Queue-&amp;gt;CorrespondAddress eq $addr;&lt;br /&gt;
        next if lc $Queue-&amp;gt;CommentAddress eq $addr;&lt;br /&gt;
        # Ignore any email address that looks like one for ANY of our queues:&lt;br /&gt;
        next if RT::EmailParser-&amp;gt;IsRTAddress($addr);&lt;br /&gt;
        my $fullname = GetFullName($addr,$addrobj-&amp;gt;phrase);&lt;br /&gt;
        $People{$addr}{RealName} = $fullname;&lt;br /&gt;
        $RT::Logger-&amp;gt;debug(&amp;quot;$scrip: Ticket #$Id correspondence contains header - $h: $addr $fullname&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Lookup the &#039;experts&#039; (staff) group to use below:&lt;br /&gt;
my $Experts = RT::Group-&amp;gt;new($self-&amp;gt;CurrentUser);&lt;br /&gt;
$Experts-&amp;gt;LoadUserDefinedGroup(&#039;Staff&#039;);&lt;br /&gt;
&lt;br /&gt;
# Now check if each user is already watching the ticket or queue:&lt;br /&gt;
&lt;br /&gt;
foreach my $addr (sort keys %People) {&lt;br /&gt;
&lt;br /&gt;
    next if ($addr =~ /^(postmaster|root|Mailer-Daemon)\@/);&lt;br /&gt;
&lt;br /&gt;
    my $User = RT::User-&amp;gt;new( $RT::SystemUser );&lt;br /&gt;
    $User-&amp;gt;LoadOrCreateByEmail(&lt;br /&gt;
        RealName     =&amp;gt; $People{$addr}{RealName},&lt;br /&gt;
        EmailAddress =&amp;gt; $addr,&lt;br /&gt;
        Comments     =&amp;gt; &amp;quot;Autocreated by $scrip&amp;quot;,&lt;br /&gt;
    );&lt;br /&gt;
    &lt;br /&gt;
    my $Name = $User-&amp;gt;Name;&lt;br /&gt;
    my $Principal = $User-&amp;gt;PrincipalId;&lt;br /&gt;
    &lt;br /&gt;
    if ( not ($Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Requestor&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsOwner($User) )) {&lt;br /&gt;
     &lt;br /&gt;
     # check if the new cc: person&#039;s domain matches a domain already on the ticket.&lt;br /&gt;
     # If so, add them as a new watcher:&lt;br /&gt;
     if (is_address_authdomain($addr,@TicketDomains)) {&lt;br /&gt;
        # If the user is a member of the experts group, then add them as an AdminCc, otherwise as a Cc:&lt;br /&gt;
        # my $type = &#039;Cc&#039;;&lt;br /&gt;
        my $type = &#039;Requestor&#039;;&lt;br /&gt;
        $type = &#039;AdminCc&#039; if $Experts-&amp;gt;HasMember($User-&amp;gt;PrincipalObj);&lt;br /&gt;
        # Add the new watcher now and check for errors:&lt;br /&gt;
        my ($ret, $msg) = $Ticket-&amp;gt;AddWatcher(Type  =&amp;gt; $type, PrincipalId =&amp;gt; $Principal);&lt;br /&gt;
        if ($ret) {&lt;br /&gt;
            $RT::Logger-&amp;gt;info(&amp;quot;$scrip: New $type watcher added to ticket #$Id: $addr (#$Principal)&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $RT::Logger-&amp;gt;error(&amp;quot;$scrip: Failed to add new $type watcher to ticket #$Id: $addr (#$Principal) - $msg&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetFullName {&lt;br /&gt;
&lt;br /&gt;
    # Get a nicely formatted name for RT RealName Field:&lt;br /&gt;
&lt;br /&gt;
    # If fullname is blank, make something up from the&lt;br /&gt;
    # local_part of the email address: &amp;quot;fred.bloggs@...&amp;quot; -&amp;gt; &amp;quot;Fred Bloggs&amp;quot;.&lt;br /&gt;
    # Do not allow fullname to contain &amp;quot;@&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
    my ($addr,$fullname) = @_;&lt;br /&gt;
    my ($local_part,$domain) = split(&#039;@&#039;, $addr);&lt;br /&gt;
&lt;br /&gt;
     if (($fullname eq &#039;&#039;) || ($fullname =~ /\@/)) {&lt;br /&gt;
      $fullname = $local_part;&lt;br /&gt;
      $fullname =~ s/[\._-]/ /g;&lt;br /&gt;
      $fullname =~ s/(\w+)/\u$1/g;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     $fullname =~ s/^[\&amp;quot;\&#039;]|[\&amp;quot;\&#039;]$//g; # strip leading/trailing &amp;quot; or &#039;&lt;br /&gt;
     $fullname =~ s/^\s+|\s+$//g; # strip leading/trailing spaces&lt;br /&gt;
&lt;br /&gt;
    return $fullname;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub is_address_authdomain {&lt;br /&gt;
&lt;br /&gt;
        # Is address in an authorised domain?&lt;br /&gt;
        # Also allow subdomain of existing domain&lt;br /&gt;
&lt;br /&gt;
        my $addr = shift;&lt;br /&gt;
        my @domains = @_;&lt;br /&gt;
        $addr = lc($addr);&lt;br /&gt;
&lt;br /&gt;
        my ($undef,$domain) = split(&#039;@&#039;,$addr);&lt;br /&gt;
        my $is_auth = 0;&lt;br /&gt;
&lt;br /&gt;
        foreach my $authdomain (@domains) {&lt;br /&gt;
&lt;br /&gt;
           # if ($domain eq $authdomain)      { $is_auth=1; last; };&lt;br /&gt;
           if ($domain =~ /(^|(\.?))$authdomain$/) { $is_auth=1; last; };&lt;br /&gt;
           if ($authdomain =~ /(^|(\.?))$domain$/) { $is_auth=1; last; };&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return($is_auth);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetTicketDomains {&lt;br /&gt;
&lt;br /&gt;
    # my $self = shift;&lt;br /&gt;
	&lt;br /&gt;
    # Get list of existing email addresses from the ticket, and push all the domains.&lt;br /&gt;
    # We&#039;ll check this later. Any cc: by an existing requestor will be allowed from &lt;br /&gt;
    # the same domain or a subdomain.&lt;br /&gt;
&lt;br /&gt;
    # For each Role, get RoleAddresses: &amp;quot;foo@bar.com, foo@baz.com&amp;quot; &lt;br /&gt;
    # Return a deduped domain list.&lt;br /&gt;
&lt;br /&gt;
    my %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
     foreach my $role (qw(Requestor Cc AdminCc)) {&lt;br /&gt;
&lt;br /&gt;
        my $roleaddresses = lc($self-&amp;gt;TicketObj-&amp;gt;RoleAddresses($role));&lt;br /&gt;
               foreach my $a (split(&#039;, &#039;, $roleaddresses)) {&lt;br /&gt;
                 my (undef,$domain) = split(&#039;@&#039;,$a);&lt;br /&gt;
                 next if ($allticketdomains{$domain});&lt;br /&gt;
                 $allticketdomains{$domain} = $domain;&lt;br /&gt;
                }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      my @ticketdomains = sort keys %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
      return (@ticketdomains);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
return 1;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 # vim:ft=perl:&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27123</id>
		<title>AddWatchersOnCorrespondDomains</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27123"/>
		<updated>2022-12-07T17:58:49Z</updated>

		<summary type="html">&lt;p&gt;Robl: remove sort values&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= AddWatchersOnCorrespondDomains =&lt;br /&gt;
&lt;br /&gt;
This RT Scrip is a modified version of [[AddWatchersOnCorrespond]] - AddWatchersOnCorrespond simply adds &#039;&#039;&#039;all&#039;&#039;&#039; recipients as watchers.&lt;br /&gt;
&lt;br /&gt;
This Scrip is the same, except it is more restrictive with the requestors added:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;AddWatchersOnCorrespondDomains&amp;lt;/code&amp;gt; will add the person making the correspondence as a [[Watcher]] to the ticket if they are not already a [[Watcher]].&lt;br /&gt;
&lt;br /&gt;
If the transaction originated from an email message, the scrip will scan the email headers and add other recipients to the ticket as [[Watcher]]s, only if:&lt;br /&gt;
&lt;br /&gt;
* They are not already a [[Watcher]], &#039;&#039;&#039;and&#039;&#039;&#039;:&lt;br /&gt;
* Their email address has the &#039;&#039;&#039;same domain&#039;&#039;&#039; (or from a subdomain) as another watcher &#039;&#039;&#039;already&#039;&#039;&#039; on the ticket.&lt;br /&gt;
&lt;br /&gt;
* e.g. New To: and Cc: recipients added by the customer will &#039;&#039;&#039;only&#039;&#039;&#039; be added as watchers if they are from the same domain as a requestor already on the ticket.&lt;br /&gt;
* (Third parties not already on the ticket will not be added.)&lt;br /&gt;
&lt;br /&gt;
* We prefer to add as &#039;&#039;&#039;Requestors&#039;&#039;&#039; rather than Cc to simplify ticket updates. (All &amp;quot;Replies to Requestors&amp;quot; always go to everyone on the ticket.) If you do not want this behaviour, change the line: &amp;lt;code&amp;gt;my $type = &#039;Requestor&#039;;&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;my $type = &#039;Cc&#039;;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* This accommodates the majority of our use cases:&lt;br /&gt;
* 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:&lt;br /&gt;
* After this executes, We also use [https://metacpan.org/pod/RT::Extension::NonWatcherRecipients RT-Extension-NonWatcherRecipients] to add a warning into the Admins email notification of any recipients on the email that are not on the ticket.&lt;br /&gt;
&lt;br /&gt;
* In our RT setup, we have a group named &#039;&#039;&#039;Staff&#039;&#039;&#039; 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 &#039;&#039;&#039;Staff&#039;&#039;&#039; 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.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;NOTE:&#039;&#039;&#039; If you &#039;&#039;&#039;remove&#039;&#039;&#039; 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 -&amp;gt; Modify who receives mail for ticket). They will not receive further email from the ticket. (This is known as a &amp;quot;Squelched&amp;quot; watcher in RT.)&lt;br /&gt;
&lt;br /&gt;
==== Changelog ====&lt;br /&gt;
&lt;br /&gt;
* 2022-12-07: (RobL) Created. Update to &#039;&#039;&#039;always populate&#039;&#039;&#039; Real Name field. See [https://forum.bestpractical.com/t/rt-autocreated-watcher-from-header-causing-bounces/37708/4 this forum post].&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Description:&#039;&#039;&#039; [[AddWatchersOnCorrespondDomains]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Condition:&#039;&#039;&#039; On Correspond&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; User Defined&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Template:&#039;&#039;&#039; Global template: Blank&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Stage:&#039;&#039;&#039; [[Normal]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom condition:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action preparation code:&#039;&#039;&#039; &amp;lt;code&amp;gt;return 1;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action cleanup code:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Scrip to automatically add Cc: from incoming emails to tickets, if the following conditions apply:&lt;br /&gt;
# 1. Sender is sending from a domain that is already a watcher on the ticket (Requestor/Cc/AdminCc)&lt;br /&gt;
# 2. New Cc is from the same domain, or a subdomain.&lt;br /&gt;
#&lt;br /&gt;
# This Scrip is based on AddWatchersOnCorrespond https://rt-wiki.bestpractical.com/wiki/AddWatchersOnCorrespond&lt;br /&gt;
# But does not add just any cc:&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Mary Jane &amp;lt;mjane@emea.isp.com&amp;gt;&lt;br /&gt;
#&lt;br /&gt;
# - If fred@isp.com (or any isp.com or *.isp.com) address is already a watcher on the ticket,&lt;br /&gt;
#   then mjane@emea.isp.com will be automatically added as a Cc watcher.&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Random Helpdesk &amp;lt;helpdesk@bigcolo.net&amp;gt;&lt;br /&gt;
# - helpdesk@bigcolo.net will NOT be added as a Cc: to the ticket if &amp;quot;bigcolo.net&amp;quot; is not already&lt;br /&gt;
#   a watcher on the ticket.&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
my %People;&lt;br /&gt;
&lt;br /&gt;
# Get some info:&lt;br /&gt;
my $scrip = &#039;Scrip:AddWatchersOnCorrespond&#039;;&lt;br /&gt;
my $Transaction = $self-&amp;gt;TransactionObj;&lt;br /&gt;
my $Queue = $self-&amp;gt;TicketObj-&amp;gt;QueueObj;&lt;br /&gt;
my $Ticket = $self-&amp;gt;TicketObj;&lt;br /&gt;
my $Id = $self-&amp;gt;TicketObj-&amp;gt;id;&lt;br /&gt;
&lt;br /&gt;
# Load a list of all domains of people on the ticket already:&lt;br /&gt;
my @TicketDomains = GetTicketDomains();&lt;br /&gt;
&lt;br /&gt;
# Extract a list of people associated with this transaction:&lt;br /&gt;
#  - including the transaction creator, and if it is an email, the sender and recipients of that email&lt;br /&gt;
&lt;br /&gt;
my $CreatorEmailAddr = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;EmailAddress;&lt;br /&gt;
my $CreatorRealName  = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;RealName;&lt;br /&gt;
$People{$CreatorEmailAddr}{RealName} = GetFullName($CreatorEmailAddr,$CreatorRealName);&lt;br /&gt;
&lt;br /&gt;
foreach my $h (qw(From To Cc)) {&lt;br /&gt;
    my $header = $Transaction-&amp;gt;Attachments-&amp;gt;First-&amp;gt;GetHeader($h);&lt;br /&gt;
    my @addr = Mail::Address-&amp;gt;parse($header);&lt;br /&gt;
    foreach my $addrobj (@addr) {&lt;br /&gt;
        my $addr = lc $RT::Nobody-&amp;gt;UserObj-&amp;gt;CanonicalizeEmailAddress($addrobj-&amp;gt;address);&lt;br /&gt;
        # Ignore the specific addresses for this queue:&lt;br /&gt;
        next if lc $Queue-&amp;gt;CorrespondAddress eq $addr;&lt;br /&gt;
        next if lc $Queue-&amp;gt;CommentAddress eq $addr;&lt;br /&gt;
        # Ignore any email address that looks like one for ANY of our queues:&lt;br /&gt;
        next if RT::EmailParser-&amp;gt;IsRTAddress($addr);&lt;br /&gt;
        my $fullname = GetFullName($addr,$addrobj-&amp;gt;phrase);&lt;br /&gt;
        $People{$addr}{RealName} = $fullname;&lt;br /&gt;
        $RT::Logger-&amp;gt;debug(&amp;quot;$scrip: Ticket #$Id correspondence contains header - $h: $addr $fullname&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Lookup the &#039;experts&#039; (staff) group to use below:&lt;br /&gt;
my $Experts = RT::Group-&amp;gt;new($self-&amp;gt;CurrentUser);&lt;br /&gt;
$Experts-&amp;gt;LoadUserDefinedGroup(&#039;Staff&#039;);&lt;br /&gt;
&lt;br /&gt;
# Now check if each user is already watching the ticket or queue:&lt;br /&gt;
&lt;br /&gt;
foreach my $addr (sort keys %People) {&lt;br /&gt;
&lt;br /&gt;
    next if ($addr =~ /^(postmaster|root|Mailer-Daemon)\@/);&lt;br /&gt;
&lt;br /&gt;
    my $User = RT::User-&amp;gt;new( $RT::SystemUser );&lt;br /&gt;
    $User-&amp;gt;LoadOrCreateByEmail(&lt;br /&gt;
        RealName     =&amp;gt; $People{$addr}{RealName},&lt;br /&gt;
        EmailAddress =&amp;gt; $addr,&lt;br /&gt;
        Comments     =&amp;gt; &amp;quot;Autocreated by $scrip&amp;quot;,&lt;br /&gt;
    );&lt;br /&gt;
    &lt;br /&gt;
    my $Name = $User-&amp;gt;Name;&lt;br /&gt;
    my $Principal = $User-&amp;gt;PrincipalId;&lt;br /&gt;
    &lt;br /&gt;
    if ( not ($Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Requestor&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsOwner($User) )) {&lt;br /&gt;
     &lt;br /&gt;
     # check if the new cc: person&#039;s domain matches a domain already on the ticket.&lt;br /&gt;
     # If so, add them as a new watcher:&lt;br /&gt;
     if (is_address_authdomain($addr,@TicketDomains)) {&lt;br /&gt;
        # If the user is a member of the experts group, then add them as an AdminCc, otherwise as a Cc:&lt;br /&gt;
        # my $type = &#039;Cc&#039;;&lt;br /&gt;
        my $type = &#039;Requestor&#039;;&lt;br /&gt;
        $type = &#039;AdminCc&#039; if $Experts-&amp;gt;HasMember($User-&amp;gt;PrincipalObj);&lt;br /&gt;
        # Add the new watcher now and check for errors:&lt;br /&gt;
        my ($ret, $msg) = $Ticket-&amp;gt;AddWatcher(Type  =&amp;gt; $type, PrincipalId =&amp;gt; $Principal);&lt;br /&gt;
        if ($ret) {&lt;br /&gt;
            $RT::Logger-&amp;gt;info(&amp;quot;$scrip: New $type watcher added to ticket #$Id: $addr (#$Principal)&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $RT::Logger-&amp;gt;error(&amp;quot;$scrip: Failed to add new $type watcher to ticket #$Id: $addr (#$Principal) - $msg&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetFullName {&lt;br /&gt;
&lt;br /&gt;
    # Get a nicely formatted name for RT RealName Field:&lt;br /&gt;
&lt;br /&gt;
    # If fullname is blank, make something up from the&lt;br /&gt;
    # local_part of the email address: &amp;quot;fred.bloggs@...&amp;quot; -&amp;gt; &amp;quot;Fred Bloggs&amp;quot;.&lt;br /&gt;
    # Do not allow fullname to contain &amp;quot;@&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
    my ($addr,$fullname) = @_;&lt;br /&gt;
    my ($local_part,$domain) = split(&#039;@&#039;, $addr);&lt;br /&gt;
&lt;br /&gt;
     if (($fullname eq &#039;&#039;) || ($fullname =~ /\@/)) {&lt;br /&gt;
      $fullname = $local_part;&lt;br /&gt;
      $fullname =~ s/[\._-]/ /g;&lt;br /&gt;
      $fullname =~ s/(\w+)/\u$1/g;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     $fullname =~ s/^[\&amp;quot;\&#039;]|[\&amp;quot;\&#039;]$//g; # strip leading/trailing &amp;quot; or &#039;&lt;br /&gt;
     $fullname =~ s/^\s+|\s+$//g; # strip leading/trailing spaces&lt;br /&gt;
&lt;br /&gt;
    return $fullname;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub is_address_authdomain {&lt;br /&gt;
&lt;br /&gt;
        # Is address in an authorised domain?&lt;br /&gt;
        # Also allow subdomain of existing domain&lt;br /&gt;
&lt;br /&gt;
        my $addr = shift;&lt;br /&gt;
        my @domains = @_;&lt;br /&gt;
        $addr = lc($addr);&lt;br /&gt;
&lt;br /&gt;
        my ($undef,$domain) = split(&#039;@&#039;,$addr);&lt;br /&gt;
        my $is_auth = 0;&lt;br /&gt;
&lt;br /&gt;
        foreach my $authdomain (@domains) {&lt;br /&gt;
&lt;br /&gt;
           # if ($domain eq $authdomain)      { $is_auth=1; last; };&lt;br /&gt;
           if ($domain =~ /(^|(\.?))$authdomain$/) { $is_auth=1; last; };&lt;br /&gt;
           if ($authdomain =~ /(^|(\.?))$domain$/) { $is_auth=1; last; };&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return($is_auth);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetTicketDomains {&lt;br /&gt;
&lt;br /&gt;
    # my $self = shift;&lt;br /&gt;
	&lt;br /&gt;
    # Get list of existing email addresses from the ticket, and push all the domains.&lt;br /&gt;
    # We&#039;ll check this later. Any cc: by an existing requestor will be allowed from &lt;br /&gt;
    # the same domain or a subdomain.&lt;br /&gt;
&lt;br /&gt;
    # For each Role, get RoleAddresses: &amp;quot;foo@bar.com, foo@baz.com&amp;quot; &lt;br /&gt;
    # Return a deduped domain list.&lt;br /&gt;
&lt;br /&gt;
    my %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
     foreach my $role (qw(Requestor Cc AdminCc)) {&lt;br /&gt;
&lt;br /&gt;
        my $roleaddresses = lc($self-&amp;gt;TicketObj-&amp;gt;RoleAddresses($role));&lt;br /&gt;
               foreach my $a (split(&#039;, &#039;, $roleaddresses)) {&lt;br /&gt;
                 my (undef,$domain) = split(&#039;@&#039;,$a);&lt;br /&gt;
                 next if ($allticketdomains{$domain});&lt;br /&gt;
                 $allticketdomains{$domain} = $domain;&lt;br /&gt;
                }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      my @ticketdomains = sort keys %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
      return (@ticketdomains);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
return 1;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 # vim:ft=perl:&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=Contributions&amp;diff=27122</id>
		<title>Contributions</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=Contributions&amp;diff=27122"/>
		<updated>2022-12-07T17:55:34Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* ScripActions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Contributions to RT =&lt;br /&gt;
&lt;br /&gt;
Contains info about software that third parties have contributed to RT. If you know of a contribution or enhancement that is not in this list, please add to the best section possible below, in alphabetical order. Also include notes about the RT versions tested.&lt;br /&gt;
&lt;br /&gt;
You may also want to check the [[Documentation]] page because many articles there describe how to add features that RT does not have by default.&lt;br /&gt;
&lt;br /&gt;
Packages that have their own installer have been moved to [[Extensions]].&lt;br /&gt;
&lt;br /&gt;
See Also the outdated [[Patches]], whose content should be moved to the appropriate subsections below.&lt;br /&gt;
&lt;br /&gt;
== Coping with Spam ==&lt;br /&gt;
&lt;br /&gt;
The techniques for dealing with spam span the divisions below and have been collected in a single place for easy review. Please see [[SpamFiltering]], limiting additions to tricks that differ significantly from the existing material for conciseness.&lt;br /&gt;
&lt;br /&gt;
== ScripConditions ==&lt;br /&gt;
&lt;br /&gt;
Custom [[Condition]]s. Doesn&#039;t matter if it&#039;s module or text to fill into &amp;quot;user defined condition&amp;quot; block in the WebUI, all live here.&lt;br /&gt;
&lt;br /&gt;
*Please*, start wiki page names with &amp;quot;On&amp;quot; prefix if you don&#039;t want to add condition into [[CustomConditionSnippets]].&lt;br /&gt;
&lt;br /&gt;
* [[CustomConditionSnippets]] - &#039;&#039;&#039;very big&#039;&#039;&#039; and organized list of simple conditions&lt;br /&gt;
&lt;br /&gt;
* [[AnyTransactionSource]];&lt;br /&gt;
* [[AnyReminderTransaction]] - a scrip condition which triggers on all reminder transactions;&lt;br /&gt;
* [[MuteResolve]] - let resolver choose not to send email on resolve;&lt;br /&gt;
* [[MuteResolve Redux]] - alternate version of MuteResolve that uses a status code and custom field to avoid some problems;&lt;br /&gt;
* [[NotResolved]] - a scrip condition to detect all ticket that aren&#039;t marked resolved;&lt;br /&gt;
* [[On Correspond Notify AdminCcs if Not Owned|OnCorrespondNotifyAdminCcsNotOwned]] - Notifies AdminCc only if ticket is unowned&lt;br /&gt;
* [[OnCreateAutoReplyException]] - a scrip condition that will send [[AutoReply]] emails to users except for those in the execption list;&lt;br /&gt;
* [[OnCreateCheckCF]] - check presence of a mandatory cf on ticket creation;&lt;br /&gt;
* [[OnCreateFromEmail]] - scrip condition that will send [[AutoReply]] emails to users only when a ticket is opened via email;&lt;br /&gt;
* [[OnCreationOfApprovalTicket]] - a scrip condition that checks if you are creating a new approval ticket&lt;br /&gt;
* [[OnCreatePageOffHours]] - scrip condition that will send email via the [[SendEmailAction]] scrip to and external account. This scrip has an additional condition where it is checking if the request is coming from a specific user or system.&lt;br /&gt;
* [[OnCreateSetUserDetails]] - parse vCards for user information.&lt;br /&gt;
* [[OnCustomFieldValueChange]] - this condition matches when [[CustomField]] value is changed;&lt;br /&gt;
* [[OnMaxPriority]] - check if ticket hits maximal priority (used with priority escalation)&lt;br /&gt;
* [[OnMerge]] - a scrip condition that triggers on ticket merges;&lt;br /&gt;
* [[OnResolveOnce]] - a scrip condition that matches when a ticket is resolved but only if the ticket was not resolved before;&lt;br /&gt;
* [[OnStatusChange]] - using RT&#039;s condition &#039;On Resolve&#039; to trigger other conditions&lt;br /&gt;
* [[OnStealEnhanced]] - small enhancement to the [[OnSteal]] condition in &amp;quot;RT Essentials&amp;quot;.&lt;br /&gt;
* [[OnTimeEstimated]] - when the time Time Estimated fields is set&lt;br /&gt;
* [[OnToOrCC]] - when a request is sent to a particular mailbox&lt;br /&gt;
* [[OnWatcherChange]] - a scrip condition to detect when a ticket watcher is added or deleted.&lt;br /&gt;
* [[OnWebCorrespond]] - determines if a reply is from an incoming email message or from the web interface.&lt;br /&gt;
* [[ReplyBasedUponContent]] - a scrip to reply to an email based upon its content&lt;br /&gt;
* [[ReplyToResolved]] - a scrip condition to detect all ticket that are marked resolved;&lt;br /&gt;
* [[ShowDashboardTabs]] - add dashboards to the personal quickbar&lt;br /&gt;
* [[TicketIDMatches]].pm - a condition which triggers only one ticket by id;&lt;br /&gt;
* [[UntouchedInHours]] - scrip condition that checks if a ticket&#039;s LastUpdate is more than the specified number of hours;&lt;br /&gt;
* [[OnCorrespondOpenUnlessResolved]] - condition to stop RT re-opening resolved tickets when the user replies to them by email;&lt;br /&gt;
* [[NoReplyAddress]] - Use a &amp;quot;no reply&amp;quot; RT email address that posts comments as a Reply/Correspondence (so they can see it in web UI), but does not send email to Requestors&lt;br /&gt;
&lt;br /&gt;
== ScripActions ==&lt;br /&gt;
&lt;br /&gt;
Nice custom [[ScripAction]]s that makes your life easier.&lt;br /&gt;
&lt;br /&gt;
* [[AddAdminCc]] - Add an [[AdminCc]] to tickets for a specific queue, or for queue change.&lt;br /&gt;
* [[AddAdminCcAndChangeQueue]] - Add an [[AdminCc]] to a ticket, and move the ticket to a queue.&lt;br /&gt;
* [[AddSquelchedCc]] - Add group members as Cc to give them access to tickets but without email notifications.&lt;br /&gt;
* [[AddRefersToOnEqualCustomField]] - Create [[RefersTo]] links to tickets with same custom field value&lt;br /&gt;
* [[AddRequestor]] - Allow some accounts to view a ticket without adding by hand some requestors.&lt;br /&gt;
* [[AddWatcherPerTicket]] - Add a watcher to a specific ticket (e.g. when certain conditions are met, such as high urgency)&lt;br /&gt;
* [[AddWatchersOnCorrespond]] - Add Actor &amp;amp;amp; other Cc&#039;d people to ticket on any Correspondence (complement to using [[ParseNewMessageForTicketCcs]] to add people on ticket creation).&lt;br /&gt;
* [[AddWatchersOnCorrespondDomains]] - Modified version of [[AddWatchersOnCorrespond]]. Only adds watchers if they are from the same domain as a watcher already on the ticket.&lt;br /&gt;
* [[LoopIn]] - Similar to [[AddWatchersOnCorrespond]], but with some additional security and rules to prevent random people from getting added to existing tickets.&lt;br /&gt;
* [[AutomaticCustomFieldValue]] - set CF value by requestor&#039;s email address.&lt;br /&gt;
* [[AutoCcOwner]] - Add the owner as an [[AdminCc]]&lt;br /&gt;
* [[AutoCcLastOwner]] - When the owner is changed, automatically add the previous owner to the Cc list.&lt;br /&gt;
* [[AutoChangeQueue]] - Change queue if a specific group member take a ticket&lt;br /&gt;
* [[AutoCloseOnNagiosRecoveryMessages]] - Automatically merges and closes a ticket based on the creation of another ticket, in this case, of a nagios generated RECOVERY e-mail&lt;br /&gt;
* [[AutoSetOwner]] - how to automatically set owner on resolution&lt;br /&gt;
* [[AutoSetOwnerIfAdminCc]] - Automatically set ticket owner to an [[AdminCc]]&lt;br /&gt;
* [[AutoSetOwnerFromCC]] - how to automatically set owner from Cc&lt;br /&gt;
* [[BounceMerge]] - Merge a Mail Bounce into the original Ticket&lt;br /&gt;
* [[CcManagers]] - Add the manager subgroup of the ticket creator as Cc, useful when managing departments as groups (as [[Rights|rights]] suggests).&lt;br /&gt;
* [[CopyContentToCF]] - When called &#039;On Create&#039; will copy &amp;lt;tt&amp;gt;$Transaction-&amp;gt;Content&amp;lt;/tt&amp;gt; to the custom field &amp;lt;tt&amp;gt;Problem&amp;lt;/tt&amp;gt;.&lt;br /&gt;
* [[CreatePriorityBasedOnCustomFieldValues]] - Automatically set the Priority based on Ticket Urgency and Impact.&lt;br /&gt;
* [[DefaultCustomFieldValue]] - set default CF value.&lt;br /&gt;
* [[DivideTicketIntoSubtasks]] - auto-creates new tickets for each subtask in a bulleted list&lt;br /&gt;
* [[DueDateinBusinessHours]] - scrip action that sets short-term ticket due dates to coincide with business hours&lt;br /&gt;
* [[EscalateTicketOnAction]] - increment the priority of a ticket whenever a certain action is taken&lt;br /&gt;
* [[ExtractCustomFieldValues]] - set [[CustomField]] with arbitrary data extracted from a ticket using a simple template&lt;br /&gt;
* [[ForkIntoNewTicket]] - a scrip action to copy response/comment ticket to another ticket&lt;br /&gt;
* [[JumpToFrontPageOnTicketResolve]] - Admonish me if you wish, but on resolve of a ticket this scrip will redirect your browser to a new URL of your choosing.&lt;br /&gt;
* [[NotifyNonRecipients]] - notify recipients unless they were already cc&#039;d on the mail&lt;br /&gt;
* [[OnCreateSetDeptHeadCc]] - On create in the case that [[CustomField]].Department = &#039;Foo&#039; then add group &#039;Head Foo&#039; as a Cc&lt;br /&gt;
* [[OnCreateAddGroupCc]] - On create from Requestor email that matches a regex, add members of arbitrary group to CC list while making sure not to add anyone already associated with the ticket&lt;br /&gt;
* [[OnOwnershipSquelchMailtoQueueWatchers]] - When a ticket&#039;s owner changes from &amp;quot;Nobody&amp;quot; to a regular user, stop sending mail to people who are just Queue Watchers.&lt;br /&gt;
* [[OnQueueChangeFixReminders]] - Tickets lose reminders when they are moved between queues. This scrip fixes that&lt;br /&gt;
* [[OnQueueChangeResetPriorityAndDueDate ]] - Reset priorities and due date when moving a ticket to another queue.&lt;br /&gt;
* [[OpenTicketOnAllMemberResolve]]&lt;br /&gt;
* [[OpenDependantsOnResolve]]&lt;br /&gt;
* [[RemoteControlLimeSurvey2]] - new Version of the old [[RemoteControlLimeSurvey]] - Scrip action to trigger [[LimeSurvey]] to add a token to a given survey.&lt;br /&gt;
* [[ResolveTicket]]&lt;br /&gt;
* [[SendEmailAction]] - sends an alert to someone not specified in the ticket&lt;br /&gt;
* [[SendHTMLEmail]] - modification of RT::Action::[[SendEmail]] for sending mails with Content-Type: text/html&lt;br /&gt;
* [[SendNagiosAlert]] - Send an alert to Nagios if a new or open ticket exists in any defined queues.&lt;br /&gt;
* [[SendAlarmPointEvent]] - Send an event to alarm point to invoke SMS/Voice/E-mail alerts&lt;br /&gt;
* [[SetActiveOnCustomerReply]] - detects if a ticket is set to a certain status and changes that status if someone other than the ticket owner replies.&lt;br /&gt;
* [[SetCorresponderAsCC]] - Add anyone who correspondes on a ticket to CC - for the lazy users.&lt;br /&gt;
* [[SetOwnerAndQueueBySubject]] - Set queue and owner when the subject matches a regex&lt;br /&gt;
* [[SetTicketPropertiesViaMail]] - scrip action that allow you to set status, owner and etc via email&lt;br /&gt;
* [[SetTimeWorkedAutomatically]] - scrip action that updates automatically the Time Worked field on the Ticket&lt;br /&gt;
* [[SpamScore2Priority]] - Expose message spam score as priority for review&lt;br /&gt;
&lt;br /&gt;
== Template parts ==&lt;br /&gt;
&lt;br /&gt;
Code that you can put into your mail [[Template]]s, [[Template]] page has also some code snippets.&lt;br /&gt;
&lt;br /&gt;
* [[AddAttachmentLinksToMail]] - adds links on file attachments that ticket has&lt;br /&gt;
* [[AddCustomFieldsValuesToMail]] - puts all [[CustomField]]s values into mail&lt;br /&gt;
* [[AddCustomFieldstoTemplates]] - extracting just one or more [[CustomField]]s, without recalling the entire set&lt;br /&gt;
* [[AddTicketHistoryToMail]] - complex template that adds ticket&#039;s history&lt;br /&gt;
* [[AddQueueNameToMailHeaders]] - add the relevent queue name to mail sent&lt;br /&gt;
* [[AddLastCommentToMail]]&lt;br /&gt;
* [[AddRichTextEditorToCustomField]] - add CKEditor to a [[CustomField]]s textarea values&lt;br /&gt;
* [[AutoreplyOrCorrespondence]] - if creator is not requestor use Corresondence instead of Autoreply&lt;br /&gt;
* [[EmailGroup]] - email an RT [[Group]]&lt;br /&gt;
* [[ForkTemplate]] - send a range of customized responses without hard-coding variants.&lt;br /&gt;
* [[ForwardFirstMessage]] - re-send the first message (i.e. ticket creation message)&lt;br /&gt;
* [[MailingListIntegration]] - Scrip + Template to optionally subscribe requestors to a listserv.&lt;br /&gt;
* [[MultipleOutgoingEmailAddresses]]&lt;br /&gt;
* [[UseActorAsSender]]&lt;br /&gt;
* [[X-Priority]] - Maps RT priority field to email priority header.&lt;br /&gt;
&lt;br /&gt;
== Callbacks ==&lt;br /&gt;
&lt;br /&gt;
Callbacks are an easy way to [[CleanlyCustomizeRT]]&lt;br /&gt;
&lt;br /&gt;
* [[CloningQueues]] - Add user functionality to clone existing queues including templates, scrips, privileges and custom fields during queue creation&lt;br /&gt;
* [[CreateChildTicket]] - Add a button to the Ticket display to create a child ticket in another queue&lt;br /&gt;
* [[HideTransactions]] - hide messages from a history view&lt;br /&gt;
* [[ModifyQuery]] -Change default simple search behavior to in/ex-clude closed tickets, etc.&lt;br /&gt;
* [[MakeClicky:Fedex]] - Make a link to Fedex tracking website whenever phrase looks like a tracking number&lt;br /&gt;
* [[QuickResolveandQuickReject]] - Create two actions in Display page which allow you to reject or resolve the ticket without no comments.&lt;br /&gt;
* [[MailtoLinksFromTransactions]] - Create mailto-inks at the top of transactions&lt;br /&gt;
* [[TwoColumnTicketLayout]] - Display a ticket&#039;s history and metadata side by side&lt;br /&gt;
* [[AutoRequestorTicketSearch]] - Automatically search for requestor&#039;s last updated tickets on creation page&lt;br /&gt;
* [[NewTicketsAlert]] - Display a messagebox on My RT listing new tickets and add new ticket count to page title&lt;br /&gt;
&lt;br /&gt;
== Extensions ==&lt;br /&gt;
&lt;br /&gt;
Patches, [[Overlays]], Mason components, configuration tools and so on. These extensions are unlikely to become RT core package.&lt;br /&gt;
&lt;br /&gt;
Packages that have their own installer have been moved to [[Extensions]] - Below are code bits too small/experimental/etc. to warrant a standalone package:&lt;br /&gt;
&lt;br /&gt;
* [[AutomaticImageResize]] -- automatically scale images that are displayed inline in your ticket history&lt;br /&gt;
* [[AutoRedirectToSelfService]] - automatically redirect your users to Self Service if they don&#039;t have &amp;quot;own ticket&amp;quot; permissions;&lt;br /&gt;
* [[BasicVsAdvancedInterface]] - allow privileged users to choose between the [[SelfService]] and RT at a Glance interfaces&lt;br /&gt;
* [[BetterPerformanceWithFullText]] - tweak to improve full text search in Postgres (and a note about Oracle).&lt;br /&gt;
* [[CalendarWidget]] - add a the dynarch.com jscalendar widget to pick dates&lt;br /&gt;
* [[CannedReplies]] - provides drop-down list of templates that can be included in a ticket reply&lt;br /&gt;
* [[ColorizedLinks]] - colorizing list of Ticket Links depends on it&#039;s status&lt;br /&gt;
* [[ConvertMultiSelectToCheckboxes]] - converts the multiselect customfield into a checkbox interface&lt;br /&gt;
* [[CreateGroupAndAddMembers]] - an overylay that grants [[AdminGroupMembership]] when a user creates a group&lt;br /&gt;
* [[CustomFieldRightsWithoutSeeQueue]] - for a custom ticket creation form that includes the custom fields and you don&#039;t want to turn on the [[SeeQueue]] right;&lt;br /&gt;
* [[DisplayCustomFieldsInTicketSearch]];&lt;br /&gt;
* [[DisplayCustomFieldsInUserPrefs]] - add user-based custom fields to User/Prefs.html&lt;br /&gt;
* [[DisplayCustomFieldsOnTicketUpdatePage]] - Make a ticket&#039;s custom fields visible when updating or resolving a ticket.&lt;br /&gt;
* [[EditCustomFieldsOnUpdate]] - edit custom fields on update, reply, comment...;&lt;br /&gt;
* [[ForwardWithMessage]] - forward a transaction or ticket WITH a message for the recipient&lt;br /&gt;
* [[GroupMembershipCheck]] - snippet that can be included in a custom form if you want to limit the display of some things to a specific group;&lt;br /&gt;
* [[HideTransactions]] - hide messages from a history view&lt;br /&gt;
* [[HomePageSavedSearches]] - display lists of saved searches on the RT home page;&lt;br /&gt;
* [[HTML5Charts]] - eye candy with jqplot;&lt;br /&gt;
* [[ImportCustomFieldValues]] - Fills custom field data from external source;&lt;br /&gt;
* [[LdapSummary]] - Several authentication and user creation techniques&lt;br /&gt;
* [[MaintenanceMode]] - a quick and dirty way to shut down your site temporarily&lt;br /&gt;
* [[MandatorySubject]] - make a ticket&#039;s Subject mandatory using [[JavaScript]]&lt;br /&gt;
* [[MoreAboutPrivilegedUsers]] - show the More About box for privileged users&lt;br /&gt;
* [[MoveRTName]] - Move the $rtname to the end of a subject line&lt;br /&gt;
* [[MultipleSubjectTokens]] - Change subject token dependend on queue name&lt;br /&gt;
* [[PasswordReset]] - show password reset on login&lt;br /&gt;
* [[PersistentSessions]] - Making users&#039; sessions persistent&lt;br /&gt;
* [[PopUpAlert]] - Send Reply instead of Comment&lt;br /&gt;
* [[QuickTicket]] - quickly create a ticket on homepage with custom fields and status&lt;br /&gt;
* [[Extension - Queue Change On Update]] - This is a plugin which adds a callback to RT with the result that you add a Queue change dropdown box to the ticket update page (comment/reply page). Very handy for proper ticket transport between Queue&#039;s, especially whena Queue represents a department.&lt;br /&gt;
* [[Rich Text Custom Fields]] - convert wiki text custom fields into rich text custom fields&lt;br /&gt;
* [[ResolveSendsReply]] - change the &amp;quot;Resolve&amp;quot; link to reply instead of comment by default&lt;br /&gt;
* [[SelectRequestor]] - allow user to select requestor from drop down lists instead of typing email address;&lt;br /&gt;
* [[SelectDefaultQueue]] - Using a user-based custom field, cause all queue name drop down lists to autopick that queue&lt;br /&gt;
* [[SendEmail]] - lets template send an e-mail without adding RT ticket info to subject line&lt;br /&gt;
* [[ShortcutPopupMenuScript]] - Javascript based popup menu of useful actions for ticket list&lt;br /&gt;
* [[SideBySideTicketScreen]] - ticket update screen that shows ticket history &amp;quot;side-by-side&amp;quot; with ticket details.&lt;br /&gt;
* [[SignatureToTheTop]] - Insert user&#039;s signature to the top of the message, not to the bottom as default&lt;br /&gt;
* [[SimpleSearchExcludeResolved]] - exclude resolved and rejected tickets from simple search results&lt;br /&gt;
* [[ShowStatusInColor]] - show status (or priority) in Color in Search screens;&lt;br /&gt;
* [[ShowPerQueueInstructions]]&lt;br /&gt;
* [[SpawnChildTicket]] - spawn a child ticket in a given queue list;&lt;br /&gt;
* [[SpatialRT]] - Plot tickets on a map. More of a recipe than a full solution but could be expanded.&lt;br /&gt;
* [http://wiki.bestpractical.com/view/Spreadsheet+RequestorDetails Spreadsheet+RequestorDetails] - Creates a Spreadsheet link which includes some requestor details if the user has the rights to see them (Global ACL [[AdminUsers]])&lt;br /&gt;
* [[SpreadsheetDisplayedFields]] - download just the displayed search result fields into a spreadsheet;&lt;br /&gt;
* [[StockAnswers]] - insert predefined templates into replies - now with a template editor;&lt;br /&gt;
* [[SuppressOutgoingMail]] - optionally turn off outgoing mail&lt;br /&gt;
* [[TextBasedPriorities]] - use &amp;lt;code&amp;gt;Normal, High, Emergency...&amp;lt;/code&amp;gt; for priority value instead of numbers;&lt;br /&gt;
* [[TicketsPerQueue]] - Display X unowned tickets per queue a user has rights to&lt;br /&gt;
* [[TimeWorked]] - Display a report with total time worked per queue/per user&lt;br /&gt;
* [[TimeWorkedReport]] - Display a report with total time worked per user per ticket for one or more queues.&lt;br /&gt;
* [[TimelineStartDue]] - Modify Timeline package to display tickets using the Start and Due date&lt;br /&gt;
* [[UpdateTimeLeft]] - update Time Left -field from &amp;lt;code&amp;gt;Update.html&amp;lt;/code&amp;gt; (/reply, comment/)&lt;br /&gt;
* [[ViewMyRequests]] - mod to [[SelfService]] that allows requestors to see ticket summaries, and details of their own tickets;&lt;br /&gt;
* [[WhoHasRightsToWhat]] - a mason component that makes it easier to understand your complex rights setup.&lt;br /&gt;
* [[WatcherSummary]] - a mason component that gives you an overview of tickets where a user is listed as a watcher.&lt;br /&gt;
* [[LockLessSessionsMySQL]] - a new version of Sessions which works in no-locking mode&lt;br /&gt;
&lt;br /&gt;
== External utils ==&lt;br /&gt;
&lt;br /&gt;
Various standalone utilities.&lt;br /&gt;
&lt;br /&gt;
* [[backupRT]] - Run a quick backup of RT files (Debian)&lt;br /&gt;
* [[backupRTDB]] - RT Database Backup Script (Debian)&lt;br /&gt;
* [http://nextup.cz/bestpractical-rt-widget/ BestPracticalRT Mac OSX Widget] - simple configurable OSX widget for quick posting tickets&lt;br /&gt;
* [http://search.cpan.org/dist/Bot-BasicBot-Pluggable-Module-RT Bot::BasicBot::Pluggable::Module::RT] - an IRC Bot module that allows full querying of RT tickets from an IRC session. It can do as much as [http://search.cpan.org/dist/RT-Client-REST/ RT::Client::REST] can.&lt;br /&gt;
* [[CleanupSessions]] - Clean up old database sessions&lt;br /&gt;
* [[CloseAll]] - Close all TT in a queue&lt;br /&gt;
* [[ConvertLegacyToRt]] - Converts a Legacy Flat File to RT Tickets.&lt;br /&gt;
* [[delete-transaction]] - [http://download.bestpractical.com/pub/rt/contrib/3.0/Other/Censorware/delete-transaction Original version] was old, poured out errors and didn;t work well on 3.8. This one is fixed.&lt;br /&gt;
* [[DenormalizedViewsForReporting]] - Several views to allow SQL reporting outside of RT.&lt;br /&gt;
* [https://github.com/botsie/dirt Dirt] - A web application that provides kanban boards, scrum taskboards, graphical and tabluar reports on top of RT. &lt;br /&gt;
* Email reminders&lt;br /&gt;
** [[DueDateRemindersByEmail]] - A script (to be run daily) that sends email notifications for expired tickets to owners and Queues/Tickets [[AdminCC]]&lt;br /&gt;
** [http://www.cs.kent.ac.uk/people/staff/tdb/rt3/ rt-remind] - Stick this in your crontab to send out reminders about open tickets.&lt;br /&gt;
** [[rtReminderMails]] - Cronscript that sends mails about reminders that are due in the next two days to the ticket and reminder owners.&lt;br /&gt;
** [[rtUnifiedreminder]] - All the other reminder scripts are based on [[StartDate]], [[DueDate]] or Priority but not all organizations make use of those fields. Also, all the other scripts only send plaintext email to the ticket owner. This script sends one HTML mail (so you can click the tickets and links to RT searches) that lists all tickets that seem to be getting too old without being touched. &amp;quot;Too old&amp;quot; is based on [[LastUpdated]] field, with the amount of time configurable for New, Open and Stalled tickets.&lt;br /&gt;
** [[rt-askForFeedback]] - This Script bases on the above &amp;quot;[[DueDateRemindersByEmail]]&amp;quot; and got modified in the way, that it sends Mails to customers other than to administrators and Ticket-Owner. You will be able to &amp;quot;remind&amp;quot; customer to get back to you with a reply if the ticket is in &amp;quot;stalled&amp;quot;-State. If there is no response within a time of &amp;quot;x&amp;quot;, you can autoclose the ticket.&lt;br /&gt;
* [http://www.bestpractical.com/pub/rt/contrib/3.0/Other/F2Wcvs-to-rt-3.0 F2Wcvs-to-rt] - Tool to help converting from the [http://f2w.sourceforge.net/ F2W] helpdesk system to RT&lt;br /&gt;
* [http://mit.edu/alexmv/Public/graph-mason-deps graph-mason-deps] uses [http://www.graphviz.org/ GraphViz] to create a graph of which components call each other&lt;br /&gt;
* [[html2mime]] - small perl script used to create a text/plain part from 100% html messages&lt;br /&gt;
* [http://pthbb.org/software/manual/mailfilter mailfilter] - spam checking and more&lt;br /&gt;
* [[Mbox2Rt]] - import a unix-style mailbox into RT&lt;br /&gt;
* [[ProcmailRecipes]] - procmail recipes used for email filtering&lt;br /&gt;
* [http://www.dmo.ca/projects/hacks/RT/RT.bm RT.bm] is a plugin for [http://www.mozilla.org/projects/mozbot/ mozbot] that allows some minimal querying of RT tickets from an IRC session.&lt;br /&gt;
* [http://www.bestpractical.com/pub/rt/contrib/3.0/Other/rtadduser rt-adduser] ([http://www.bestpractical.com/pub/rt/contrib/3.0/Other/rtadduser.README docs]) - command line tool to add RT users.&lt;br /&gt;
* [[Rt-auth-user|rt-auth-user]] - perl script for authenticating a user against RT (both local and external sources through [[ExternalAuth]] )&lt;br /&gt;
* [http://mimosaid.007sites.com/rt-batch-add-users.txt rt-batch-add-users] - command line tool to add a batch of RT users based on data of a csv file.&lt;br /&gt;
* [rt-batch-stats [[RT3BatchStats]]] - Command Line or batch statistics.&lt;br /&gt;
* [[RtBounceHandler]] - scan bounce email for ticket details, then post essense of bounce info to that ticket.&lt;br /&gt;
* [http://www.cpan.org/authors/id/A/AH/AHARRISON/scripts/rt-class-map-1.3.pl rt-class-map-1.3-pl] - Show methods available to specific RT objects.&lt;br /&gt;
* [http://www.bestpractical.com/pub/rt/contrib/3.0/rt-cvsgate.txt rt-cvsgate] ([http://www.bestpractical.com/pub/rt/contrib/3.0/rt-cvsgate.README docs]) - cvs integration for request tracker.&lt;br /&gt;
* [http://www.cs.kent.ac.uk/people/staff/tdb/rt3/ rt-escalate] ([[ConfigureEscalation]]) - stick this in your crontab to escalate priority on tickets automatically&lt;br /&gt;
* [[RTLogins]] - simple php script that creates a login report (&amp;quot;Who&#039;s using RT?&amp;quot;)&lt;br /&gt;
* [http://wiki.bestpractical.com/view/rt_logins_email2ldap rt_logins_email2ldap] - script to convert email usernames to LDAP/Active Directory usernames&lt;br /&gt;
* [[rt-google-charts]] - produce google charts for queue statistics&lt;br /&gt;
* [http://www.bestpractical.com/pub/rt/contrib/3.0/Conversion/rt3-on-pg-to-mysql rt-on-pg-to-mysql] - Convert your rt database from postgres to mysql.&lt;br /&gt;
* [http://shellscripts.org/project/rtqueues rt-queues] Shellscript called from procmail to sort E-Mails to the correct queue. Parses incoming mails and based on addresses in To: and CC: fields automatically sorts mails to the correct queue. This makes changing your MTA configuration for every new queue obsolete.&lt;br /&gt;
* [[RtTalkToSelf]] - a mail filter script that allows a single RT instance to have one ticket as the &amp;quot;external requestor&amp;quot; of another.&lt;br /&gt;
* [[scan-and-set]] - sample perl script to all the text attachments of open tickets for a text string and set a custom field with the result.&lt;br /&gt;
* [http://www.jeconley.com/pub/rt/statdump statdump/statcron] - script and cronjob to generate RT management reports. The original URL is defunct, but a patched version of the scripts are still in [http://www.nabble.com/Patched-statdump-statcron-scripts-td2264154.html listarchives]. An updated version that fixed [[AverageTickets]] calcs and CURDATE selection used to be available at http://www.lei.net.au/stats.tgz.&lt;br /&gt;
* [[CountTickets]] - a BASH script to count tickets in a [[MySQL]] db.&lt;br /&gt;
* [[ShredderControl]] - a BASH script to shred tickets using [[RTx]]-Shredder.&lt;br /&gt;
* [[MigrateBugzillaToRT]] - Migrate a Bugzilla instance cleanly to RT&lt;br /&gt;
* [[IntegrateSphinx]] - How to integrate the Sphinx full-text search engine into RT&lt;br /&gt;
* [[SendingCommentsDirectlyToATicketWithExim4]] - How to configure exim4 to send comments to tickets based on ticket ID and custom field values&lt;br /&gt;
* [[rt-clonequeue]] - a perl script used to create a new queue using an existing one as a template (copying its custom fields, permissions, templates and scrips but not its tickets).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Browser Tools ===&lt;br /&gt;
&lt;br /&gt;
* [[AutomaticTextareaAutosave]] - Firefox + Windows only&lt;br /&gt;
* Integrated Browser Search - Add a custom search engine for RT system to your modern browser with [[OpenSearchPluginForRT]]. See also [[SearchRTFromFirefox]].&lt;br /&gt;
* [[RTKeyboardShortcut]] - Firefox + Greasmonky script (can be added to an installation)&lt;br /&gt;
&lt;br /&gt;
== Database Queries ==&lt;br /&gt;
&lt;br /&gt;
* [[QueryResolvedByUser]]&lt;br /&gt;
&lt;br /&gt;
== Other ==&lt;br /&gt;
&lt;br /&gt;
* [http://www.cs.mu.oz.au/systems/rt.html Department of Computer Science and Software Engineering, The University of Melbourne&#039;s RT page]&lt;br /&gt;
* [http://page.mi.fu-berlin.de/~pape/rt3screenshots/ Dirk Pape&#039;s RT page] (Fixed &amp;quot;Fork&amp;quot; tarball link, I hope it is the right Version.)&lt;br /&gt;
* [http://web.mit.edu/tooltime/ MIT IS&amp;amp;amp;T RT page]&lt;br /&gt;
* [http://www.usit.uio.no/it/rt/modifications/ University of Oslo&#039;s RT page]&lt;br /&gt;
* [http://www.cs.kent.ac.uk/people/staff/tdb/rt3/ University of Kent&#039;s RT page]&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=Contributions&amp;diff=27121</id>
		<title>Contributions</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=Contributions&amp;diff=27121"/>
		<updated>2022-12-07T17:54:43Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* ScripActions */  add AddWatchersOnCorrespondDomains&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Contributions to RT =&lt;br /&gt;
&lt;br /&gt;
Contains info about software that third parties have contributed to RT. If you know of a contribution or enhancement that is not in this list, please add to the best section possible below, in alphabetical order. Also include notes about the RT versions tested.&lt;br /&gt;
&lt;br /&gt;
You may also want to check the [[Documentation]] page because many articles there describe how to add features that RT does not have by default.&lt;br /&gt;
&lt;br /&gt;
Packages that have their own installer have been moved to [[Extensions]].&lt;br /&gt;
&lt;br /&gt;
See Also the outdated [[Patches]], whose content should be moved to the appropriate subsections below.&lt;br /&gt;
&lt;br /&gt;
== Coping with Spam ==&lt;br /&gt;
&lt;br /&gt;
The techniques for dealing with spam span the divisions below and have been collected in a single place for easy review. Please see [[SpamFiltering]], limiting additions to tricks that differ significantly from the existing material for conciseness.&lt;br /&gt;
&lt;br /&gt;
== ScripConditions ==&lt;br /&gt;
&lt;br /&gt;
Custom [[Condition]]s. Doesn&#039;t matter if it&#039;s module or text to fill into &amp;quot;user defined condition&amp;quot; block in the WebUI, all live here.&lt;br /&gt;
&lt;br /&gt;
*Please*, start wiki page names with &amp;quot;On&amp;quot; prefix if you don&#039;t want to add condition into [[CustomConditionSnippets]].&lt;br /&gt;
&lt;br /&gt;
* [[CustomConditionSnippets]] - &#039;&#039;&#039;very big&#039;&#039;&#039; and organized list of simple conditions&lt;br /&gt;
&lt;br /&gt;
* [[AnyTransactionSource]];&lt;br /&gt;
* [[AnyReminderTransaction]] - a scrip condition which triggers on all reminder transactions;&lt;br /&gt;
* [[MuteResolve]] - let resolver choose not to send email on resolve;&lt;br /&gt;
* [[MuteResolve Redux]] - alternate version of MuteResolve that uses a status code and custom field to avoid some problems;&lt;br /&gt;
* [[NotResolved]] - a scrip condition to detect all ticket that aren&#039;t marked resolved;&lt;br /&gt;
* [[On Correspond Notify AdminCcs if Not Owned|OnCorrespondNotifyAdminCcsNotOwned]] - Notifies AdminCc only if ticket is unowned&lt;br /&gt;
* [[OnCreateAutoReplyException]] - a scrip condition that will send [[AutoReply]] emails to users except for those in the execption list;&lt;br /&gt;
* [[OnCreateCheckCF]] - check presence of a mandatory cf on ticket creation;&lt;br /&gt;
* [[OnCreateFromEmail]] - scrip condition that will send [[AutoReply]] emails to users only when a ticket is opened via email;&lt;br /&gt;
* [[OnCreationOfApprovalTicket]] - a scrip condition that checks if you are creating a new approval ticket&lt;br /&gt;
* [[OnCreatePageOffHours]] - scrip condition that will send email via the [[SendEmailAction]] scrip to and external account. This scrip has an additional condition where it is checking if the request is coming from a specific user or system.&lt;br /&gt;
* [[OnCreateSetUserDetails]] - parse vCards for user information.&lt;br /&gt;
* [[OnCustomFieldValueChange]] - this condition matches when [[CustomField]] value is changed;&lt;br /&gt;
* [[OnMaxPriority]] - check if ticket hits maximal priority (used with priority escalation)&lt;br /&gt;
* [[OnMerge]] - a scrip condition that triggers on ticket merges;&lt;br /&gt;
* [[OnResolveOnce]] - a scrip condition that matches when a ticket is resolved but only if the ticket was not resolved before;&lt;br /&gt;
* [[OnStatusChange]] - using RT&#039;s condition &#039;On Resolve&#039; to trigger other conditions&lt;br /&gt;
* [[OnStealEnhanced]] - small enhancement to the [[OnSteal]] condition in &amp;quot;RT Essentials&amp;quot;.&lt;br /&gt;
* [[OnTimeEstimated]] - when the time Time Estimated fields is set&lt;br /&gt;
* [[OnToOrCC]] - when a request is sent to a particular mailbox&lt;br /&gt;
* [[OnWatcherChange]] - a scrip condition to detect when a ticket watcher is added or deleted.&lt;br /&gt;
* [[OnWebCorrespond]] - determines if a reply is from an incoming email message or from the web interface.&lt;br /&gt;
* [[ReplyBasedUponContent]] - a scrip to reply to an email based upon its content&lt;br /&gt;
* [[ReplyToResolved]] - a scrip condition to detect all ticket that are marked resolved;&lt;br /&gt;
* [[ShowDashboardTabs]] - add dashboards to the personal quickbar&lt;br /&gt;
* [[TicketIDMatches]].pm - a condition which triggers only one ticket by id;&lt;br /&gt;
* [[UntouchedInHours]] - scrip condition that checks if a ticket&#039;s LastUpdate is more than the specified number of hours;&lt;br /&gt;
* [[OnCorrespondOpenUnlessResolved]] - condition to stop RT re-opening resolved tickets when the user replies to them by email;&lt;br /&gt;
* [[NoReplyAddress]] - Use a &amp;quot;no reply&amp;quot; RT email address that posts comments as a Reply/Correspondence (so they can see it in web UI), but does not send email to Requestors&lt;br /&gt;
&lt;br /&gt;
== ScripActions ==&lt;br /&gt;
&lt;br /&gt;
Nice custom [[ScripAction]]s that makes your life easier.&lt;br /&gt;
&lt;br /&gt;
* [[AddAdminCc]] - Add an [[AdminCc]] to tickets for a specific queue, or for queue change.&lt;br /&gt;
* [[AddAdminCcAndChangeQueue]] - Add an [[AdminCc]] to a ticket, and move the ticket to a queue.&lt;br /&gt;
* [[AddSquelchedCc]] - Add group members as Cc to give them access to tickets but without email notifications.&lt;br /&gt;
* [[AddRefersToOnEqualCustomField]] - Create [[RefersTo]] links to tickets with same custom field value&lt;br /&gt;
* [[AddRequestor]] - Allow some accounts to view a ticket without adding by hand some requestors.&lt;br /&gt;
* [[AddWatcherPerTicket]] - Add a watcher to a specific ticket (e.g. when certain conditions are met, such as high urgency)&lt;br /&gt;
* [[AddWatchersOnCorrespond]] - Add Actor &amp;amp;amp; other Cc&#039;d people to ticket on any Correspondence (complement to using [[ParseNewMessageForTicketCcs]] to add people on ticket creation).&lt;br /&gt;
* [[AddWatchersOnCorrespondDomains]] - Modified version of [[AddWatchersOnCorrespond]], But only adds watchers if they are from the same domain as a watcher already on the ticket.&lt;br /&gt;
* [[LoopIn]] - Similar to [[AddWatchersOnCorrespond]], but with some additional security and rules to prevent random people from getting added to existing tickets.&lt;br /&gt;
* [[AutomaticCustomFieldValue]] - set CF value by requestor&#039;s email address.&lt;br /&gt;
* [[AutoCcOwner]] - Add the owner as an [[AdminCc]]&lt;br /&gt;
* [[AutoCcLastOwner]] - When the owner is changed, automatically add the previous owner to the Cc list.&lt;br /&gt;
* [[AutoChangeQueue]] - Change queue if a specific group member take a ticket&lt;br /&gt;
* [[AutoCloseOnNagiosRecoveryMessages]] - Automatically merges and closes a ticket based on the creation of another ticket, in this case, of a nagios generated RECOVERY e-mail&lt;br /&gt;
* [[AutoSetOwner]] - how to automatically set owner on resolution&lt;br /&gt;
* [[AutoSetOwnerIfAdminCc]] - Automatically set ticket owner to an [[AdminCc]]&lt;br /&gt;
* [[AutoSetOwnerFromCC]] - how to automatically set owner from Cc&lt;br /&gt;
* [[BounceMerge]] - Merge a Mail Bounce into the original Ticket&lt;br /&gt;
* [[CcManagers]] - Add the manager subgroup of the ticket creator as Cc, useful when managing departments as groups (as [[Rights|rights]] suggests).&lt;br /&gt;
* [[CopyContentToCF]] - When called &#039;On Create&#039; will copy &amp;lt;tt&amp;gt;$Transaction-&amp;gt;Content&amp;lt;/tt&amp;gt; to the custom field &amp;lt;tt&amp;gt;Problem&amp;lt;/tt&amp;gt;.&lt;br /&gt;
* [[CreatePriorityBasedOnCustomFieldValues]] - Automatically set the Priority based on Ticket Urgency and Impact.&lt;br /&gt;
* [[DefaultCustomFieldValue]] - set default CF value.&lt;br /&gt;
* [[DivideTicketIntoSubtasks]] - auto-creates new tickets for each subtask in a bulleted list&lt;br /&gt;
* [[DueDateinBusinessHours]] - scrip action that sets short-term ticket due dates to coincide with business hours&lt;br /&gt;
* [[EscalateTicketOnAction]] - increment the priority of a ticket whenever a certain action is taken&lt;br /&gt;
* [[ExtractCustomFieldValues]] - set [[CustomField]] with arbitrary data extracted from a ticket using a simple template&lt;br /&gt;
* [[ForkIntoNewTicket]] - a scrip action to copy response/comment ticket to another ticket&lt;br /&gt;
* [[JumpToFrontPageOnTicketResolve]] - Admonish me if you wish, but on resolve of a ticket this scrip will redirect your browser to a new URL of your choosing.&lt;br /&gt;
* [[NotifyNonRecipients]] - notify recipients unless they were already cc&#039;d on the mail&lt;br /&gt;
* [[OnCreateSetDeptHeadCc]] - On create in the case that [[CustomField]].Department = &#039;Foo&#039; then add group &#039;Head Foo&#039; as a Cc&lt;br /&gt;
* [[OnCreateAddGroupCc]] - On create from Requestor email that matches a regex, add members of arbitrary group to CC list while making sure not to add anyone already associated with the ticket&lt;br /&gt;
* [[OnOwnershipSquelchMailtoQueueWatchers]] - When a ticket&#039;s owner changes from &amp;quot;Nobody&amp;quot; to a regular user, stop sending mail to people who are just Queue Watchers.&lt;br /&gt;
* [[OnQueueChangeFixReminders]] - Tickets lose reminders when they are moved between queues. This scrip fixes that&lt;br /&gt;
* [[OnQueueChangeResetPriorityAndDueDate ]] - Reset priorities and due date when moving a ticket to another queue.&lt;br /&gt;
* [[OpenTicketOnAllMemberResolve]]&lt;br /&gt;
* [[OpenDependantsOnResolve]]&lt;br /&gt;
* [[RemoteControlLimeSurvey2]] - new Version of the old [[RemoteControlLimeSurvey]] - Scrip action to trigger [[LimeSurvey]] to add a token to a given survey.&lt;br /&gt;
* [[ResolveTicket]]&lt;br /&gt;
* [[SendEmailAction]] - sends an alert to someone not specified in the ticket&lt;br /&gt;
* [[SendHTMLEmail]] - modification of RT::Action::[[SendEmail]] for sending mails with Content-Type: text/html&lt;br /&gt;
* [[SendNagiosAlert]] - Send an alert to Nagios if a new or open ticket exists in any defined queues.&lt;br /&gt;
* [[SendAlarmPointEvent]] - Send an event to alarm point to invoke SMS/Voice/E-mail alerts&lt;br /&gt;
* [[SetActiveOnCustomerReply]] - detects if a ticket is set to a certain status and changes that status if someone other than the ticket owner replies.&lt;br /&gt;
* [[SetCorresponderAsCC]] - Add anyone who correspondes on a ticket to CC - for the lazy users.&lt;br /&gt;
* [[SetOwnerAndQueueBySubject]] - Set queue and owner when the subject matches a regex&lt;br /&gt;
* [[SetTicketPropertiesViaMail]] - scrip action that allow you to set status, owner and etc via email&lt;br /&gt;
* [[SetTimeWorkedAutomatically]] - scrip action that updates automatically the Time Worked field on the Ticket&lt;br /&gt;
* [[SpamScore2Priority]] - Expose message spam score as priority for review&lt;br /&gt;
&lt;br /&gt;
== Template parts ==&lt;br /&gt;
&lt;br /&gt;
Code that you can put into your mail [[Template]]s, [[Template]] page has also some code snippets.&lt;br /&gt;
&lt;br /&gt;
* [[AddAttachmentLinksToMail]] - adds links on file attachments that ticket has&lt;br /&gt;
* [[AddCustomFieldsValuesToMail]] - puts all [[CustomField]]s values into mail&lt;br /&gt;
* [[AddCustomFieldstoTemplates]] - extracting just one or more [[CustomField]]s, without recalling the entire set&lt;br /&gt;
* [[AddTicketHistoryToMail]] - complex template that adds ticket&#039;s history&lt;br /&gt;
* [[AddQueueNameToMailHeaders]] - add the relevent queue name to mail sent&lt;br /&gt;
* [[AddLastCommentToMail]]&lt;br /&gt;
* [[AddRichTextEditorToCustomField]] - add CKEditor to a [[CustomField]]s textarea values&lt;br /&gt;
* [[AutoreplyOrCorrespondence]] - if creator is not requestor use Corresondence instead of Autoreply&lt;br /&gt;
* [[EmailGroup]] - email an RT [[Group]]&lt;br /&gt;
* [[ForkTemplate]] - send a range of customized responses without hard-coding variants.&lt;br /&gt;
* [[ForwardFirstMessage]] - re-send the first message (i.e. ticket creation message)&lt;br /&gt;
* [[MailingListIntegration]] - Scrip + Template to optionally subscribe requestors to a listserv.&lt;br /&gt;
* [[MultipleOutgoingEmailAddresses]]&lt;br /&gt;
* [[UseActorAsSender]]&lt;br /&gt;
* [[X-Priority]] - Maps RT priority field to email priority header.&lt;br /&gt;
&lt;br /&gt;
== Callbacks ==&lt;br /&gt;
&lt;br /&gt;
Callbacks are an easy way to [[CleanlyCustomizeRT]]&lt;br /&gt;
&lt;br /&gt;
* [[CloningQueues]] - Add user functionality to clone existing queues including templates, scrips, privileges and custom fields during queue creation&lt;br /&gt;
* [[CreateChildTicket]] - Add a button to the Ticket display to create a child ticket in another queue&lt;br /&gt;
* [[HideTransactions]] - hide messages from a history view&lt;br /&gt;
* [[ModifyQuery]] -Change default simple search behavior to in/ex-clude closed tickets, etc.&lt;br /&gt;
* [[MakeClicky:Fedex]] - Make a link to Fedex tracking website whenever phrase looks like a tracking number&lt;br /&gt;
* [[QuickResolveandQuickReject]] - Create two actions in Display page which allow you to reject or resolve the ticket without no comments.&lt;br /&gt;
* [[MailtoLinksFromTransactions]] - Create mailto-inks at the top of transactions&lt;br /&gt;
* [[TwoColumnTicketLayout]] - Display a ticket&#039;s history and metadata side by side&lt;br /&gt;
* [[AutoRequestorTicketSearch]] - Automatically search for requestor&#039;s last updated tickets on creation page&lt;br /&gt;
* [[NewTicketsAlert]] - Display a messagebox on My RT listing new tickets and add new ticket count to page title&lt;br /&gt;
&lt;br /&gt;
== Extensions ==&lt;br /&gt;
&lt;br /&gt;
Patches, [[Overlays]], Mason components, configuration tools and so on. These extensions are unlikely to become RT core package.&lt;br /&gt;
&lt;br /&gt;
Packages that have their own installer have been moved to [[Extensions]] - Below are code bits too small/experimental/etc. to warrant a standalone package:&lt;br /&gt;
&lt;br /&gt;
* [[AutomaticImageResize]] -- automatically scale images that are displayed inline in your ticket history&lt;br /&gt;
* [[AutoRedirectToSelfService]] - automatically redirect your users to Self Service if they don&#039;t have &amp;quot;own ticket&amp;quot; permissions;&lt;br /&gt;
* [[BasicVsAdvancedInterface]] - allow privileged users to choose between the [[SelfService]] and RT at a Glance interfaces&lt;br /&gt;
* [[BetterPerformanceWithFullText]] - tweak to improve full text search in Postgres (and a note about Oracle).&lt;br /&gt;
* [[CalendarWidget]] - add a the dynarch.com jscalendar widget to pick dates&lt;br /&gt;
* [[CannedReplies]] - provides drop-down list of templates that can be included in a ticket reply&lt;br /&gt;
* [[ColorizedLinks]] - colorizing list of Ticket Links depends on it&#039;s status&lt;br /&gt;
* [[ConvertMultiSelectToCheckboxes]] - converts the multiselect customfield into a checkbox interface&lt;br /&gt;
* [[CreateGroupAndAddMembers]] - an overylay that grants [[AdminGroupMembership]] when a user creates a group&lt;br /&gt;
* [[CustomFieldRightsWithoutSeeQueue]] - for a custom ticket creation form that includes the custom fields and you don&#039;t want to turn on the [[SeeQueue]] right;&lt;br /&gt;
* [[DisplayCustomFieldsInTicketSearch]];&lt;br /&gt;
* [[DisplayCustomFieldsInUserPrefs]] - add user-based custom fields to User/Prefs.html&lt;br /&gt;
* [[DisplayCustomFieldsOnTicketUpdatePage]] - Make a ticket&#039;s custom fields visible when updating or resolving a ticket.&lt;br /&gt;
* [[EditCustomFieldsOnUpdate]] - edit custom fields on update, reply, comment...;&lt;br /&gt;
* [[ForwardWithMessage]] - forward a transaction or ticket WITH a message for the recipient&lt;br /&gt;
* [[GroupMembershipCheck]] - snippet that can be included in a custom form if you want to limit the display of some things to a specific group;&lt;br /&gt;
* [[HideTransactions]] - hide messages from a history view&lt;br /&gt;
* [[HomePageSavedSearches]] - display lists of saved searches on the RT home page;&lt;br /&gt;
* [[HTML5Charts]] - eye candy with jqplot;&lt;br /&gt;
* [[ImportCustomFieldValues]] - Fills custom field data from external source;&lt;br /&gt;
* [[LdapSummary]] - Several authentication and user creation techniques&lt;br /&gt;
* [[MaintenanceMode]] - a quick and dirty way to shut down your site temporarily&lt;br /&gt;
* [[MandatorySubject]] - make a ticket&#039;s Subject mandatory using [[JavaScript]]&lt;br /&gt;
* [[MoreAboutPrivilegedUsers]] - show the More About box for privileged users&lt;br /&gt;
* [[MoveRTName]] - Move the $rtname to the end of a subject line&lt;br /&gt;
* [[MultipleSubjectTokens]] - Change subject token dependend on queue name&lt;br /&gt;
* [[PasswordReset]] - show password reset on login&lt;br /&gt;
* [[PersistentSessions]] - Making users&#039; sessions persistent&lt;br /&gt;
* [[PopUpAlert]] - Send Reply instead of Comment&lt;br /&gt;
* [[QuickTicket]] - quickly create a ticket on homepage with custom fields and status&lt;br /&gt;
* [[Extension - Queue Change On Update]] - This is a plugin which adds a callback to RT with the result that you add a Queue change dropdown box to the ticket update page (comment/reply page). Very handy for proper ticket transport between Queue&#039;s, especially whena Queue represents a department.&lt;br /&gt;
* [[Rich Text Custom Fields]] - convert wiki text custom fields into rich text custom fields&lt;br /&gt;
* [[ResolveSendsReply]] - change the &amp;quot;Resolve&amp;quot; link to reply instead of comment by default&lt;br /&gt;
* [[SelectRequestor]] - allow user to select requestor from drop down lists instead of typing email address;&lt;br /&gt;
* [[SelectDefaultQueue]] - Using a user-based custom field, cause all queue name drop down lists to autopick that queue&lt;br /&gt;
* [[SendEmail]] - lets template send an e-mail without adding RT ticket info to subject line&lt;br /&gt;
* [[ShortcutPopupMenuScript]] - Javascript based popup menu of useful actions for ticket list&lt;br /&gt;
* [[SideBySideTicketScreen]] - ticket update screen that shows ticket history &amp;quot;side-by-side&amp;quot; with ticket details.&lt;br /&gt;
* [[SignatureToTheTop]] - Insert user&#039;s signature to the top of the message, not to the bottom as default&lt;br /&gt;
* [[SimpleSearchExcludeResolved]] - exclude resolved and rejected tickets from simple search results&lt;br /&gt;
* [[ShowStatusInColor]] - show status (or priority) in Color in Search screens;&lt;br /&gt;
* [[ShowPerQueueInstructions]]&lt;br /&gt;
* [[SpawnChildTicket]] - spawn a child ticket in a given queue list;&lt;br /&gt;
* [[SpatialRT]] - Plot tickets on a map. More of a recipe than a full solution but could be expanded.&lt;br /&gt;
* [http://wiki.bestpractical.com/view/Spreadsheet+RequestorDetails Spreadsheet+RequestorDetails] - Creates a Spreadsheet link which includes some requestor details if the user has the rights to see them (Global ACL [[AdminUsers]])&lt;br /&gt;
* [[SpreadsheetDisplayedFields]] - download just the displayed search result fields into a spreadsheet;&lt;br /&gt;
* [[StockAnswers]] - insert predefined templates into replies - now with a template editor;&lt;br /&gt;
* [[SuppressOutgoingMail]] - optionally turn off outgoing mail&lt;br /&gt;
* [[TextBasedPriorities]] - use &amp;lt;code&amp;gt;Normal, High, Emergency...&amp;lt;/code&amp;gt; for priority value instead of numbers;&lt;br /&gt;
* [[TicketsPerQueue]] - Display X unowned tickets per queue a user has rights to&lt;br /&gt;
* [[TimeWorked]] - Display a report with total time worked per queue/per user&lt;br /&gt;
* [[TimeWorkedReport]] - Display a report with total time worked per user per ticket for one or more queues.&lt;br /&gt;
* [[TimelineStartDue]] - Modify Timeline package to display tickets using the Start and Due date&lt;br /&gt;
* [[UpdateTimeLeft]] - update Time Left -field from &amp;lt;code&amp;gt;Update.html&amp;lt;/code&amp;gt; (/reply, comment/)&lt;br /&gt;
* [[ViewMyRequests]] - mod to [[SelfService]] that allows requestors to see ticket summaries, and details of their own tickets;&lt;br /&gt;
* [[WhoHasRightsToWhat]] - a mason component that makes it easier to understand your complex rights setup.&lt;br /&gt;
* [[WatcherSummary]] - a mason component that gives you an overview of tickets where a user is listed as a watcher.&lt;br /&gt;
* [[LockLessSessionsMySQL]] - a new version of Sessions which works in no-locking mode&lt;br /&gt;
&lt;br /&gt;
== External utils ==&lt;br /&gt;
&lt;br /&gt;
Various standalone utilities.&lt;br /&gt;
&lt;br /&gt;
* [[backupRT]] - Run a quick backup of RT files (Debian)&lt;br /&gt;
* [[backupRTDB]] - RT Database Backup Script (Debian)&lt;br /&gt;
* [http://nextup.cz/bestpractical-rt-widget/ BestPracticalRT Mac OSX Widget] - simple configurable OSX widget for quick posting tickets&lt;br /&gt;
* [http://search.cpan.org/dist/Bot-BasicBot-Pluggable-Module-RT Bot::BasicBot::Pluggable::Module::RT] - an IRC Bot module that allows full querying of RT tickets from an IRC session. It can do as much as [http://search.cpan.org/dist/RT-Client-REST/ RT::Client::REST] can.&lt;br /&gt;
* [[CleanupSessions]] - Clean up old database sessions&lt;br /&gt;
* [[CloseAll]] - Close all TT in a queue&lt;br /&gt;
* [[ConvertLegacyToRt]] - Converts a Legacy Flat File to RT Tickets.&lt;br /&gt;
* [[delete-transaction]] - [http://download.bestpractical.com/pub/rt/contrib/3.0/Other/Censorware/delete-transaction Original version] was old, poured out errors and didn;t work well on 3.8. This one is fixed.&lt;br /&gt;
* [[DenormalizedViewsForReporting]] - Several views to allow SQL reporting outside of RT.&lt;br /&gt;
* [https://github.com/botsie/dirt Dirt] - A web application that provides kanban boards, scrum taskboards, graphical and tabluar reports on top of RT. &lt;br /&gt;
* Email reminders&lt;br /&gt;
** [[DueDateRemindersByEmail]] - A script (to be run daily) that sends email notifications for expired tickets to owners and Queues/Tickets [[AdminCC]]&lt;br /&gt;
** [http://www.cs.kent.ac.uk/people/staff/tdb/rt3/ rt-remind] - Stick this in your crontab to send out reminders about open tickets.&lt;br /&gt;
** [[rtReminderMails]] - Cronscript that sends mails about reminders that are due in the next two days to the ticket and reminder owners.&lt;br /&gt;
** [[rtUnifiedreminder]] - All the other reminder scripts are based on [[StartDate]], [[DueDate]] or Priority but not all organizations make use of those fields. Also, all the other scripts only send plaintext email to the ticket owner. This script sends one HTML mail (so you can click the tickets and links to RT searches) that lists all tickets that seem to be getting too old without being touched. &amp;quot;Too old&amp;quot; is based on [[LastUpdated]] field, with the amount of time configurable for New, Open and Stalled tickets.&lt;br /&gt;
** [[rt-askForFeedback]] - This Script bases on the above &amp;quot;[[DueDateRemindersByEmail]]&amp;quot; and got modified in the way, that it sends Mails to customers other than to administrators and Ticket-Owner. You will be able to &amp;quot;remind&amp;quot; customer to get back to you with a reply if the ticket is in &amp;quot;stalled&amp;quot;-State. If there is no response within a time of &amp;quot;x&amp;quot;, you can autoclose the ticket.&lt;br /&gt;
* [http://www.bestpractical.com/pub/rt/contrib/3.0/Other/F2Wcvs-to-rt-3.0 F2Wcvs-to-rt] - Tool to help converting from the [http://f2w.sourceforge.net/ F2W] helpdesk system to RT&lt;br /&gt;
* [http://mit.edu/alexmv/Public/graph-mason-deps graph-mason-deps] uses [http://www.graphviz.org/ GraphViz] to create a graph of which components call each other&lt;br /&gt;
* [[html2mime]] - small perl script used to create a text/plain part from 100% html messages&lt;br /&gt;
* [http://pthbb.org/software/manual/mailfilter mailfilter] - spam checking and more&lt;br /&gt;
* [[Mbox2Rt]] - import a unix-style mailbox into RT&lt;br /&gt;
* [[ProcmailRecipes]] - procmail recipes used for email filtering&lt;br /&gt;
* [http://www.dmo.ca/projects/hacks/RT/RT.bm RT.bm] is a plugin for [http://www.mozilla.org/projects/mozbot/ mozbot] that allows some minimal querying of RT tickets from an IRC session.&lt;br /&gt;
* [http://www.bestpractical.com/pub/rt/contrib/3.0/Other/rtadduser rt-adduser] ([http://www.bestpractical.com/pub/rt/contrib/3.0/Other/rtadduser.README docs]) - command line tool to add RT users.&lt;br /&gt;
* [[Rt-auth-user|rt-auth-user]] - perl script for authenticating a user against RT (both local and external sources through [[ExternalAuth]] )&lt;br /&gt;
* [http://mimosaid.007sites.com/rt-batch-add-users.txt rt-batch-add-users] - command line tool to add a batch of RT users based on data of a csv file.&lt;br /&gt;
* [rt-batch-stats [[RT3BatchStats]]] - Command Line or batch statistics.&lt;br /&gt;
* [[RtBounceHandler]] - scan bounce email for ticket details, then post essense of bounce info to that ticket.&lt;br /&gt;
* [http://www.cpan.org/authors/id/A/AH/AHARRISON/scripts/rt-class-map-1.3.pl rt-class-map-1.3-pl] - Show methods available to specific RT objects.&lt;br /&gt;
* [http://www.bestpractical.com/pub/rt/contrib/3.0/rt-cvsgate.txt rt-cvsgate] ([http://www.bestpractical.com/pub/rt/contrib/3.0/rt-cvsgate.README docs]) - cvs integration for request tracker.&lt;br /&gt;
* [http://www.cs.kent.ac.uk/people/staff/tdb/rt3/ rt-escalate] ([[ConfigureEscalation]]) - stick this in your crontab to escalate priority on tickets automatically&lt;br /&gt;
* [[RTLogins]] - simple php script that creates a login report (&amp;quot;Who&#039;s using RT?&amp;quot;)&lt;br /&gt;
* [http://wiki.bestpractical.com/view/rt_logins_email2ldap rt_logins_email2ldap] - script to convert email usernames to LDAP/Active Directory usernames&lt;br /&gt;
* [[rt-google-charts]] - produce google charts for queue statistics&lt;br /&gt;
* [http://www.bestpractical.com/pub/rt/contrib/3.0/Conversion/rt3-on-pg-to-mysql rt-on-pg-to-mysql] - Convert your rt database from postgres to mysql.&lt;br /&gt;
* [http://shellscripts.org/project/rtqueues rt-queues] Shellscript called from procmail to sort E-Mails to the correct queue. Parses incoming mails and based on addresses in To: and CC: fields automatically sorts mails to the correct queue. This makes changing your MTA configuration for every new queue obsolete.&lt;br /&gt;
* [[RtTalkToSelf]] - a mail filter script that allows a single RT instance to have one ticket as the &amp;quot;external requestor&amp;quot; of another.&lt;br /&gt;
* [[scan-and-set]] - sample perl script to all the text attachments of open tickets for a text string and set a custom field with the result.&lt;br /&gt;
* [http://www.jeconley.com/pub/rt/statdump statdump/statcron] - script and cronjob to generate RT management reports. The original URL is defunct, but a patched version of the scripts are still in [http://www.nabble.com/Patched-statdump-statcron-scripts-td2264154.html listarchives]. An updated version that fixed [[AverageTickets]] calcs and CURDATE selection used to be available at http://www.lei.net.au/stats.tgz.&lt;br /&gt;
* [[CountTickets]] - a BASH script to count tickets in a [[MySQL]] db.&lt;br /&gt;
* [[ShredderControl]] - a BASH script to shred tickets using [[RTx]]-Shredder.&lt;br /&gt;
* [[MigrateBugzillaToRT]] - Migrate a Bugzilla instance cleanly to RT&lt;br /&gt;
* [[IntegrateSphinx]] - How to integrate the Sphinx full-text search engine into RT&lt;br /&gt;
* [[SendingCommentsDirectlyToATicketWithExim4]] - How to configure exim4 to send comments to tickets based on ticket ID and custom field values&lt;br /&gt;
* [[rt-clonequeue]] - a perl script used to create a new queue using an existing one as a template (copying its custom fields, permissions, templates and scrips but not its tickets).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Browser Tools ===&lt;br /&gt;
&lt;br /&gt;
* [[AutomaticTextareaAutosave]] - Firefox + Windows only&lt;br /&gt;
* Integrated Browser Search - Add a custom search engine for RT system to your modern browser with [[OpenSearchPluginForRT]]. See also [[SearchRTFromFirefox]].&lt;br /&gt;
* [[RTKeyboardShortcut]] - Firefox + Greasmonky script (can be added to an installation)&lt;br /&gt;
&lt;br /&gt;
== Database Queries ==&lt;br /&gt;
&lt;br /&gt;
* [[QueryResolvedByUser]]&lt;br /&gt;
&lt;br /&gt;
== Other ==&lt;br /&gt;
&lt;br /&gt;
* [http://www.cs.mu.oz.au/systems/rt.html Department of Computer Science and Software Engineering, The University of Melbourne&#039;s RT page]&lt;br /&gt;
* [http://page.mi.fu-berlin.de/~pape/rt3screenshots/ Dirk Pape&#039;s RT page] (Fixed &amp;quot;Fork&amp;quot; tarball link, I hope it is the right Version.)&lt;br /&gt;
* [http://web.mit.edu/tooltime/ MIT IS&amp;amp;amp;T RT page]&lt;br /&gt;
* [http://www.usit.uio.no/it/rt/modifications/ University of Oslo&#039;s RT page]&lt;br /&gt;
* [http://www.cs.kent.ac.uk/people/staff/tdb/rt3/ University of Kent&#039;s RT page]&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27120</id>
		<title>AddWatchersOnCorrespondDomains</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27120"/>
		<updated>2022-12-07T17:50:37Z</updated>

		<summary type="html">&lt;p&gt;Robl: changelog&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= AddWatchersOnCorrespondDomains =&lt;br /&gt;
&lt;br /&gt;
This RT Scrip is a modified version of [[AddWatchersOnCorrespond]] - AddWatchersOnCorrespond simply adds &#039;&#039;&#039;all&#039;&#039;&#039; recipients as watchers.&lt;br /&gt;
&lt;br /&gt;
This Scrip is the same, except it is more restrictive with the requestors added:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;AddWatchersOnCorrespondDomains&amp;lt;/code&amp;gt; will add the person making the correspondence as a [[Watcher]] to the ticket if they are not already a [[Watcher]].&lt;br /&gt;
&lt;br /&gt;
If the transaction originated from an email message, the scrip will scan the email headers and add other recipients to the ticket as [[Watcher]]s, only if:&lt;br /&gt;
&lt;br /&gt;
* They are not already a [[Watcher]], &#039;&#039;&#039;and&#039;&#039;&#039;:&lt;br /&gt;
* Their email address has the &#039;&#039;&#039;same domain&#039;&#039;&#039; (or from a subdomain) as another watcher &#039;&#039;&#039;already&#039;&#039;&#039; on the ticket.&lt;br /&gt;
&lt;br /&gt;
* e.g. New To: and Cc: recipients added by the customer will &#039;&#039;&#039;only&#039;&#039;&#039; be added as watchers if they are from the same domain as a requestor already on the ticket.&lt;br /&gt;
* (Third parties not already on the ticket will not be added.)&lt;br /&gt;
&lt;br /&gt;
* We prefer to add as &#039;&#039;&#039;Requestors&#039;&#039;&#039; rather than Cc to simplify ticket updates. (All &amp;quot;Replies to Requestors&amp;quot; always go to everyone on the ticket.) If you do not want this behaviour, change the line: &amp;lt;code&amp;gt;my $type = &#039;Requestor&#039;;&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;my $type = &#039;Cc&#039;;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* This accommodates the majority of our use cases:&lt;br /&gt;
* 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:&lt;br /&gt;
* After this executes, We also use [https://metacpan.org/pod/RT::Extension::NonWatcherRecipients RT-Extension-NonWatcherRecipients] to add a warning into the Admins email notification of any recipients on the email that are not on the ticket.&lt;br /&gt;
&lt;br /&gt;
* In our RT setup, we have a group named &#039;&#039;&#039;Staff&#039;&#039;&#039; 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 &#039;&#039;&#039;Staff&#039;&#039;&#039; 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.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;NOTE:&#039;&#039;&#039; If you &#039;&#039;&#039;remove&#039;&#039;&#039; 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 -&amp;gt; Modify who receives mail for ticket). They will not receive further email from the ticket. (This is known as a &amp;quot;Squelched&amp;quot; watcher in RT.)&lt;br /&gt;
&lt;br /&gt;
==== Changelog ====&lt;br /&gt;
&lt;br /&gt;
* 2022-12-07: (RobL) Created. Update to &#039;&#039;&#039;always populate&#039;&#039;&#039; Real Name field. See [https://forum.bestpractical.com/t/rt-autocreated-watcher-from-header-causing-bounces/37708/4 this forum post].&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Description:&#039;&#039;&#039; [[AddWatchersOnCorrespondDomains]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Condition:&#039;&#039;&#039; On Correspond&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; User Defined&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Template:&#039;&#039;&#039; Global template: Blank&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Stage:&#039;&#039;&#039; [[Normal]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom condition:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action preparation code:&#039;&#039;&#039; &amp;lt;code&amp;gt;return 1;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action cleanup code:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Scrip to automatically add Cc: from incoming emails to tickets, if the following conditions apply:&lt;br /&gt;
# 1. Sender is sending from a domain that is already a watcher on the ticket (Requestor/Cc/AdminCc)&lt;br /&gt;
# 2. New Cc is from the same domain, or a subdomain.&lt;br /&gt;
#&lt;br /&gt;
# This Scrip is based on AddWatchersOnCorrespond https://rt-wiki.bestpractical.com/wiki/AddWatchersOnCorrespond&lt;br /&gt;
# But does not add just any cc:&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Mary Jane &amp;lt;mjane@emea.isp.com&amp;gt;&lt;br /&gt;
#&lt;br /&gt;
# - If fred@isp.com (or any isp.com or *.isp.com) address is already a watcher on the ticket,&lt;br /&gt;
#   then mjane@emea.isp.com will be automatically added as a Cc watcher.&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Random Helpdesk &amp;lt;helpdesk@bigcolo.net&amp;gt;&lt;br /&gt;
# - helpdesk@bigcolo.net will NOT be added as a Cc: to the ticket if &amp;quot;bigcolo.net&amp;quot; is not already&lt;br /&gt;
#   a watcher on the ticket.&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
my %People;&lt;br /&gt;
&lt;br /&gt;
# Get some info:&lt;br /&gt;
my $scrip = &#039;Scrip:AddWatchersOnCorrespond&#039;;&lt;br /&gt;
my $Transaction = $self-&amp;gt;TransactionObj;&lt;br /&gt;
my $Queue = $self-&amp;gt;TicketObj-&amp;gt;QueueObj;&lt;br /&gt;
my $Ticket = $self-&amp;gt;TicketObj;&lt;br /&gt;
my $Id = $self-&amp;gt;TicketObj-&amp;gt;id;&lt;br /&gt;
&lt;br /&gt;
# Load a list of all domains of people on the ticket already:&lt;br /&gt;
my @TicketDomains = GetTicketDomains();&lt;br /&gt;
&lt;br /&gt;
# Extract a list of people associated with this transaction:&lt;br /&gt;
#  - including the transaction creator, and if it is an email, the sender and recipients of that email&lt;br /&gt;
&lt;br /&gt;
my $CreatorEmailAddr = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;EmailAddress;&lt;br /&gt;
my $CreatorRealName  = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;RealName;&lt;br /&gt;
$People{$CreatorEmailAddr}{RealName} = GetFullName($CreatorEmailAddr,$CreatorRealName);&lt;br /&gt;
&lt;br /&gt;
foreach my $h (qw(From To Cc)) {&lt;br /&gt;
    my $header = $Transaction-&amp;gt;Attachments-&amp;gt;First-&amp;gt;GetHeader($h);&lt;br /&gt;
    my @addr = Mail::Address-&amp;gt;parse($header);&lt;br /&gt;
    foreach my $addrobj (@addr) {&lt;br /&gt;
        my $addr = lc $RT::Nobody-&amp;gt;UserObj-&amp;gt;CanonicalizeEmailAddress($addrobj-&amp;gt;address);&lt;br /&gt;
        # Ignore the specific addresses for this queue:&lt;br /&gt;
        next if lc $Queue-&amp;gt;CorrespondAddress eq $addr;&lt;br /&gt;
        next if lc $Queue-&amp;gt;CommentAddress eq $addr;&lt;br /&gt;
        # Ignore any email address that looks like one for ANY of our queues:&lt;br /&gt;
        next if RT::EmailParser-&amp;gt;IsRTAddress($addr);&lt;br /&gt;
        my $fullname = GetFullName($addr,$addrobj-&amp;gt;phrase);&lt;br /&gt;
        $People{$addr}{RealName} = $fullname;&lt;br /&gt;
        $RT::Logger-&amp;gt;debug(&amp;quot;$scrip: Ticket #$Id correspondence contains header - $h: $addr $fullname&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Lookup the &#039;experts&#039; (staff) group to use below:&lt;br /&gt;
my $Experts = RT::Group-&amp;gt;new($self-&amp;gt;CurrentUser);&lt;br /&gt;
$Experts-&amp;gt;LoadUserDefinedGroup(&#039;Staff&#039;);&lt;br /&gt;
&lt;br /&gt;
# Now check if each user is already watching the ticket or queue:&lt;br /&gt;
&lt;br /&gt;
foreach my $addr (sort { $a &amp;lt;=&amp;gt; $b } keys %People) {&lt;br /&gt;
&lt;br /&gt;
    next if ($addr =~ /^(postmaster|root|Mailer-Daemon)\@/);&lt;br /&gt;
&lt;br /&gt;
    my $User = RT::User-&amp;gt;new( $RT::SystemUser );&lt;br /&gt;
    $User-&amp;gt;LoadOrCreateByEmail(&lt;br /&gt;
        RealName     =&amp;gt; $People{$addr}{RealName},&lt;br /&gt;
        EmailAddress =&amp;gt; $addr,&lt;br /&gt;
        Comments     =&amp;gt; &amp;quot;Autocreated by $scrip&amp;quot;,&lt;br /&gt;
    );&lt;br /&gt;
    &lt;br /&gt;
    my $Name = $User-&amp;gt;Name;&lt;br /&gt;
    my $Principal = $User-&amp;gt;PrincipalId;&lt;br /&gt;
    &lt;br /&gt;
    if ( not ($Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Requestor&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsOwner($User) )) {&lt;br /&gt;
     &lt;br /&gt;
     # check if the new cc: person&#039;s domain matches a domain already on the ticket.&lt;br /&gt;
     # If so, add them as a new watcher:&lt;br /&gt;
     if (is_address_authdomain($addr,@TicketDomains)) {&lt;br /&gt;
        # If the user is a member of the experts group, then add them as an AdminCc, otherwise as a Cc:&lt;br /&gt;
        # my $type = &#039;Cc&#039;;&lt;br /&gt;
        my $type = &#039;Requestor&#039;;&lt;br /&gt;
        $type = &#039;AdminCc&#039; if $Experts-&amp;gt;HasMember($User-&amp;gt;PrincipalObj);&lt;br /&gt;
        # Add the new watcher now and check for errors:&lt;br /&gt;
        my ($ret, $msg) = $Ticket-&amp;gt;AddWatcher(Type  =&amp;gt; $type, PrincipalId =&amp;gt; $Principal);&lt;br /&gt;
        if ($ret) {&lt;br /&gt;
            $RT::Logger-&amp;gt;info(&amp;quot;$scrip: New $type watcher added to ticket #$Id: $addr (#$Principal)&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $RT::Logger-&amp;gt;error(&amp;quot;$scrip: Failed to add new $type watcher to ticket #$Id: $addr (#$Principal) - $msg&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetFullName {&lt;br /&gt;
&lt;br /&gt;
    # Get a nicely formatted name for RT RealName Field:&lt;br /&gt;
&lt;br /&gt;
    # If fullname is blank, make something up from the&lt;br /&gt;
    # local_part of the email address: &amp;quot;fred.bloggs@...&amp;quot; -&amp;gt; &amp;quot;Fred Bloggs&amp;quot;.&lt;br /&gt;
    # Do not allow fullname to contain &amp;quot;@&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
    my ($addr,$fullname) = @_;&lt;br /&gt;
    my ($local_part,$domain) = split(&#039;@&#039;, $addr);&lt;br /&gt;
&lt;br /&gt;
     if (($fullname eq &#039;&#039;) || ($fullname =~ /\@/)) {&lt;br /&gt;
      $fullname = $local_part;&lt;br /&gt;
      $fullname =~ s/[\._-]/ /g;&lt;br /&gt;
      $fullname =~ s/(\w+)/\u$1/g;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     $fullname =~ s/^[\&amp;quot;\&#039;]|[\&amp;quot;\&#039;]$//g; # strip leading/trailing &amp;quot; or &#039;&lt;br /&gt;
     $fullname =~ s/^\s+|\s+$//g; # strip leading/trailing spaces&lt;br /&gt;
&lt;br /&gt;
    return $fullname;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub is_address_authdomain {&lt;br /&gt;
&lt;br /&gt;
        # Is address in an authorised domain?&lt;br /&gt;
        # Also allow subdomain of existing domain&lt;br /&gt;
&lt;br /&gt;
        my $addr = shift;&lt;br /&gt;
        my @domains = @_;&lt;br /&gt;
        $addr = lc($addr);&lt;br /&gt;
&lt;br /&gt;
        my ($undef,$domain) = split(&#039;@&#039;,$addr);&lt;br /&gt;
        my $is_auth = 0;&lt;br /&gt;
&lt;br /&gt;
        foreach my $authdomain (@domains) {&lt;br /&gt;
&lt;br /&gt;
           # if ($domain eq $authdomain)      { $is_auth=1; last; };&lt;br /&gt;
           if ($domain =~ /(^|(\.?))$authdomain$/) { $is_auth=1; last; };&lt;br /&gt;
           if ($authdomain =~ /(^|(\.?))$domain$/) { $is_auth=1; last; };&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return($is_auth);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetTicketDomains {&lt;br /&gt;
&lt;br /&gt;
    # my $self = shift;&lt;br /&gt;
	&lt;br /&gt;
    # Get list of existing email addresses from the ticket, and push all the domains.&lt;br /&gt;
    # We&#039;ll check this later. Any cc: by an existing requestor will be allowed from &lt;br /&gt;
    # the same domain or a subdomain.&lt;br /&gt;
&lt;br /&gt;
    # For each Role, get RoleAddresses: &amp;quot;foo@bar.com, foo@baz.com&amp;quot; &lt;br /&gt;
    # Return a deduped domain list.&lt;br /&gt;
&lt;br /&gt;
    my %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
     foreach my $role (qw(Requestor Cc AdminCc)) {&lt;br /&gt;
&lt;br /&gt;
        my $roleaddresses = lc($self-&amp;gt;TicketObj-&amp;gt;RoleAddresses($role));&lt;br /&gt;
               foreach my $a (split(&#039;, &#039;, $roleaddresses)) {&lt;br /&gt;
                 my (undef,$domain) = split(&#039;@&#039;,$a);&lt;br /&gt;
                 next if ($allticketdomains{$domain});&lt;br /&gt;
                 $allticketdomains{$domain} = $domain;&lt;br /&gt;
                }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      my @ticketdomains = sort keys %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
      return (@ticketdomains);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
return 1;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 # vim:ft=perl:&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespond&amp;diff=27119</id>
		<title>AddWatchersOnCorrespond</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespond&amp;diff=27119"/>
		<updated>2022-12-07T17:47:26Z</updated>

		<summary type="html">&lt;p&gt;Robl: link to AddWatchersOnCorrespondDomains&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= AddWatchersOnCorrespond =&lt;br /&gt;
&lt;br /&gt;
This RT [[Scrip]] will add the person making the correspondence as a [[Watcher]] to the ticket if they are not already a [[Watcher]], also, if the transaction originated from an email message, the script will scan the email headers and add all other recipients to the ticket as [[Watcher]]s (if they are not yet [[Watcher]]s). This can be used to complement the [[ParseNewMessageForTicketCcs]] [[SiteConfig]] option, which is part of the [[EmailInterface]] and does the same thing when tickets are created.&lt;br /&gt;
&lt;br /&gt;
In our RT setup, we have a group named &#039;&#039;&#039;general&#039;&#039;&#039; 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 &#039;&#039;&#039;general&#039;&#039;&#039; group, then the Scrip will add them as an [[AdminCC]] [[Watcher]] instead of a [[CC]] [[Watcher]]. It should be fairly easy for others who do not need this feature to remove or modify this [[Scrip]] accordingly.&lt;br /&gt;
&lt;br /&gt;
I wrote this [[Scrip]] to replace the patch we used to make to the RT Email Interface code, called [[ParseFollowupMessageForTicketCcs]]. Accordingly, it carries the same security warning about allowing basically &#039;&#039;&#039;anyone&#039;&#039;&#039; to add themselves to &#039;&#039;&#039;any&#039;&#039;&#039; ticket, simply by sending an appropriately formatted email. Some RT sites might not want this behaviour, but it is necessary for us.&lt;br /&gt;
&lt;br /&gt;
* [[AddWatchersOnCorrespondDomains]] is a modified version of this Scrip which is more restrictive by domain.&lt;br /&gt;
&lt;br /&gt;
Changelog&lt;br /&gt;
&lt;br /&gt;
2012-01-01: Fixed a bug in which the owner of the ticket would have been added as a Cc: for every reply he made. (HaimDimer)&lt;br /&gt;
&lt;br /&gt;
2022-12-06: Populate RealName from email header when creating new user. (RT inserts the user&#039;s email address in the &amp;quot;phrase&amp;quot; part of the From: header if RealName is empty. This fix prevents emails bouncing because a mail provider&#039;s anti-spoofing policy rejects mail with an email address in the &amp;quot;phrase&amp;quot; section of From:/Cc: headers. )&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Description:&#039;&#039;&#039; [[AddWatchersOnCorrespond]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Condition:&#039;&#039;&#039; On Correspond&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; User Defined&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Template:&#039;&#039;&#039; Global template: Blank&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Stage:&#039;&#039;&#039; [[TransactionBatch]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom condition:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action preparation code:&#039;&#039;&#039; &amp;lt;code&amp;gt;return 1;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action cleanup code:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Get some info:&lt;br /&gt;
my $scrip = &#039;Scrip:AddWatchersOnCorrespond&#039;;&lt;br /&gt;
my $Transaction = $self-&amp;gt;TransactionObj;&lt;br /&gt;
my $EmailAddr = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;EmailAddress;&lt;br /&gt;
my $Queue = $self-&amp;gt;TicketObj-&amp;gt;QueueObj;&lt;br /&gt;
my $Ticket = $self-&amp;gt;TicketObj;&lt;br /&gt;
my $Id = $self-&amp;gt;TicketObj-&amp;gt;id;&lt;br /&gt;
&lt;br /&gt;
# Extract a list of people associated with this transaction:&lt;br /&gt;
#  - including the transaction creator, and if it is an email, the sender and recipients of that email&lt;br /&gt;
my %People;&lt;br /&gt;
my $CreatorEmailAddr = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;EmailAddress;&lt;br /&gt;
my $CreatorRealName  = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;RealName;&lt;br /&gt;
$People{$CreatorEmailAddr}{RealName} = GetFullName($CreatorEmailAddr,$CreatorRealName);&lt;br /&gt;
&lt;br /&gt;
foreach my $h (qw(From To Cc)) {&lt;br /&gt;
    my $header = $Transaction-&amp;gt;Attachments-&amp;gt;First-&amp;gt;GetHeader($h);&lt;br /&gt;
    my @addr = Mail::Address-&amp;gt;parse($header);&lt;br /&gt;
    foreach my $addrobj (@addr) {&lt;br /&gt;
        my $addr = lc $RT::Nobody-&amp;gt;UserObj-&amp;gt;CanonicalizeEmailAddress($addrobj-&amp;gt;address);&lt;br /&gt;
        # Ignore the specific addresses for this queue:&lt;br /&gt;
        next if lc $Queue-&amp;gt;CorrespondAddress eq $addr;&lt;br /&gt;
        next if lc $Queue-&amp;gt;CommentAddress eq $addr;&lt;br /&gt;
        # Ignore any email address that looks like one for ANY of our queues:&lt;br /&gt;
        next if RT::EmailParser-&amp;gt;IsRTAddress($addr);&lt;br /&gt;
 &lt;br /&gt;
        # Get phrase from address header for full name: &amp;quot;Fred Bloggs&amp;quot; &amp;lt;fred@example.com&amp;gt; &lt;br /&gt;
        # (Extract Fred Bloggs to fullname.) Clean up. If nothing suitable then make something from email.&lt;br /&gt;
        my $fullname = GetFullName($addr,$addrobj-&amp;gt;phrase);&lt;br /&gt;
        $People{$addr}{RealName} = $fullname;&lt;br /&gt;
        $RT::Logger-&amp;gt;debug(&amp;quot;$scrip: Ticket #$Id correspondence contains header - $h: $addr $fullname&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Lookup the &#039;experts&#039; (general) group to use below:&lt;br /&gt;
my $Experts = RT::Group-&amp;gt;new($self-&amp;gt;CurrentUser);&lt;br /&gt;
$Experts-&amp;gt;LoadUserDefinedGroup(&#039;general&#039;);&lt;br /&gt;
&lt;br /&gt;
# Now check if each user is already watching the ticket or queue:&lt;br /&gt;
foreach my $addr (sort keys %People) {&lt;br /&gt;
&lt;br /&gt;
    next if ($addr =~ /^(postmaster|root|Mailer-Daemon)\@/);&lt;br /&gt;
&lt;br /&gt;
    my $User = RT::User-&amp;gt;new( $RT::SystemUser );&lt;br /&gt;
    $User-&amp;gt;LoadOrCreateByEmail(&lt;br /&gt;
        RealName     =&amp;gt; $People{$addr}{RealName},&lt;br /&gt;
        EmailAddress =&amp;gt; $addr,&lt;br /&gt;
        Comments     =&amp;gt; &amp;quot;Autocreated by $scrip&amp;quot;,&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    my $Name = $User-&amp;gt;Name;&lt;br /&gt;
    my $Principal = $User-&amp;gt;PrincipalId;&lt;br /&gt;
    if ( not ($Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Requestor&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsOwner($User) )) {&lt;br /&gt;
        # If the user is a member of the experts group, then add them as an AdminCc, otherwise as a Cc:&lt;br /&gt;
        my $type = &#039;Cc&#039;;&lt;br /&gt;
        $type = &#039;AdminCc&#039; if $Experts-&amp;gt;HasMember($User-&amp;gt;PrincipalObj);&lt;br /&gt;
        # Add the new watcher now and check for errors:&lt;br /&gt;
        my ($ret, $msg) = $Ticket-&amp;gt;AddWatcher(Type  =&amp;gt; $type, PrincipalId =&amp;gt; $Principal);&lt;br /&gt;
        if ($ret) {&lt;br /&gt;
            $RT::Logger-&amp;gt;info(&amp;quot;$scrip: New $type watcher added to ticket #$Id: $addr $fullname (#$Principal)&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $RT::Logger-&amp;gt;error(&amp;quot;$scrip: Failed to add new $type watcher to ticket #$Id: $addr (#$Principal) - $msg&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetFullName {&lt;br /&gt;
&lt;br /&gt;
    # Get a nicely formatted name for RT RealName Field:&lt;br /&gt;
&lt;br /&gt;
    # If fullname is blank, make something up from the&lt;br /&gt;
    # local_part of the email address: &amp;quot;fred.bloggs@...&amp;quot; -&amp;gt; &amp;quot;Fred Bloggs&amp;quot;.&lt;br /&gt;
    # Do not allow fullname to contain &amp;quot;@&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
    my ($addr,$fullname) = @_;&lt;br /&gt;
    my ($local_part,$domain) = split(&#039;@&#039;, $addr);&lt;br /&gt;
&lt;br /&gt;
     if (($fullname eq &#039;&#039;) || ($fullname =~ /\@/)) {&lt;br /&gt;
      $fullname = $local_part;&lt;br /&gt;
      $fullname =~ s/[\._-]/ /g;&lt;br /&gt;
      $fullname =~ s/(\w+)/\u$1/g;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     $fullname =~ s/^[\&amp;quot;\&#039;]|[\&amp;quot;\&#039;]$//g; # strip leading/trailing &amp;quot; or &#039;&lt;br /&gt;
     $fullname =~ s/^\s+|\s+$//g; # strip leading/trailing spaces&lt;br /&gt;
&lt;br /&gt;
    return $fullname;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
return 1;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 # vim:ft=perl:&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
	<entry>
		<id>https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27118</id>
		<title>AddWatchersOnCorrespondDomains</title>
		<link rel="alternate" type="text/html" href="https://rt-wiki.bestpractical.com/index.php?title=AddWatchersOnCorrespondDomains&amp;diff=27118"/>
		<updated>2022-12-07T17:46:10Z</updated>

		<summary type="html">&lt;p&gt;Robl: /* AddWatchersOnCorrespondDomains */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= AddWatchersOnCorrespondDomains =&lt;br /&gt;
&lt;br /&gt;
This RT Scrip is a modified version of [[AddWatchersOnCorrespond]] - AddWatchersOnCorrespond simply adds &#039;&#039;&#039;all&#039;&#039;&#039; recipients as watchers.&lt;br /&gt;
&lt;br /&gt;
This Scrip is the same, except it is more restrictive with the requestors added:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;AddWatchersOnCorrespondDomains&amp;lt;/code&amp;gt; will add the person making the correspondence as a [[Watcher]] to the ticket if they are not already a [[Watcher]].&lt;br /&gt;
&lt;br /&gt;
If the transaction originated from an email message, the scrip will scan the email headers and add other recipients to the ticket as [[Watcher]]s, only if:&lt;br /&gt;
&lt;br /&gt;
* They are not already a [[Watcher]], &#039;&#039;&#039;and&#039;&#039;&#039;:&lt;br /&gt;
* Their email address has the &#039;&#039;&#039;same domain&#039;&#039;&#039; (or from a subdomain) as another watcher &#039;&#039;&#039;already&#039;&#039;&#039; on the ticket.&lt;br /&gt;
&lt;br /&gt;
* e.g. New To: and Cc: recipients added by the customer will &#039;&#039;&#039;only&#039;&#039;&#039; be added as watchers if they are from the same domain as a requestor already on the ticket.&lt;br /&gt;
* (Third parties not already on the ticket will not be added.)&lt;br /&gt;
&lt;br /&gt;
* We prefer to add as &#039;&#039;&#039;Requestors&#039;&#039;&#039; rather than Cc to simplify ticket updates. (All &amp;quot;Replies to Requestors&amp;quot; always go to everyone on the ticket.) If you do not want this behaviour, change the line: &amp;lt;code&amp;gt;my $type = &#039;Requestor&#039;;&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;my $type = &#039;Cc&#039;;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* This accommodates the majority of our use cases:&lt;br /&gt;
* 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:&lt;br /&gt;
* After this executes, We also use [https://metacpan.org/pod/RT::Extension::NonWatcherRecipients RT-Extension-NonWatcherRecipients] to add a warning into the Admins email notification of any recipients on the email that are not on the ticket.&lt;br /&gt;
&lt;br /&gt;
* In our RT setup, we have a group named &#039;&#039;&#039;Staff&#039;&#039;&#039; 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 &#039;&#039;&#039;Staff&#039;&#039;&#039; 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.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;NOTE:&#039;&#039;&#039; If you &#039;&#039;&#039;remove&#039;&#039;&#039; 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 -&amp;gt; Modify who receives mail for ticket). They will not receive further email from the ticket. (This is known as a &amp;quot;Squelched&amp;quot; watcher in RT.)&lt;br /&gt;
&lt;br /&gt;
==== Changelog ====&lt;br /&gt;
&lt;br /&gt;
2022-12-07: Created. Update to &#039;&#039;&#039;always populate&#039;&#039;&#039; Real Name field. See [https://forum.bestpractical.com/t/rt-autocreated-watcher-from-header-causing-bounces/37708/4 this forum post].&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Description:&#039;&#039;&#039; [[AddWatchersOnCorrespondDomains]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Condition:&#039;&#039;&#039; On Correspond&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; User Defined&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Template:&#039;&#039;&#039; Global template: Blank&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Stage:&#039;&#039;&#039; [[Normal]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom condition:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action preparation code:&#039;&#039;&#039; &amp;lt;code&amp;gt;return 1;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Custom action cleanup code:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# &amp;lt;rob@lonap.net&amp;gt;&lt;br /&gt;
# Scrip to automatically add Cc: from incoming emails to tickets, if the following conditions apply:&lt;br /&gt;
# 1. Sender is sending from a domain that is already a watcher on the ticket (Requestor/Cc/AdminCc)&lt;br /&gt;
# 2. New Cc is from the same domain, or a subdomain.&lt;br /&gt;
#&lt;br /&gt;
# This Scrip is based on AddWatchersOnCorrespond https://rt-wiki.bestpractical.com/wiki/AddWatchersOnCorrespond&lt;br /&gt;
# But does not add just any cc:&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Mary Jane &amp;lt;mjane@emea.isp.com&amp;gt;&lt;br /&gt;
#&lt;br /&gt;
# - If fred@isp.com (or any isp.com or *.isp.com) address is already a watcher on the ticket,&lt;br /&gt;
#   then mjane@emea.isp.com will be automatically added as a Cc watcher.&lt;br /&gt;
#&lt;br /&gt;
# Example: From: Fred Bloggs &amp;lt;fred@isp.com&amp;gt;&lt;br /&gt;
#          Cc:   Random Helpdesk &amp;lt;helpdesk@bigcolo.net&amp;gt;&lt;br /&gt;
# - helpdesk@bigcolo.net will NOT be added as a Cc: to the ticket if &amp;quot;bigcolo.net&amp;quot; is not already&lt;br /&gt;
#   a watcher on the ticket.&lt;br /&gt;
#&lt;br /&gt;
&lt;br /&gt;
my %People;&lt;br /&gt;
&lt;br /&gt;
# Get some info:&lt;br /&gt;
my $scrip = &#039;Scrip:AddWatchersOnCorrespond&#039;;&lt;br /&gt;
my $Transaction = $self-&amp;gt;TransactionObj;&lt;br /&gt;
my $Queue = $self-&amp;gt;TicketObj-&amp;gt;QueueObj;&lt;br /&gt;
my $Ticket = $self-&amp;gt;TicketObj;&lt;br /&gt;
my $Id = $self-&amp;gt;TicketObj-&amp;gt;id;&lt;br /&gt;
&lt;br /&gt;
# Load a list of all domains of people on the ticket already:&lt;br /&gt;
my @TicketDomains = GetTicketDomains();&lt;br /&gt;
&lt;br /&gt;
# Extract a list of people associated with this transaction:&lt;br /&gt;
#  - including the transaction creator, and if it is an email, the sender and recipients of that email&lt;br /&gt;
&lt;br /&gt;
my $CreatorEmailAddr = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;EmailAddress;&lt;br /&gt;
my $CreatorRealName  = $self-&amp;gt;TransactionObj-&amp;gt;CreatorObj-&amp;gt;RealName;&lt;br /&gt;
$People{$CreatorEmailAddr}{RealName} = GetFullName($CreatorEmailAddr,$CreatorRealName);&lt;br /&gt;
&lt;br /&gt;
foreach my $h (qw(From To Cc)) {&lt;br /&gt;
    my $header = $Transaction-&amp;gt;Attachments-&amp;gt;First-&amp;gt;GetHeader($h);&lt;br /&gt;
    my @addr = Mail::Address-&amp;gt;parse($header);&lt;br /&gt;
    foreach my $addrobj (@addr) {&lt;br /&gt;
        my $addr = lc $RT::Nobody-&amp;gt;UserObj-&amp;gt;CanonicalizeEmailAddress($addrobj-&amp;gt;address);&lt;br /&gt;
        # Ignore the specific addresses for this queue:&lt;br /&gt;
        next if lc $Queue-&amp;gt;CorrespondAddress eq $addr;&lt;br /&gt;
        next if lc $Queue-&amp;gt;CommentAddress eq $addr;&lt;br /&gt;
        # Ignore any email address that looks like one for ANY of our queues:&lt;br /&gt;
        next if RT::EmailParser-&amp;gt;IsRTAddress($addr);&lt;br /&gt;
        my $fullname = GetFullName($addr,$addrobj-&amp;gt;phrase);&lt;br /&gt;
        $People{$addr}{RealName} = $fullname;&lt;br /&gt;
        $RT::Logger-&amp;gt;debug(&amp;quot;$scrip: Ticket #$Id correspondence contains header - $h: $addr $fullname&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Lookup the &#039;experts&#039; (staff) group to use below:&lt;br /&gt;
my $Experts = RT::Group-&amp;gt;new($self-&amp;gt;CurrentUser);&lt;br /&gt;
$Experts-&amp;gt;LoadUserDefinedGroup(&#039;Staff&#039;);&lt;br /&gt;
&lt;br /&gt;
# Now check if each user is already watching the ticket or queue:&lt;br /&gt;
&lt;br /&gt;
foreach my $addr (sort { $a &amp;lt;=&amp;gt; $b } keys %People) {&lt;br /&gt;
&lt;br /&gt;
    next if ($addr =~ /^(postmaster|root|Mailer-Daemon)\@/);&lt;br /&gt;
&lt;br /&gt;
    my $User = RT::User-&amp;gt;new( $RT::SystemUser );&lt;br /&gt;
    $User-&amp;gt;LoadOrCreateByEmail(&lt;br /&gt;
        RealName     =&amp;gt; $People{$addr}{RealName},&lt;br /&gt;
        EmailAddress =&amp;gt; $addr,&lt;br /&gt;
        Comments     =&amp;gt; &amp;quot;Autocreated by $scrip&amp;quot;,&lt;br /&gt;
    );&lt;br /&gt;
    &lt;br /&gt;
    my $Name = $User-&amp;gt;Name;&lt;br /&gt;
    my $Principal = $User-&amp;gt;PrincipalId;&lt;br /&gt;
    &lt;br /&gt;
    if ( not ($Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Queue-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Cc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;AdminCc&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsWatcher(Type =&amp;gt; &#039;Requestor&#039;, PrincipalId =&amp;gt; $Principal) or&lt;br /&gt;
            $Ticket-&amp;gt;IsOwner($User) )) {&lt;br /&gt;
     &lt;br /&gt;
     # check if the new cc: person&#039;s domain matches a domain already on the ticket.&lt;br /&gt;
     # If so, add them as a new watcher:&lt;br /&gt;
     if (is_address_authdomain($addr,@TicketDomains)) {&lt;br /&gt;
        # If the user is a member of the experts group, then add them as an AdminCc, otherwise as a Cc:&lt;br /&gt;
        # my $type = &#039;Cc&#039;;&lt;br /&gt;
        my $type = &#039;Requestor&#039;;&lt;br /&gt;
        $type = &#039;AdminCc&#039; if $Experts-&amp;gt;HasMember($User-&amp;gt;PrincipalObj);&lt;br /&gt;
        # Add the new watcher now and check for errors:&lt;br /&gt;
        my ($ret, $msg) = $Ticket-&amp;gt;AddWatcher(Type  =&amp;gt; $type, PrincipalId =&amp;gt; $Principal);&lt;br /&gt;
        if ($ret) {&lt;br /&gt;
            $RT::Logger-&amp;gt;info(&amp;quot;$scrip: New $type watcher added to ticket #$Id: $addr (#$Principal)&amp;quot;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $RT::Logger-&amp;gt;error(&amp;quot;$scrip: Failed to add new $type watcher to ticket #$Id: $addr (#$Principal) - $msg&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetFullName {&lt;br /&gt;
&lt;br /&gt;
    # Get a nicely formatted name for RT RealName Field:&lt;br /&gt;
&lt;br /&gt;
    # If fullname is blank, make something up from the&lt;br /&gt;
    # local_part of the email address: &amp;quot;fred.bloggs@...&amp;quot; -&amp;gt; &amp;quot;Fred Bloggs&amp;quot;.&lt;br /&gt;
    # Do not allow fullname to contain &amp;quot;@&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
    my ($addr,$fullname) = @_;&lt;br /&gt;
    my ($local_part,$domain) = split(&#039;@&#039;, $addr);&lt;br /&gt;
&lt;br /&gt;
     if (($fullname eq &#039;&#039;) || ($fullname =~ /\@/)) {&lt;br /&gt;
      $fullname = $local_part;&lt;br /&gt;
      $fullname =~ s/[\._-]/ /g;&lt;br /&gt;
      $fullname =~ s/(\w+)/\u$1/g;&lt;br /&gt;
     }&lt;br /&gt;
&lt;br /&gt;
     $fullname =~ s/^[\&amp;quot;\&#039;]|[\&amp;quot;\&#039;]$//g; # strip leading/trailing &amp;quot; or &#039;&lt;br /&gt;
     $fullname =~ s/^\s+|\s+$//g; # strip leading/trailing spaces&lt;br /&gt;
&lt;br /&gt;
    return $fullname;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub is_address_authdomain {&lt;br /&gt;
&lt;br /&gt;
        # Is address in an authorised domain?&lt;br /&gt;
        # Also allow subdomain of existing domain&lt;br /&gt;
&lt;br /&gt;
        my $addr = shift;&lt;br /&gt;
        my @domains = @_;&lt;br /&gt;
        $addr = lc($addr);&lt;br /&gt;
&lt;br /&gt;
        my ($undef,$domain) = split(&#039;@&#039;,$addr);&lt;br /&gt;
        my $is_auth = 0;&lt;br /&gt;
&lt;br /&gt;
        foreach my $authdomain (@domains) {&lt;br /&gt;
&lt;br /&gt;
           # if ($domain eq $authdomain)      { $is_auth=1; last; };&lt;br /&gt;
           if ($domain =~ /(^|(\.?))$authdomain$/) { $is_auth=1; last; };&lt;br /&gt;
           if ($authdomain =~ /(^|(\.?))$domain$/) { $is_auth=1; last; };&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return($is_auth);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
sub GetTicketDomains {&lt;br /&gt;
&lt;br /&gt;
    # my $self = shift;&lt;br /&gt;
	&lt;br /&gt;
    # Get list of existing email addresses from the ticket, and push all the domains.&lt;br /&gt;
    # We&#039;ll check this later. Any cc: by an existing requestor will be allowed from &lt;br /&gt;
    # the same domain or a subdomain.&lt;br /&gt;
&lt;br /&gt;
    # For each Role, get RoleAddresses: &amp;quot;foo@bar.com, foo@baz.com&amp;quot; &lt;br /&gt;
    # Return a deduped domain list.&lt;br /&gt;
&lt;br /&gt;
    my %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
     foreach my $role (qw(Requestor Cc AdminCc)) {&lt;br /&gt;
&lt;br /&gt;
        my $roleaddresses = lc($self-&amp;gt;TicketObj-&amp;gt;RoleAddresses($role));&lt;br /&gt;
               foreach my $a (split(&#039;, &#039;, $roleaddresses)) {&lt;br /&gt;
                 my (undef,$domain) = split(&#039;@&#039;,$a);&lt;br /&gt;
                 next if ($allticketdomains{$domain});&lt;br /&gt;
                 $allticketdomains{$domain} = $domain;&lt;br /&gt;
                }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      my @ticketdomains = sort keys %allticketdomains;&lt;br /&gt;
&lt;br /&gt;
      return (@ticketdomains);&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
return 1;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 # vim:ft=perl:&lt;br /&gt;
&lt;br /&gt;
----&lt;/div&gt;</summary>
		<author><name>Robl</name></author>
	</entry>
</feed>