Automation Variables and Templates in GLPI 11

A technical guide to GLPI 11 notification variables: the ##...## tags that really work, the FOREACH block almost nobody knows, the ##lang.*## tags for multilingual email, why the requester comes out blank, and the queue query NexTool runs when a notification does not arrive.

In this article7 sections

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 queuednotification automatic 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:

TagWhat 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:

WhereSubstitutes ##tag##?What it is for
Notification template (body and subject)Yes - tags, FOREACH and ##lang.*##Dynamic email body
Ticket template (TicketTemplate)NoPredefine, hide or make fields mandatory at creation
Task / followup templateNoReusable base text, no dynamic substitution
Notification subject lineYesSame 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.

Frequently Asked Questions

Almost always because the template uses ##user.name##, which does not exist in Ticket notifications (it belongs to User-object notifications, like password reset). GLPI raises no error, it just substitutes an empty string. Use ##ticket.authors## for the requester list, or the ##FOREACHauthors## block with ##author.name## and ##author.email## to handle each one.

Use the FOREACH modifier: ##FOREACH LAST 1 followups## ... ##ENDFOREACHfollowups##. LAST 1 grabs only the most recent followup; FIRST and larger numbers are also accepted. Without the modifier the loop prints every followup on each send, producing the classic mile-long email.

Yes. Every value tag has a label companion ##lang.*##: for ##ticket.status## there is ##lang.ticket.status##, which prints the label translated into the recipient's language. Building the body with ##lang.ticket.field##: ##ticket.field## pairs, a single template serves several languages.

The queue, not the template. GLPI queues every message in glpi_queuednotifications and the queuednotification automatic action does the sending. Check rows with sent_time NULL and send_time in the past: a rising sent_try means SMTP is rejecting; sent_try at zero means cron is stopped.

No. The ticket template (TicketTemplate) is for predefining, hiding or making fields mandatory at creation - it does not do ##tag## substitution. The ##...## automation variables live in notification templates (body and subject). Confusing the two is the root of much 'the variable does not work'.

Need help?