CodamAIDocs
Topicdone

Edit templates

Who may change which template: editors (platform administrator, mail-template-admin) all of them, tenant administrators only their own and only approved kinds. Reset a default instead of deleting it.

Variants
editortenant administratorkind not approved → refusedsomeone else's template → refusedtemplate cannot be generated → refusedreset a defaultdelete a tenant's wording

What this is about

All templates are in the database: the default per email and language, plus the wording of individual tenants. They can be changed at runtime, without a new delivery. Because an email is sent in the platform’s name, it is precisely defined who may change what.

The calls

CallDoes
GET /cias/notification/templatesall templates you may see
POST /cias/notification/templatessave a template: create or replace
POST /cias/notification/templates/{id}/resetreset a default to the shipped text
DELETE /cias/notification/templates/{id}delete a tenant’s wording. The tenant then gets the default again

An editor sees every template in the list, a tenant administrator only their tenant’s wording. The calls exist only if codamai.cias.notification.enabled, persistence: jpa and rest are switched on. Otherwise they answer 404, as if they did not exist.

Save a template

Request
POST /cias/notification/templates
Authorization: Bearer <token of Anna, tenant-admin in nordbau>
{
  "key": "WELCOME",
  "locale": "de",
  "subject": "Willkommen bei Nordbau",
  "body": "Guten Tag,\n\nIhr Zugang ist bereit: ${loginUrl}\n\nIhr Nordbau-Team",
  "html": false
}
Response
HTTP 200
{
  "id": "7d2c…",
  "key": "WELCOME",
  "tenantKey": "nordbau",
  "locale": "de",
  "subject": "Willkommen bei Nordbau",
  "body": "Guten Tag, …",
  "html": false,
  "updatedBy": "5c9e…",
  "updatedAt": "2026-10-01T10:15:00Z",
  "origin": "EDITED",
  "isDefault": false
}
FieldMeaning
idmissing: create, or replace the template with the same features. Set: replace exactly this template
keywhich email, see Which emails exist
tenantKeyfor which tenant. Only an editor can set it. Empty means: the default. For everyone else CIAS takes the tenant from the token and discards the value
localethe language, for example de or en
subject, bodysubject and text, with placeholders like ${loginUrl}
htmlwhether the text is HTML. Then CIAS also sends a plain-text version
updatedBy, updatedAtwho changed it last and when. The person comes from the token. Empty for a shipped default
originPACKAGED while it is the shipped text, EDITED once somebody changed it
isDefaultwhether this is the default, the template without a tenant

A template is identified by email, tenant and language. Without a tenant there is exactly one per email and language: saving without tenantKey changes the default, it never creates a second one. Saving twice with the same features likewise results in one template.

The check

May this caller save this template?
  1. CIAS
    authenticated
    Does the request have a token, and does it name the person?
    ↳ no 403 cias.notification.template-forbidden
  2. CIAS
    editor?
    yes → may save any template, the tenant comes from tenantKey
  3. CIAS
    tenant administrator
    Does the caller have the tenant administrator role, and does their token name a tenant?
    ↳ no 403 cias.notification.template-forbidden
  4. CIAS
    approved kind
    Is the email on the list that tenants may change?
    ↳ no 422 cias.notification.template-rejected
  5. CIAS
    own template
    When replacing by id: does the template belong to the own tenant, and is it not a default?
    ↳ no 403 cias.notification.template-forbidden
  6. CIAS
    can be generated
    CIAS generates the template once as a trial, with empty values for all placeholders. Does that work?
    ↳ no 422 cias.notification.template-rejected, with the first line of the error message
  7. Template saved

Who may do what, per installation

Which roles are editors is set by codamai.cias.notification.editor-roles, or the environment variable CIAS_NOTIFICATION_EDITOR_ROLES. The default is platform-admin,mail-template-admin. Which emails tenants may change is decided by each installation. There is deliberately no default for that: an installation that has not asked itself the question does not start.

InstallationEditorsTenant roleEmails tenants may change
standalone CIASfrom CIAS_NOTIFICATION_EDITOR_ROLES, default platform-admin, mail-template-admintenant-adminMEMBERSHIP_INVITATION, INVITATION, WELCOME
embedded in the hub backendthe installation’s platform administrator roles, plus the same settingtenant-adminnone

mail-template-admin is a realm role, see The platform’s realm roles. It reaches the wording of every tenant and is therefore never granted within a tenant.

The list for tenants is an allow list, not a block list. New emails are therefore blocked for tenants at first. Why these in particular are not approved:

  • VERIFY_EMAIL and PASSWORD_SETUP carry a link that opens an account. Whoever can reword them can write a convincing phishing email from the platform’s sender.
  • ALREADY_REGISTERED may go to someone from an entirely different tenant.
  • APPROVAL_PENDING and REJECTED word a decision. Whoever decides should not rewrite it afterwards.

The variants

Who changes what

When: The token carries platform-admin or mail-template-admin.

May save any template: the default (tenantKey empty) or the wording of any tenant. Is not bound by the allow list. Sees all templates in the list.

Result: allowed

When: Anna is tenant-admin in nordbau and saves WELCOME.

CIAS saves the template as the wording of nordbau, whatever is in tenantKey. Anna sees only the wording of nordbau in the list.

Result: allowed, if WELCOME is approved

When: Anna wants to change VERIFY_EMAIL.

The email is not on the allow list. The answer names the reason, so Anna knows what is going on.

Result: 422 cias.notification.template-rejected

When: Anna sends the id of a default or of another tenant's wording.

Only editors change defaults.

Result: 403 cias.notification.template-forbidden

When: The text contains, for example, Hallo ${name without a closing brace, or a blocked command like ?new.

CIAS refuses to save and names the spot. Nothing is saved.

Result: 422 cias.notification.template-rejected

When: POST /cias/notification/templates/{id}/reset by an editor

The default gets the shipped text again and counts as PACKAGED. So with the next release it follows automatically again. For an email CIAS ships no text for, there is nothing to reset.

Result: 200 with the template, or 422 cias.notification.template-rejected

When: DELETE /cias/notification/templates/{id}

The same rules as for saving. Afterwards the tenant gets the default again. A default cannot be deleted, only reset. A template you may not see does not exist for you: 404, not 403. That way nobody can count which templates other customers have.

Result: 204, 404 cias.notification.template-not-found, or 422 for a default

What a template can do

Templates are written in FreeMarker, a template language. You can insert placeholders (${loginUrl}) and write simple conditions:

<#if passwordUrl?has_content>
Vergeben Sie jetzt Ihr Passwort: ${passwordUrl}
<#else>
Melden Sie sich an: ${loginUrl}
</#if>

Nothing more. Because people other than the developers write templates, FreeMarker is locked down: a template cannot create classes, cannot call methods and cannot reach the objects behind the values. It only sees the values of the context, plus ${application} and ${flow} in every email.

When saving, CIAS generates the template once as a trial, with the same locked-down setup and empty values for all placeholders. A template that cannot be generated this way is refused. Still check with a real email that the text arrives the way you want: placeholders with values sometimes behave differently from empty ones.

Errors at a glance

ResponseWhen
400 cias.notification.invalid-payloadrequired field missing, field too long (subject up to 512, text up to 65535 characters), invalid email key
403 cias.notification.template-forbiddennot authenticated, no suitable role, someone else’s template, tenant administrator tries to reset
404 cias.notification.template-not-foundid unknown or invisible to you
404 without a bodythe calls are switched off in this installation
422 cias.notification.template-rejectedtenants may not change this email, template cannot be generated, deleting a default, nothing to reset to

Pitfalls

Next

Sources in the code and the knowledge base
  • CIAS/cias-notification – MailTemplateAdminController (GET/POST /cias/notification/templates, DELETE /cias/notification/templates/{id}, POST /cias/notification/templates/{id}/reset), MailTemplateRestDtos (SaveTemplateRequest, TemplateResponse), MailTemplateExceptionHandler, NotificationEndpointGuard
  • CIAS/cias-notification – MailTemplateService (list, save, delete, reset, scopeFor, locate, requireWritable), MailTemplateEditPolicy (editorRoles, EDITOR_ROLES), EditorContext, StoredTemplate (origin, isDefault), MailTemplateValidator
  • CIAS/cias-notification – FreeMarkerMailRenderer (lockedDownConfiguration), FreeMarkerTemplateValidator
  • CIAS/cias-runtime – CiasMailTemplateEditConfiguration; hub-backend – CiasRegistrationSupportConfiguration.mailTemplateEditPolicy
  • CIAS/cias-notification/docs/adr – ADR-019 (section 7, amendment 2026-10-01)
Search