for details about the internal implementation see README in module code folder
The Notification Module is watching for events that are configured to trigger (push) notifications to users. This module watches for database changes and creates notification events which can be sent through various channels:
- Push Notifications through Firebase
- "In-App" Notifications directly in the toolbar of our frontend UI
- Email Notifications through SMTP (optional, active when the SMTP host, sender address and Keycloak access are configured, see Setup)
Push Notifications are sent through Firebase Cloud Messaging (FCM).
You can make a request to the API to check if a certain feature is currently enabled and available:
> GET /actuator/features
// response:
{
"notification": {
"enabled": true,
"email": { "enabled": true }
}
}
notification.email is only reported when notification emails can actually be sent (see
Setup), so the app can offer the email option only when it works.
If the aam-services backend is not deployed at all, such a request will usually return a HTTP 504 error. You should also account for that possibility.
Users configure their notification rules in the frontend. A rule's conditions follow MongoDB query
syntax and semantics, including negations ($not, $nor, $ne, $nin), $in, $elemMatch,
comparisons, $exists, $regex and dotted paths into nested fields.
Note that values are compared strictly by type: a number stored as a string in a condition
(e.g. {"age": {"$gte": "18"}}) does not match a numeric field.
A condition value can be the placeholder "$current_user" (e.g. {"assignedTo": {"$elemMatch": "$current_user"}}),
which stands for the entity linked to the rule owner's user account (the Keycloak user attribute exact_username).
This requires the Keycloak settings below (see Setup); without them, or for a user account without a
linked entity, rules using the placeholder never trigger. Changes to the linked entity take effect within a day
(NOTIFICATION_CONFIGCACHE_TTLMILLIS, default 86400000), or immediately when the user's notification settings
are saved.
The following environment variables are required:
FEATURES_NOTIFICATIONAPI_ENABLED=true
FEATURES_NOTIFICATIONAPI_MODE=firebase # delivery mode for push notifications
# Email notifications (optional): setting SPRING_MAIL_HOST switches them on, there is no separate flag.
# They are sent when the sender address, the SMTP host and the Keycloak access below are all set.
# Sender address only; the display name and subject prefix are managed in the
# templates/{locale}/notification/email-branding.properties template file.
NOTIFICATION_EMAIL_FROM=notifications@your-instance.org
SPRING_MAIL_HOST=<smtp-host>
SPRING_MAIL_PORT=587
SPRING_MAIL_USERNAME=<smtp-username>
SPRING_MAIL_PASSWORD=<smtp-password>
SPRING_MAIL_PROPERTIES_MAIL_SMTP_AUTH=true
SPRING_MAIL_PROPERTIES_MAIL_SMTP_STARTTLS_ENABLE=true
# Language of the email boilerplate (en, de, fr). Defaults to en.
# Keep in sync with the site default language.
NOTIFICATION_EMAIL_LOCALE=en
# Needed for email notifications (lookup recipient addresses in Keycloak)
# and for the "$current_user" placeholder in notification rules (lookup of the user's linked entity)
KEYCLOAK_SERVERURL=https://<your-keycloak-url>
KEYCLOAK_REALM=<your-realm>
KEYCLOAK_CLIENTID=<service-client-id>
KEYCLOAK_CLIENTSECRET=<service-client-secret>
# Firebase Configuration: Confidential (!)
NOTIFICATIONFIREBASECONFIGURATION_CREDENTIALFILEBASE64=<base-64-encoded-firebase-credential-file>
APPLICATION_BASEURL=<your-instance>.aam-digital.comThe Keycloak client for the lookup is the aam-backend client (KEYCLOAK_CLIENTID); its service account needs the
realm-management role view-users. The definition of that client that ships with the backend
(aam-backend-client.json, see its
README) grants it.
Notification links are derived centrally from APPLICATION_BASEURL:
- Email "manage notification settings":
https://<base-url>/user-account?_tabIndex=1 - Push notification link target (FCM webpush
linkandurlcustom data)
Database change-detection is activated automatically when notification (or reporting) is enabled; no separate flag needs to be set.
The email channel is on when the notification API is enabled and SPRING_MAIL_HOST,
NOTIFICATION_EMAIL_FROM and KEYCLOAK_SERVERURL all have a non-blank value. An empty value, as in
the deployment template (SPRING_MAIL_HOST=), counts as not set. Without them the service starts
normally and only sends notifications through its other channels.
KEYCLOAK_SERVERURL (with the realm and client settings next to it) is needed because a notification
only identifies the recipient by user id. The email address itself is looked up in the user's Keycloak
account when the email is created.
On startup the service logs which state it is in. If an SMTP host is set but the sender address or the Keycloak access is missing, it logs an error naming the missing settings, so a half-finished configuration is not mistaken for a working one.
The former flag FEATURES_NOTIFICATIONAPI_EMAIL_ENABLED is no longer read, whatever its value. If it
is still set, the service logs a warning at startup; remove it. In particular, a deployment that
has an SMTP server configured now sends notification emails even if the flag is false; to stop
this, clear SPRING_MAIL_HOST.
Notes:
- Email notifications are only sent for users with
channels.email=truein theirNotificationConfig:*document. - If a user has no email address in Keycloak, that notification is skipped for email delivery.
- Ensure the server firewall allows outgoing traffic for the SMTP port.
If sending an email fails, the service automatically retries up to 3 times before giving up.
Notifications that are still owed are held in the notification-outbox CouchDB database, so no
notifications are silently lost.
When the service restarts, all held notifications are automatically retried once. This means the recovery path for any delivery problem is always the same: fix the root cause, then restart the service.
Examples:
- SMTP server temporarily unreachable → retried up to 3 times with a growing pause, then held until next restart
- Wrong SMTP credentials → held immediately (retrying wouldn't help), fixed after updating credentials and restarting
To see what is currently held, list the outbox and look at attempts and lastError:
GET /notification-outbox/_all_docs?include_docs=true
An entry whose attempts has reached the maximum is waiting for a restart. Entries are deleted as
soon as they are delivered, so an empty database means nothing is outstanding.
The email language is selected by NOTIFICATION_EMAIL_LOCALE (defaults to en).
Templates are organized in a per-language folder ({locale}/notification/...) so a
translator can copy a whole language folder (e.g. en/ → de/) and translate the
bundled templates. A region suffix in the configured locale is ignored
(de-DE → de).
For the configured locale, the backend resolves the template in this order and uses the first that exists:
- Mounted override, localized:
/opt/app/templates/{locale}/notification/create-notification-email-template.html - Mounted override, legacy unsuffixed (kept for backward compatibility):
/opt/app/templates/notification/create-notification-email-template.html - Bundled classpath, localized:
src/main/resources/templates/{locale}/notification/create-notification-email-template.html - Bundled classpath, English fallback:
src/main/resources/templates/en/notification/create-notification-email-template.html
Bundled languages: en, de, fr.
Example volume mount and override layout:
volumes:
- ./config/aam-backend-service/templates:/opt/app/templates:roconfig/aam-backend-service/templates/
de/notification/create-notification-email-template.html
de/notification/email-branding.properties
fr/notification/create-notification-email-template.html
Important:
- Template changes require a container restart.
- The backend does not manage logo files or inline image attachments. Template authors can embed images directly in the HTML template (for example as data URIs or remote image URLs).
The sender display name and the subject prefix are not environment variables. They are kept
together with the email templates in {locale}/notification/email-branding.properties, so all
template-related text is managed in one place:
# Display name shown in front of the sender address, e.g. "Aam Digital <notifications@example.org>".
from-name=Aam Digital
# Prefix prepended to every notification email subject, e.g. "Aam Digital: A new record was added".
subject-prefix=Aam Digitalfrom-nameis combined with the configured address (NOTIFICATION_EMAIL_FROM, which holds the bare address only) to form the final sender headerfrom-name <NOTIFICATION_EMAIL_FROM>. Iffrom-nameis empty, the bare address is used as-is.subject-prefixis prepended to every email subject.
The file is resolved per locale (NOTIFICATION_EMAIL_LOCALE) using the same order as the HTML
template, using the first that exists:
- Mounted override, localized:
/opt/app/templates/{locale}/notification/email-branding.properties - Mounted override, legacy unsuffixed:
/opt/app/templates/notification/email-branding.properties - Bundled classpath, localized:
src/main/resources/templates/{locale}/notification/email-branding.properties - Bundled classpath, English fallback:
src/main/resources/templates/en/notification/email-branding.properties
Only the English (en) branding file is bundled; other locales fall back to it unless you provide a
localized copy (e.g. to translate the subject prefix). If the file — or an individual key — is
missing, the subject prefix falls back to Aam Digital and no display name is added.
To filter notifications based on entity-level permissions (so users only receive notifications for entities they can access), configure the connection to the replication-backend:
AAMREPLICATIONBACKENDCLIENTCONFIGURATION_BASEPATH=http://replication-backend:5984
AAMREPLICATIONBACKENDCLIENTCONFIGURATION_BASICAUTHUSERNAME=<admin-username>
AAMREPLICATIONBACKENDCLIENTCONFIGURATION_BASICAUTHPASSWORD=<admin-password>If these variables are not set, notifications are sent without permission filtering
(all users matching a notification rule receive the notification regardless of entity access).
This is the expected default for systems without access control (no Config:Permissions document configured).
To explicitly disable permission filtering set AAMREPLICATIONBACKENDCLIENTCONFIGURATION_BASEPATH= (empty value).
When the replication-backend is configured but temporarily unavailable, notifications are not sent (fail-closed) to avoid leaking data to unauthorized users. Each such failure is logged at ERROR.
A permission check that gets no answer is abandoned after
AAMREPLICATIONBACKENDCLIENTCONFIGURATION_RESPONSETIMEOUTINSECONDS (default 10) and treated as
unavailable, so an unresponsive replication-backend delays notifications instead of stopping them.
If you want to send Push Notifications, we rely on Firebase Cloud Messaging (FCM).
- Create a Firebase Account
- Create a Firebase Project
- Select "Cloud Messaging" and "Add a web app" (you can skip the second step of setup through npm)
- Open the "Project Settings" page
- Under "Your apps" copy to JSON config object
- store this as
assets/firebase-config.jsonin the Aam Digital frontend (or overwrite the empty sample file in your ndb-setup folder in your deployment) - make sure this is proper json format (i.e. the keys are also in double quotes)
- store this as
- Create a Service Account or new key for it
- ... through the firebase interface as described here
- download the
firebase-credentials.jsonwith its key - Encode it as base64
run this to print the encoded file to the console:
base64 -i firebase-credentials.json
- Copy the output (remove line breaks to make it a single line of encoded text)
- Add this as an environment variable:
NOTIFICATIONFIREBASECONFIGURATION_CREDENTIALFILEBASE64as described above
- To apply the config restart the container, if necessary
Users need to have certain Entity Permissions in the frontend to be able to access and configure Notifications:
In
the User Permissions doc
Config:Permissions in the CouchDB, make sure the following rules are present:
{
"default": [
{
"subject": ["NotificationConfig", "NotificationEvent"],
"action": "manage"
}
]
}