An automation variable is what separates a generic "Your ticket was updated" from "Ticket 12345 - Printer out of toner moved to Processing, handled by technician Ana Paula". In GLPI these variables are the notification tags: ##...## markers the engine swaps for real data at send time. They look trivial until the day the requester's name comes out blank in the email and nobody knows why. This guide is the map we use when sustaining client GLPI estates to get the template right the first time.
Where variables live: a template is not a notification
Before picking tags, understand the chain GLPI assembles, because most "the variable does not work" cases are configuration errors, not syntax. A message only goes out when four pieces line up:
- Notification (Setup > Notifications): binds the event - new ticket, followup, solution, satisfaction survey.
- Template (Notification templates): the plain-text body and the HTML body, where you write the
##...##tags. - Recipients: who gets it - requester, assigned technician, group, watcher.
- Queue and delivery: GLPI does not send the email on the spot; it queues it and the
queuednotificationautomatic action dispatches it.
Putting the right tag in a template not bound to any active notification produces no email at all - and that is not the tag's fault.
The tags you actually use
Tags are contextual: the available set changes with the item (Ticket, Change, Problem). For tickets, these cover most templates - all checked straight against the GLPI 11 notification engine:
| Tag | What it returns |
|---|---|
##ticket.id## | Ticket number |
##ticket.title## | Title |
##ticket.description## | Description (opening content) |
##ticket.url## | Direct link to the ticket (login required) |
##ticket.status## | Current status |
##ticket.priority## | Priority |
##ticket.category## | Category |
##ticket.creationdate## | Opening date |
##ticket.solvedate## | Solution date |
##ticket.duedate## | Resolution deadline (SLA) |
##ticket.authors## | Requester(s) - comma-separated list |
##ticket.assigntousers## | Assigned technician(s) - comma-separated list |
##ticket.assigntogroups## | Assigned group(s) |
##ticket.solution## | Solution text |
##followup.author## / ##followup.description## | Inside ##FOREACHfollowups## |
##task.author## / ##task.description## | Inside ##FOREACHtasks## |
##author.name## / ##author.email## | Inside ##FOREACHauthors## |
Note that ##ticket.assigntousers## returns a ready-made comma-separated list - GLPI does the implode internally. It is not a loop, it is plain text: it solves "I have two technicians and want both in the email" with no trick.
The most common field mistake: the requester is not ##user.name##
This is, by far, the issue we reopen most in template audits: the requester's name arrives blank. The cause is almost always the same - someone copied ##user.name## from a password-reset tutorial into a ticket notification. In a Ticket notification, ##user.name## simply does not exist: it belongs to User-object notifications (password reset, account creation). GLPI raises no error - it just substitutes an empty string, and the technician spends the afternoon chasing a bug that is actually a data-source problem. A ticket's requester is ##ticket.authors## (ready-made list) or, if you want each name and email separately, the FOREACH block below.
FOREACH: the part almost nobody knows
A ticket has N followups, N tasks, N requesters. There is no magic tag that dumps everything formatted: GLPI exposes the collections through a repeat block. You open the loop, write the inner part once with the item's tags and close it - and you can even cap how many items with FIRST or LAST:
Hello ##ticket.authors##,
Your ticket ##ticket.id## - ##ticket.title##
changed to: ##ticket.status##
Technician(s): ##ticket.assigntousers##
Latest followup:
##FOREACH LAST 1 followups##
[##followup.date##] ##followup.author##:
##followup.description##
##ENDFOREACHfollowups##
Open it: ##ticket.url##
The LAST 1 (it also accepts FIRST and larger numbers) is what avoids the mile-long email repeating the whole history on every new followup - a recurring client request most teams "solve" by hacking out content, when the modifier was all they needed. Collections that accept FOREACH include followups, tasks, authors, actors, costs and the linked ones (linkedtickets, linkedchanges, linkedproblems).
Multilingual notifications for free: the ##lang.*## tags
A detail that only shows up when you open the full tag list: every value tag has a label companion. For ##ticket.status## (the value) there is ##lang.ticket.status##, which prints the "Status" label translated into the recipient's language. Building the template with label + value pairs, the same message goes out in Portuguese to one user and in Spanish to another, without duplicating the template:
##lang.ticket.title##: ##ticket.title##
##lang.ticket.status##: ##ticket.status##
##lang.ticket.priority##: ##ticket.priority##
For a client with entities in more than one country, this collapses the maintenance of "one template per language" into a single template. You do not translate the value - data is data; only the label changes, and ##lang.*## handles that on its own.
Diagnosis: the variable is right, but the email does not go out
When the template is flawless and the email still vanishes, the problem is not the tag - it is the queue. GLPI writes every message to glpi_queuednotifications and the queuednotification automatic action does the actual sending. If the action does not run (cron stopped) or SMTP refuses, the message piles up with a null sent_time. This is the first thing we run on a "notifications not arriving" ticket:
-- Queued notifications that should already have gone out:
-- sent_time NULL = not sent yet; send_time in the past = overdue.
SELECT id, name AS subject, recipient, send_time, sent_try
FROM glpi_queuednotifications
WHERE sent_time IS NULL
AND send_time <= NOW()
ORDER BY send_time ASC
LIMIT 30;
Rows piling up with a rising sent_try mean SMTP is rejecting; a growing queue with sent_try at zero means cron is stopped. In both cases the template is fine - and you just saved the afternoon you would have spent rewriting tags for nothing.
Where each variable works (decision matrix)
The confusion we inherit from almost every new estate is thinking "ticket template" and "notification template" are the same thing. They are not - and only one of them does ##tag## substitution:
| Where | Substitutes ##tag##? | What it is for |
|---|---|---|
| Notification template (body and subject) | Yes - tags, FOREACH and ##lang.*## | Dynamic email body |
| Ticket template (TicketTemplate) | No | Predefine, hide or make fields mandatory at creation |
| Task / followup template | No | Reusable base text, no dynamic substitution |
| Notification subject line | Yes | Same tags as the body, on a single line |
Final tip worth all the rest: when editing a notification template, GLPI shows the available-tags tab for that specific item. It is the source of truth - contextual and always aligned to the installed version. Before copying a tag from a blog (this one included), confirm it appears there.
Need notifications that actually inform - multilingual, with the right requester and no email stuck in the queue? Discover NexTool's GLPI sustaining service.
Reviewed by the NexTool Solutions team.