Templates
Needs review
Needs review: Confirm whether new tenants get default email, SMS and PDF templates; the template seeders are not among the ulams:tenant:create steps.
Every message ulams sends to a user, and every certificate it generates, comes from a template stored in the tenant database. Configuration → Templates edits them.
How events become messages
Section titled “How events become messages”flowchart LR
E[Domain event<br/>e.g. ResetPassword] --> L[Template listener]
L --> A{Template assigned<br/>to the item?}
A -- yes --> T[Assigned template]
A -- no --> D[Default template<br/>for event + channel]
T --> R[Fill in variables]
D --> R
R --> C[Channel: email, SMS or PDF]
The templates package listens to every Ulams\… event.
The channel packages register, in code, which events they support and which variables each
event provides: Template::register(Event, Channel, Variables). When a registered event
fires, the listener, for each channel:
- picks the template assigned to the item the event is about, if the event type supports assignment (for example a certificate assigned to one course), otherwise the default template for that event and channel;
- skips the channel and writes an error to the log if there is no template, or if the template is invalid (for example it lacks a required variable);
- replaces the variables and hands the result to the channel, which sends the email or SMS or generates the PDF.
So a message is sent only if a default (or assigned) template exists. Each channel package has
a seeder that creates a default template, named Default template for event … on … channel,
for every registered event that has none: TemplatesEmailSeeder (run by the API’s
DatabaseSeeder), TemplateSmsSeeder and TemplatesPdfSeeder. Tenant provisioning does not
run them, so check the lists on a new tenant. The complete list of events and the template
types registered for them is generated in
Events and notifications.
| Channel | Package | Sections you edit | Sent to |
|---|---|---|---|
| templates-email | title (subject), content (MJML) |
the user’s email | |
| templates-pdf | title, content (a fabric.js design) |
stored as a PDF for the user | |
| SMS | templates-sms | content (text) |
the user’s phone; nothing is sent without one |
The templates screen
Section titled “The templates screen”| Screen | Route | Permission needed to see it |
|---|---|---|
| Template lists (tabs Email, PDF, SMS) | /configuration/templates/:template |
template_read |
| New or edit template | /configuration/templates/:template/:id (new for a new template) |
template_read |
Saving and deleting need template_create, template_update and template_delete;
generating PDFs manually needs events_trigger. The tabs can be hidden with the
hideTemplateTab-email and hideTemplateTab-sms settings, and the PDF tab with
disable-Certificates (see Settings).

Each list shows ID, creation date, name, event and whether the template is the default, with New, Edit and Delete.
Editing a template
Section titled “Editing a template”- Open a tab and click New, or Edit on a row.
- Enter a Name and choose the Event. The event list contains only events registered for this channel, and the choice decides which variables you can use.
- Tick Set as default template if this template should be used for the event.
- Fill in the sections. Above each section the form lists the event’s variables; the required variables must appear in the template, otherwise it is saved as invalid and never sent. A new template is pre-filled with the event’s default content.
- Click Submit.
Variables
Section titled “Variables”Variables are written with an @ prefix, for example @VarUserName or @VarCourseTitle.
There are two kinds:
- Event variables, defined by the variable class registered for the event. The form
shows them for the selected event (they come from
GET /api/admin/templates/variables). - Global variables built from the tenant settings: every public,
enumerable setting that is not of type
jsonbecomes@GlobalSettingsfollowed by its key and type in camel case. For exampleglobal.companyNameof typetextbecomes@GlobalSettingsCompanyNameText. Markdown values are converted to HTML and file and image values to URLs.
You can keep recurring blocks, such as an email header and footer, in settings and use them as global variables in every template. When such a setting changes, the email templates that use it are rendered again in the background.
Preview
Section titled “Preview”Preview renders the template with mocked variable values and sends the result to you: the email to your address, the SMS to your phone number. A PDF preview is not sent anywhere.
Email templates
Section titled “Email templates”The content section is MJML, which is rendered to HTML when the
template is saved, not when each email is sent. The renderer is configured on the
Package Templates Email tab of Settings (ulams_templates_email.mjml.*):
either the MJML API (use_api, api_url, api_id, api_secret) or a local mjml binary
(MJML_BINARY_PATH in the environment). default_template is the MJML wrapper into which the
content is placed (@VarTemplateContent).
Email templates exist for events of most packages: account registration, verification, blocking and deletion, password reset and change, group membership, course access and deadlines, payments, tasks, webinars and consultations, video processing, users imported from CSV and invitations of people without an account.
PDF templates
Section titled “PDF templates”PDF templates are certificates and other documents designed in a visual editor (fabric.js).
They are registered for CourseFinished, so a certificate is generated when a learner
finishes a course, and for the manually triggered event. A certificate can be assigned to a
specific course in the course editor; see Content creators.
For a template of the manually triggered event, the form has Generate PDF: choose users and generate the document for them now. Products have a similar action. Generated PDFs are listed under the form.
SMS templates
Section titled “SMS templates”SMS templates are registered for consultation events (term approved, rejected, reported and reminders) and the manually triggered event. Sending needs a driver configured on the Package Sms tab of Settings:
sms.default:twilio,requestbin, ormail(sends the text as an email to<phone>@sms.com, useful only for testing);sms.drivers.twilio.sid,tokenandfromfor Twilio, or theTWILIO_SID,TWILIO_TOKENandTWILIO_FROMenvironment variables.