Entity Relationship Alerts
Relationship alerts are the general version of the e-commerce Back-in-Stock trigger, but for any custom entity - seats, flights, classes, shows, appointments, B2B inventory. The idea is simple:
When a record in one catalog changes, notify every contact who is attached to that record through a second, related entity.
You declare a parent entity - the catalog whose records change (e.g.
concert_tickets or series) - and choose how the audience is found: either a
related opt-in list (a waitlist / "follow" entity) or the people who fired a
relevant event (watched / viewed). See Two ways to define the audience below.
When a parent record's watched field transitions, Joryio resolves that audience, and emits a per-user event you can use as a journey or campaign entry trigger - deduplicated per person so flapping values can't spam anyone.
Why two entities?
The built-in product Back-in-Stock feature is one click because the product catalog has a known shape (a stock field, a subscriber list). Custom entities are freeform - Joryio can't guess which field means "available" or which entity holds the waitlist - so you declare it once in the parent entity's settings.
For most e-commerce stores the product catalog is still the easy path. Reach for relationship alerts when your catalog isn't products, or when the audience is an explicit list rather than inferred browsing intent.
Two ways to define the audience
Before the config, the key choice - who gets notified:
- Behavioral (
audienceSource: "event") - the people who fired an intent event (e.g.Watched Episode,Viewed Flight) that references the record, within a lookback window. No follow/waitlist records to maintain - the audience is the behavior. This mirrors how product back-in-stock builds its audience from browsing events. Best for "notify everyone who watched / viewed." - Explicit (
audienceSource: "child_entity", default) - the rows of a related opt-in entity (a "notify me" waitlist, a "follow this show" list). Best when people deliberately subscribe, independent of any behavior.
You don't need a follow entity for the VOD case - see Example 2.
"Notify me" as an event vs. a waitlist entity
A common question: if a "Notify me" tap emits an event (Notify Me with a
product_id), do you still need a waitlist entity? Usually not - point event
mode at it (intentEvent: "Notify Me", intentRefProperty: "product_id") and
you're done.
The one thing that pulls you back to a child entity is durability. An event audience is bounded by two things:
- the lookback (
intentLookbackDays), and - event retention - your workspace's data-retention TTL eventually deletes the event, and once it's gone event mode can't see it.
Restock timing is open-ended - an item might return next week or in eight months. If the "notify me" event has aged out by the time it restocks, event mode misses that person; a waitlist row persists until you clear it. So:
- Short, predictable horizon (new episodes, quick restocks) → event mode, no entity.
- Open-ended horizon, or you want a manageable "notify once then remove" list → child waitlist entity.
Configuration
Set relationshipAlert in the parent entity's settings:
| Field | Meaning | Example |
|---|---|---|
enabled | Turn the alert on | true |
triggerMode | What counts as a fire on field (see below) | "restock" |
field | The parent field whose transition triggers the alert | "available" |
eventName | The per-user event to emit | "back_in_stock" |
cooldownDays | Per-(person, record) suppression window (default 7) | 7 |
audienceSource | "child_entity" (default) or "event" | "event" |
childEntity | (child mode) internal name of the waitlist/follows entity | "ticket_waitlist" |
childRefField | (child mode) field on child rows holding the parent record's id | "ticketId" |
intentEvent | (event mode) the intent event whose audience to notify | "Watched Episode" |
intentRefProperty | (event mode) event property holding the parent record's id | "series_id" |
intentLookbackDays | (event mode) how far back to look (default 90) | 90 |
Trigger modes
restock- a number goes from ≤ 0 to > 0 (back in stock). Default.increase- a number goes up (e.g. an episode count 8 → 9).changed- a value changes to a new, non-empty value (e.g. a "latest episode id").
In child-entity mode the child entity must declare a User Link (which
field maps to a contact, by user id / external id / email / phone / attribute) -
that's how each row resolves to a person. In event mode the intent event's
user_id is the contact. A prior unknown value never fires (for the numeric
modes), so first-time populates don't false-alert.
Example 1 - Concert tickets back in stock
Goal: a show sells out; when tickets are released again, notify everyone who asked to be told.
-
Parent entity
concert_ticketswith a numeric fieldavailable. -
Child entity
ticket_waitlistwith fieldsticketId(the show/record it references) andemail. Set its User Link toemail. -
On
concert_tickets, set:{
"relationshipAlert": {
"enabled": true,
"triggerMode": "restock",
"field": "available",
"childEntity": "ticket_waitlist",
"childRefField": "ticketId",
"eventName": "back_in_stock",
"cooldownDays": 30
}
} -
When someone taps "Notify me", your app/site adds a row to
ticket_waitlist:{ ticketId: "SHOW-A", email: "fan@example.com" }. -
When
concert_tickets/SHOW-Aavailablegoes0 → 50, Joryio emits aback_in_stockevent for every waitlisted contact.
Build a one-step journey (or a triggered campaign) entered by back_in_stock.
Personalize it with the record's own fields, which ride along on the event:
{{ event.name }} tickets are back! Grab yours before they sell out again.
The event also carries event.record_id, event.new_value (the new stock), and
any scalar field on the record.
Example 2 - New episode of a series someone watches (VOD / Netflix-style)
Goal: when a new episode of a series drops, tell everyone who has been watching that series.
Do I need a "follows" entity, or can I just use a watch event? You can use
the watch event - and for VOD that's the natural choice. You already emit a
Watched Episode (or watch_series) event when someone plays an episode; point
the alert at that event and Joryio notifies everyone who watched the series
recently. No follow records to create or maintain. Use a follows entity only
if you want an explicit opt-in "follow this show" list that's separate from
watching.
Is it the same as restock or different? The audience side here uses
behavior instead of a waitlist (that's the audienceSource: "event" option),
and the trigger differs too: a new episode isn't a 0 → >0 stock change,
it's an episode count going up (or a "latest episode" id changing). Same
engine, two different knobs.
What you need - just one entity:
-
Parent entity
serieswith:- a display field like
title, - a numeric field
episodeCount(or a stringlatestEpisodeId). - No child entity.
- a display field like
-
On
series, set:{
"relationshipAlert": {
"enabled": true,
"triggerMode": "increase",
"field": "episodeCount",
"audienceSource": "event",
"intentEvent": "Watched Episode",
"intentRefProperty": "series_id",
"intentLookbackDays": 90,
"eventName": "new_episode",
"cooldownDays": 1
}
}(Prefer
"triggerMode": "changed"with"field": "latestEpisodeId"if you track the newest episode by id rather than a running count.) -
The connection is the event property. Your player already sends, on each play, something like:
{ "event": "Watched Episode", "series_id": "S-42", "episode": 12 }intentRefProperty: "series_id"is what links a watch to theseriesrecord- it must match the record's id or primary-key value (
S-42).
- it must match the record's id or primary-key value (
-
How to trigger: when your catalog ingestion bumps
series/S-42episodeCount8 → 9(a normal record update), Joryio queries who firedWatched Episodewithseries_id = S-42in the last 90 days and emits anew_episodeevent for each of them. -
Build a journey/campaign entered by
new_episode:New episode of {{ event.name }} is out now. Pick up where you left off →
If you prefer an explicit follow list instead of watch behavior, keep
triggerMode: "increase" but use the child-entity audience: add a
series_follows entity (seriesId, userId, User Link → userId), set
audienceSource: "child_entity", childEntity: "series_follows",
childRefField: "seriesId", and add a follow row when someone taps Follow.
Restock vs. new-episode at a glance
| Concert restock | New episode (VOD) | |
|---|---|---|
| Entities needed | parent + child waitlist | just the parent |
| Audience | ticket_waitlist rows (opt-in) | Watched Episode event (behavior) |
audienceSource | child_entity | event |
| Watched field | available (stock) | episodeCount / latestEpisodeId |
triggerMode | restock (0 → >0) | increase / changed |
| Emitted event | back_in_stock | new_episode |
Excluding people who already acted
If you send some time after release, you probably don't want to nudge people who already watched the new episode (or already bought the restocked item). There are two layers, and you'll often use both:
1. Skip them at fire time - excludeEvent
The alert can drop, from the audience, anyone who already fired an event for this transition's new value. For the VOD example, add to the config:
{
"excludeEvent": "Watched Episode",
"excludeRefProperty": "series_id",
"excludeValueProperty": "episode",
"excludeLookbackDays": 30
}
Now when episode 9 drops, Joryio notifies people who watched the series but
subtracts anyone who already watched episode 9 (series_id = this series
and episode = the new value). excludeValueProperty is what pins it to the
specific new episode - for that to line up, the field's new value should match
the id your watch event carries (this is cleanest with triggerMode: "changed"
latestEpisodeId, or when episode numbers equal the count). Works for restock too - setexcludeEventto your purchase event to skip recent buyers.
2. Re-check at send time - the accurate answer for delayed sends
Fire-time exclusion only knows who watched up to the moment the alert fires. If you deliberately wait 2 days before sending, people will watch during those two days - and only a send-time check can catch them. Do it in the journey:
- Entry trigger:
new_episode. - Wait 2 days.
- Condition / behavior split: has NOT done
Watched Episodewhereepisode={{ trigger.new_value }}in the last 2 days → only this branch continues to the send.
This re-evaluates each person the moment before sending, so anyone who caught up
during the wait is filtered out. Use excludeEvent (layer 1) to keep the initial
audience tight, and the journey condition (layer 2) to make a delayed send exact.
Using the alert event
Whatever you name it, eventName behaves like any other event in Joryio:
- It appears in the event picker as a journey/campaign entry trigger.
- Its properties (
record_id,new_value,field, plus the record's scalar fields and anamelabel) are available asevent.*in Liquid. - Sends still honor consent, suppression, and quiet-time like every other channel send.
How it fires (under the hood)
The transition is detected on the entity update path and fanned out off the
hot path, so a slow audience never blocks the record write. Notifications are
deduplicated per (contact, record) for cooldownDays, and a child row only
counts once even if several rows resolve to the same contact.
Bulk imports
Bulk record imports don't fire alerts by default - so a first-time load never
pages everyone. Opt in per import by sending triggerAlerts: true on the bulk
endpoint. When enabled and the entity has a business primary key, the import
upserts-and-diffs: records that already exist are updated and their transitions
fire (e.g. a nightly stock or episodeCount feed), while brand-new rows only
fire changed-mode alerts (numeric modes need a prior value to transition from).
Getting set up
You can wire the two entities and the relationshipAlert config by hand today.
The AI assistant will also be able to scaffold the
whole thing - create the child follows/waitlist entity, set the config, and build
the notify journey - from a single request.