Merge branch 'edge' into docs/platform-facts-protocol-5

This commit is contained in:
2026-09-01 13:19:46 +00:00
9 changed files with 286 additions and 9 deletions

View File

@@ -0,0 +1,175 @@
---
title: Engagement rules
description: Decide what your site mails and shows people — the rule editor, saved audiences, the trigger catalog and the send log that answers "did they actually get it".
---
import { Aside } from '@astrojs/starlight/components';
[Notifications and email](/docs/administration/notifications-and-email/) is about where a
message goes. [Message templates](/docs/administration/message-templates/) is about what it
says. This page is the part in between: **what makes one get sent at all.**
A **rule** is four decisions — *when* (a trigger), *to whom* (an audience), *by what*
(channels), and *how often* (timing). **Admin → Engagement → Rules.**
## Nothing sends until you turn it on
Every rule arrives switched **off**. That is true of the ones you make, and it is true of
the ones your modules ship with them: install a game module and you get a shelf of ready
rules, all dark, none of them mailing anybody. Turning one on is a deliberate, separate
act.
The same caution runs through the rest of the screen. Every rule carries a **hard ceiling
on sends per hour** — you cannot save one without a number — because the failure mode of an
automated mailer is not a wrong message, it is ten thousand of them at four in the morning.
<Aside type="caution" title="Upgrading? Your Team emails are here now">
Team notification email used to be its own pipeline. It is engagement rules now, and — like
every other seeded rule — those four rules arrive **disabled**. If your members were getting
Team mail before an upgrade, it stops until you turn them on. The admin dashboard says so
while it is true.
</Aside>
## What a rule is made of
**The trigger** is the event that fires it: a house falling into disrepair, a post being
published, a login failing. Pick it from what is registered — see
[the catalog](#the-trigger-catalog) below. A rule's trigger is **fixed once the rule
exists**: its cooldowns, its pending messages and its whole send history are about one
event, so changing it would silently be a different rule wearing the same name. Make a new
one instead.
**The audience** is who hears about it. Some are built in — the person the event is about,
everyone subscribed to it, staff. Others come from your modules and are named in their own
vocabulary. You can also point a rule at a **saved audience** you composed yourself; see
[Audiences](#audiences).
**The channels** are how it reaches them: on the site, by email, by push. A rule can name
more than one, and each channel picks its own template — the same event can be a sentence
in the inbox and a properly laid-out letter in the mail.
**The timing** is the part worth reading twice.
- A **delay** holds the message before it goes, so a situation that resolves itself never
produces a message at all.
- **Cancel on** names the events that call it back. A warning that a house is about to
collapse waits fifteen minutes and is cancelled outright if the owner turns up and
repairs it — nobody is told their house was in danger after it stopped being in danger.
- A **cooldown** is the "not again for a while" limit, counted **per person, per subject
and per channel**. Per subject, so a cooldown about one house says nothing about another.
Per channel, so "one a day about this house" means one email *and* one inbox item, which
is what an operator setting that limit means.
## Audiences
**Admin → Engagement → Audiences** is where you build a named set of people out of the ones
your modules declare — *members of this Team*, *the sitting governors* — and combine them:
all of these, any of these, none of these.
One rule governs the whole screen: **composition narrows and never widens.**
- The ceiling of a saved audience is **derived** from the tightest thing in it, never
chosen. That is true of "any of" too, where the intuitive answer — the widest of the two —
is the wrong one. A ceiling says what an expression is *allowed* to reach, not what it
happens to resolve to today.
- **"None of" is only offered inside an "all of" group.** Alone it would have to mean
"everybody except these", which is a broadcast built out of a short list, and it is not
offered anywhere it would mean that.
- Two audiences with no relationship between them — staff and "the person this is about",
say — have no honest combined ceiling, so the save is refused rather than guessing which
side to take.
Before you save a rule, the editor shows you a **reach preview**: a number, never a list of
names. It will also tell you when a number is a floor rather than an answer, and when an
audience resolves to nobody at all and why.
## The ceiling, and why a rule will not offer the audience you expected
Every trigger declares the **widest audience a rule may ever give it**. It is the security
boundary of the whole system, and it is set in code by whoever declared the event, not in
the admin panel. Staff-only events cannot be widened into public ones by anybody, including
you.
Seven values, and they are a **tree, not a ladder**:
| Ceiling | Who that is |
| --- | --- |
| `everyone` | Everyone, including signed-out visitors |
| `authenticated` | Any signed-in user |
| `subscribers` | Signed-in users subscribed to this event |
| `members` | Members of a module-declared list |
| `staff` | Staff only — admins, editors and moderators |
| `admin` | Administrators only |
| `owner` | Only the user the event is about |
<Aside type="note" title="Fewer people is not less exposure">
The tempting reading is a ladder — that a staff-only event could obviously also go to just
one person. It cannot, and the example is the whole argument: cheat detection is a
staff-only event, and "just one person" would be *the player it was detected on*. The
question a ceiling answers is never how many, it is **which**.
</Aside>
So `staff` does not permit `owner`, `members` does not permit `subscribers`, and the editor
simply does not offer you the audiences the trigger forbids. The one exception proves the
rule: `admin` sits under `staff`, because every administrator really is staff.
## The trigger catalog
**Admin → Engagement → Triggers** lists every event a rule can be built on, and it is
read-only on purpose — **there is no table behind it**. A trigger is declared in code, by
the site or by an installed module, so what you are looking at is whatever registered on
this boot. Uninstall a module and its triggers stop appearing; nothing was deleted.
Two things it shows that are invisible everywhere else:
- **The variables** each event carries, with an example of each. This is the list a template
is allowed to reference — when a message comes out with a hole in it, this is the screen
that says why.
- **The ceiling**, so when the rule editor offers you a narrower set of audiences than you
expected, you can see the number it is obeying.
### Dormant rules
A rule can be switched on and still be unable to fire — most often because the module that
declared its trigger, or the audience it points at, is no longer installed. Those are
badged **dormant** in the list, with the reason, because "this rule cannot fire" is a
different fact from "this rule is off" and you need both. The on/off switch keeps working
on a dormant rule, deliberately: a rule whose module has gone is exactly the rule you most
want to be able to stop.
## The send log
**Admin → Engagement → Send Log** answers one question: *did that person get that message,
and if not, why not?* Every attempt is a row — when, what fired it, which user, which
channel, and the result. Filter by result to go straight to what failed.
| Result | What it means |
| --- | --- |
| **Sent** | Handed to the channel successfully |
| **Failed** | The attempt errored — the reason is on the row, not hidden in a tooltip |
| **Not sent** | Suppressed before it was attempted: unsubscribed, unverified, or on the [suppression list](/docs/administration/troubleshooting/) |
| **Bounced** | The receiving server rejected it after the fact |
| **Marked as spam** | The recipient reported it |
Test sends from the template editor land here too, labelled as such, so you can confirm
your own test arrived before turning a rule on for real.
<Aside type="tip" title="Two things it will not show you, on purpose">
**The email address.** The log stores a one-way hash of it — enough to tie a bounce back to
a delivery, not enough to become a second address book.
**A name.** It holds the user id, and that is deliberate: joining the account list in would
quietly turn a delivery log into a staff-readable directory. Paste the id into Moderation,
which is where a person's record belongs.
</Aside>
## What a game module brings
A module declares its own triggers and its own audiences, in its own vocabulary, and it may
ship rules and message bodies to go with them. The Ultima Online module ships a large family
of them — houses falling to ruin, vendors running out of gold, a governor being seated, a
guild's fortunes — written in the voice of an in-world office rather than a system alert.
All of them arrive **disabled**, like every other seeded rule. Read the list in
**Admin → Engagement → Triggers**, turn on the ones your shard should send, and check the
send log the first time each one fires.

View File

@@ -129,4 +129,5 @@ rejected sender address, a bad password, a relay that will not accept your domai
The log does not store anybody's email address. It keeps a one-way fingerprint instead, so
that a bounce can be matched back to a delivery without the log itself becoming a second
copy of your members' addresses.
copy of your members' addresses. The rest of that screen — and the rules that decide a
message is sent at all — is [Engagement rules](/docs/administration/engagement-rules/).

View File

@@ -9,6 +9,9 @@ Four separate delivery paths, each optional, each off until you configure it. A
configures none of them still works — it just never reaches anyone who is not looking at
it.
This page is about the paths themselves. What decides that a particular message gets sent
down one of them is a rule — see [Engagement rules](/docs/administration/engagement-rules/).
## Email
**Admin → Settings → Email delivery.** The site sends contact-form messages, invitations,
@@ -154,6 +157,10 @@ member sets their own. The defaults are not symmetrical, and the asymmetry is de
- **Muting a Team silences all three for that Team**, whatever the grid says, without
touching any of their other Teams.
The operator's side of the same question — which events exist, and how wide an audience each
one may ever be given — is [Engagement rules](/docs/administration/engagement-rules/). When
a message went nowhere and you want to know why, the send log there is the screen that says.
<Aside type="caution" title="Nothing here retries">
The announcement dispatcher sends once, and the Team notification bridge states plainly that
a message is sent once and not retried. If Discord is down when a post is published, that

View File

@@ -90,8 +90,9 @@ it says. What changed is that it is now precise: it stops the emails it came wit
that Team's push notifications alone, where before it silenced both.
You can change what any of these messages say — see
[Message templates](/docs/administration/message-templates/) — and see who was actually
sent what in **Admin → Engagement → Send log**.
[Message templates](/docs/administration/message-templates/) — decide which of them are sent
at all under [Engagement rules](/docs/administration/engagement-rules/), and see who was
actually sent what in **Admin → Engagement → Send log**.
## The Discord bridges

View File

@@ -114,6 +114,22 @@ a Team. See [Teams](/docs/administration/teams/).
- **A missed announcement does not come back.** Nothing retries; the post itself is still
on the site.
## Nothing is sent for one particular event
Mail works, other notifications arrive, but this one thing never produces anything. The
answer is almost always in **Engagement → Rules**, and it is one of four:
- **The rule is off.** Every rule ships disabled, including the ones your modules bring
with them, so "installed" is not "on".
- **The rule is badged *dormant*.** It is switched on but cannot fire — usually because the
module that declared its trigger, or the audience it points at, is no longer installed.
- **It fired and was held back by its own cooldown**, which is per person, per subject and
per channel. The Send Log shows nothing for a message that was never queued.
- **The audience resolves to nobody.** The rule editor's reach preview is the fastest way
to find that out — it will tell you the count is zero and why.
[Engagement rules](/docs/administration/engagement-rules/) walks through all four.
## One person stopped receiving email
Everyone else is getting mail, so the transport is fine. Check