Ticket Field and Email Hooks
The nine hooks over custom fields and tickets arriving by email.
Overview
Two subjects meet here: the custom fields an operator defines, and the handling of tickets arriving by email.
The email gates stand apart from other gates: your block raises no error and is skipped silently. It stops the unwanted message but leaves no trace, so you must record the reason yourself.
Reference
Stopping a custom field being saved
Runs before a ticket custom field is saved.
Hook::add('gate:ticket.custom_field.save', 10, function ($id, $did, $type) {
// A zero department means it shows everywhere.
if ($type === 'password' && $did === 0)
return 'A password field cannot be opened to every department.';
return null;
});Following a custom field being saved
Runs after a custom field is saved.
Hook::add('action:ticket.custom_field.saved', 10, function ($field, $isNew) {
// Name and description live in the language record, not here.
Acme::syncFieldSchema((int) ($field['id'] ?? 0));
});Stopping a custom field deletion
Runs before custom fields are deleted.
Hook::add('gate:ticket.custom_field.delete', 10, function ($id) {
// It is an array even for a single deletion.
foreach ($id as $one)
if (Acme::fieldHasData((int) $one)) return 'A field holding data cannot be deleted.';
return null;
});Following a custom field being deleted
Runs after a custom field is deleted.
Hook::add('action:ticket.custom_field.deleted', 10, function ($i, $flang) {
// If you need the name, the language record is the only source.
Acme::dropFieldSchema($i, $flang['name'] ?? '');
});Changing the custom field list
Runs after the custom fields are read. Add a field of your own to the list here.
Hook::add('filter:ticket.custom_fields', 10, function (&$fields, &$ctx) {
// A zero department means every department is being asked for.
$fields = array_values(array_filter($fields,
fn ($f) => Acme::fieldVisible((int) ($f['id'] ?? 0))));
});Changing the group fields
Runs after the custom fields tied to an access group are read.
Hook::add('filter:ticket.access_group_fields', 10, function (&$fields, &$ctx) {
// The language never arrives empty.
$fields = Acme::orderFields($fields, $ctx['lang']);
});Skipping an incoming email
Runs before an incoming email becomes a ticket. This is where you keep unwanted mail out of the system entirely.
Hook::add('gate:ticket.pipe_import', 10, function ($mail, $did, $msgId) {
// The veto is SILENT: record the reason yourself.
if (Acme::isAutoReply($mail)) {
Acme::log('auto-reply skipped', $msgId);
return 'auto-reply';
}
return null;
});Stopping a ticket opening from email
Runs immediately before an incoming email becomes a ticket. The message has passed the mail gate and its department is resolved.
Hook::add('gate:ticket.open', 10,
function ($subject, $message, $departmentId, $clientId, $clientEmail, $mail) {
// For an unrecognised sender the customer id is ZERO.
if (!$clientId && Acme::strangersBlocked()) return 'unrecognised sender';
return null;
});Cleaning the text from an email
Runs before the body and subject of an incoming email are written to a ticket. Trimming quoted blocks and signatures belongs here.
Hook::add('filter:ticket.pipe_message_text', 10, function (&$message, &$subject, $mail) {
// Both are passed by link.
$message = Acme::stripQuotedReply($message);
});Pitfalls
When other gates block, an error is thrown and the user sees the reason. The email gates do not work that way: your block is recorded as skipped, the job is not counted as failed and nobody sees anything. It is the right place to stop unwanted mail, but without recording the reason yourself no trace remains.
The custom field save event gives the structural row: id, department, status, rank. The name and description are not there; they sit in the language record. A listener looking for the name works with an empty value.
Related Articles
- Support Ticket Hooks
- Customer Account Hooks
- How Hooks Work
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.