Skip to content

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.

ConceptWhat it is
EventA named, catalogued notification opportunity — the thing rules subscribe to. Events carry a payload schema that powers filter and template autocomplete.
TemplateThe subject line and body of a message. Templates support inheritance (a shared base layout that leaves extend) and per-organization overrides.
RuleThe 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 ruleDeclarative recipient logic — resolve recipients by entity field, role, group, segment, or subscriber preference, with no custom code.
Side effectAn action's declared output. Attaching a side effect to an action is what makes it emit an event.
SubscriptionA citizen's notification preferences, used by subscriber-based recipient resolution.
Delivery ledgerA 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: true

Subjects render with simple field substitution ({entity[public_ref]}); bodies render with full templating ({{ entity.title }}).

Atelier — declare your application, generate the product.