Appearance
Notifications
Notifications are where Atelier's core promise — declare the interesting parts, the platform provides the rest — shows up most clearly. A notification isn't a code change. It's configuration: an action emits a named event, one or more rules subscribe to that event, templates render the message, and a durable workflow fans delivery out to every matching recipient on every selected channel.
The runtime dispatch path is driven entirely by your configuration. When you add a rule in the authoring UI or declare one in a provisioning sheet, it takes effect immediately — no deploy, no engineering ticket. This is the moment where Atelier's execution plane hands a finished event off to a declarative messaging layer.
The building blocks
Atelier models notifications as a small set of composable concepts. You assemble them; the platform runs them.
| Concept | What it is |
|---|---|
| Event | A named, catalogued notification opportunity — the thing rules subscribe to. Events carry a payload schema that powers filter and template autocomplete. |
| Template | The subject line and body of a message. Templates support inheritance (a shared base layout that leaves extend) and per-organization overrides. |
| Rule | The subscription: when event X matches this filter, send template Y to these recipients on these channels. Rules can be global or scoped to a single organization. |
| Recipient rule | Declarative recipient logic — resolve recipients by entity field, role, group, segment, or subscriber preference, with no custom code. |
| Side effect | An action's declared output. Attaching a side effect to an action is what makes it emit an event. |
| Subscription | A citizen's notification preferences, used by subscriber-based recipient resolution. |
| Delivery ledger | A per-recipient record of every send attempt, written after each channel delivery for auditability and exactly-once guarantees. |
How you author a notification
Adding a notification is a two-step operator flow. No part of it requires a code change.
Step 1 — Emit the event. Open the action you want to notify on, go to its Effects tab, and add a new side effect that emits an event from the catalog. The action now publishes that event every time it runs.
Step 2 — Subscribe with a rule. Create a notification rule and choose:
- Event — what triggers the rule.
- Filter — an optional condition builder (
field/ operator /value) so the rule only fires for the payloads you care about. An empty filter always matches. - Template — the message to render.
- Recipients — who receives it, resolved declaratively.
- Channels — email, in-app tray, real-time, and more.
- Scope — leave blank for global, or pin the rule to a single organization.
- Order and Active — control sequencing and on/off.
Templates are authored in a dedicated editor with a syntax-highlighting code panel for the subject and HTML/text bodies, an extends selector for inheritance, a one-click Clone for org button to create an organization-specific override, and a live, sandboxed preview so you can see the rendered message before you save.
Events themselves are part of the curated vocabulary: you subscribe to them, you don't invent them ad hoc. This keeps the catalog meaningful and prevents orphaned events that nothing ever emits.
Declaring notifications in a sheet
Everything you can do in the UI, you can also declare in a provisioning sheet. A vertical's sheet can declare its side-effect emits and notification rules directly, so a new application ships with its messaging already wired up. This is the recommended path when you're standing up a new vertical — the notifications come online with the rest of the application, fully reproducible.
How notifications are provisioned and forked
Notification configuration follows Atelier's template-and-fork tenancy model:
- Event types live in the shared platform vocabulary — one curated catalog every application reads from.
- Templates, rules, and recipient rules are authored once on a template application and then forked into each tenant. The fork copies the configuration in with all references rewired automatically.
- After the fork, each tenant edits their own copies through the exact same editors — change a template's wording, retarget a rule, add an organization override — with zero risk to any other tenant.
- Subscriptions and the delivery ledger are pure runtime data, created per tenant as messages flow.
Seeding is idempotent and order-aware: shared base layouts are always provisioned before the templates that extend them, so a fork or re-run never lands in a broken intermediate state.
Recipient resolution
Rules resolve recipients declaratively. Out of the box you can target:
- An entity field — follow a relationship on the triggering record (for example, the assigned staff member).
- A role or group — everyone holding a role or belonging to a group, optionally scoped.
- Subscribers — citizens who opted in via their notification preferences.
- A segment — a named audience.
For advanced cases, engineering can register a named recipient resolver that immediately surfaces in the rule picker — but the declarative kinds cover the common cases without any code.
Channels
A rule delivers on one or more channels — email, the in-app notification tray, and real-time push are all first-class. Channels are selected per rule, so the same event can reach a citizen by email and a staff member in their tray from a single configuration.
Delivery guarantees
Atelier's notification runtime is built for correctness under real-world conditions:
- Tenant isolation by construction. Rule and recipient lookup is always scoped to the tenant of the triggering record. A rule in one tenant can never fan out into another.
- Exactly-once delivery. Each rule's delivery runs as its own durable workflow with a deterministic identity per event, rule, record, and edit — so retries and replays never double-send.
- Auditable ledger. Every send attempt is recorded after the fact with a retry-stable dedup key, giving you a complete, replay-safe history of what went where.
- Fail-soft dispatch. Notification delivery never interferes with the action that triggered it. If a downstream channel hiccups, the originating action still completes cleanly.
- Override precedence is predictable. When an organization-scoped rule matches an event, it cleanly takes precedence over the global rule for that organization — overrides suppress, they don't double up.
Extending notifications
Notifications are designed to grow with your applications:
- A new notification, no code. Attach a side-effect emit to an existing action, author a rule — done. This is the everyday path.
- A new recipient target, no code. Use a declarative recipient rule keyed on an entity field, role, group, segment, or subscriber set.
- A new event. When you need a genuinely new notification opportunity, it joins the curated catalog and an action emits it.
- Per-tenant overrides. Clone a template for an organization to tailor wording or branding for part of a tenant, then point that organization's rule at it.
- Whole new verticals. Declare emits and rules in the vertical's sheet so messaging ships with the application.
Notifications are a clean illustration of the Atelier model end to end: the execution plane raises an event, declarative configuration decides who hears about it, and a durable workflow makes sure they do — reliably, tenant by tenant, with nothing hand-coded along the way.
Example: a notification in a sheet
Declare the event an action emits, the message template, who receives it, and the rule that ties them together. Adding a notification is data, not code.
yaml
notifications:
events:
- { code: proposal_validated, label: "Proposal validated", is_active: true }
templates:
- code: proposal_validated
label: "Proposal validated"
subject: "Your proposal {entity[public_ref]} was validated"
body_html: "<p>Your proposal <strong>{{ entity.title }}</strong> has been validated and moves to the next stage.</p>"
body_text: "Your proposal {{ entity.title }} has been validated."
is_active: true
recipient_rules:
- { label: "Submitter", kind: entity_field, path: submitter_email }
rules:
- name: "Proposal validated -> Submitter"
event: proposal_validated
template: proposal_validated
recipient_rule: "Submitter"
channels: [inapp, email]
order: 0
is_active: trueSubjects render with simple field substitution ({entity[public_ref]}); bodies render with full templating ({{ entity.title }}).