Skip to content

Email Template Sender Rules

Ed Mozley edited this page Aug 19, 2026 · 3 revisions

Limiting automatic replies to particular senders

An email template can be scoped to an address, a domain, or several of either β€” so the auto-reply your team sees is not the one a stranger gets.

Set at Tickets β†’ Settings β†’ Email Templates, on each template.

Asked for in discussion #80, released as update #1126.


1. The problem this had to solve first

Scoping templates is easy. Scoping them without creating a trap is the whole job.

The obvious design is a rule list evaluated top to bottom, first match wins β€” how firewalls work, and how most people expect it to. It has a failure mode that fits this feature exactly:

Set up two templates, one for @a.com and one for @b.com. Both correct, both tested, everybody happy. Twelve months later c.com is onboarded by somebody who has never seen this screen. They match nothing. They get silence. Nothing is broken, nothing is logged, and there is no error to search for β€” just a customer who says they never heard back.

And separately: it asks an administrator to get two things right, the rules and the ordering, when only one of them is visible in what they are configuring.


2. The most specific template wins

There is no ordering to get wrong, because ordering does not decide anything:

Level Example Beats
An exact address alerts@a.com everything below
A domain a.com Everyone
Everyone (no rules) β€” β€”

So a template naming alerts@a.com wins for that sender even when a template for a.com exists, and both win over the general one. Dragging templates up and down cannot change which one is sent. display_order survives only as a tie-break between two templates of identical specificity, which the screen flags rather than resolving quietly.

This is also the rule you would guess. A template written for one customer's domain obviously ought to beat the generic one, and nobody expects to have to say so.

No rules means everyone, not nobody

The empty case is the permissive one, deliberately. A template nobody has restricted keeps behaving exactly as it did before this feature existed, and a new template starts unrestricted β€” so narrowing is always a deliberate act, and an installation has a catch-all unless somebody removes it on purpose.

Inverting that would make "forgot to add a rule" mean silence, which is the failure this design is built to avoid.


3. Something worth knowing about how it used to work

Before this, the template picker was:

SELECT * FROM ticket_email_templates
 WHERE event_trigger = ? AND is_active = 1
 ORDER BY display_order ASC, id ASC
 LIMIT 1

Exactly one template per event was ever used β€” whichever sorted first β€” and any others were silently ignored. If you have ever added a second template for New ticket from email and wondered why nothing changed, that is why. So ordering was already deciding everything; it just looked like a display preference.


4. Three things guard the gap

Restricting templates makes it possible for a sender to match nothing. Each of these catches that at a different moment, and the third is the one that matters most.

Check what a sender would get

Type an address, pick an event, and FreeITSM names the template that would be sent and why:

someone@a.com     ->  "Customer reply" would be sent, because a rule
                      names the domain @a.com.

someone@c.com     ->  No reply would be sent. This address matches none
                      of the templates for that event, and none of them
                      applies to everyone.

It calls the same function that chooses the template for real (templateSelectForRecipient()), rather than a second implementation of the matching in the browser. A simulator that can disagree with reality is worse than none.

A warning when there is no catch-all

If every active template for an event is restricted, the screen says so and names the events affected. It can be dismissed, because it is not always wrong: an installation that deliberately only auto-replies to known customers is doing this on purpose, and a warning that cannot be silenced is one people stop reading.

The warning is suppressed entirely if the screen could not load its settings β€” an administrator who has already acknowledged it should not be nagged again because a request failed.

The one that works when nobody is looking πŸ”‘

Both of the above require somebody to be on the settings screen. In the twelve-months-later scenario nobody is β€” that is the entire premise.

So when an automatic email is not sent because no template matched, that is written down at the moment it happens, against the ticket and in the mailbox's outbound log:

Time To Route Result
09:14 someone@c.com Ticket template Not sent

No email template applies to this sender. Every template for this event is limited to particular senders, and none of them covers this one.

The question is then answerable by anybody looking at the ticket, without knowing this feature exists.

The outbound log had two outcomes before this β€” sent and failed β€” and rendered anything that was not a failure as Sent. A deliberate non-send would have been labelled Sent, which is worse than saying nothing: it puts a confident wrong answer in the one place somebody goes looking. It is now a third state.


5. Writing the rules

Type either form into the box; the @ decides which it is.

You type Stored as Matches
someone@a.com address that one sender
a.com domain anyone at a.com
@a.com domain the same β€” the @ is stripped

Case and surrounding spaces do not matter; everything is folded to lower case on the way in. Duplicates are dropped rather than stored twice.

A domain with no dot in it, or an address that is not one, is refused at save time rather than stored. A rule that can never match would look exactly like a working rule on the screen, and quietly do nothing β€” which is the fault this whole feature is trying not to have.


6. πŸ“ For developers

File Role
includes/template_email.php templateSelectForRecipient() β€” the matcher, and the only place specificity is decided. getActiveTemplate() remains as a thin wrapper
includes/email_log.php emailLogSkipped() β€” the deliberate non-send, status = 'skipped'
api/tickets/simulate_email_template.php The simulator. Calls the matcher; implements nothing itself
api/tickets/save_email_template.php Validates every rule before the template is written β€” there is no transaction, so a rule rejected afterwards would leave the template saved while the caller is told the save failed
api/tickets/delete_email_template.php Removes the rules too. There is no foreign key, and an AUTO_INCREMENT id can be reissued after a restart
ticket_email_template_rules template_id, match_type (address|domain), match_value

Two behaviours are load-bearing and easy to undo by accident:

  • An absent rules key means "leave them alone"; an empty array means "everyone". Collapsing the two would let any client that posts no rules silently unrestrict every template it saves.
  • A missing ticket_email_template_rules table yields no rules, which means every template applies to everyone β€” i.e. a part-upgraded install behaves exactly as it did before the feature, rather than falling silent.

sendTemplateEmail() now builds the merge data before choosing the template, because the choice depends on the recipient. That order used to be the other way round.


Related pages

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally