Skip to main content

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:

FieldMeaningExample
enabledTurn the alert ontrue
triggerModeWhat counts as a fire on field (see below)"restock"
fieldThe parent field whose transition triggers the alert"available"
eventNameThe per-user event to emit"back_in_stock"
cooldownDaysPer-(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.

  1. Parent entity concert_tickets with a numeric field available.

  2. Child entity ticket_waitlist with fields ticketId (the show/record it references) and email. Set its User Link to email.

  3. On concert_tickets, set:

    {
    "relationshipAlert": {
    "enabled": true,
    "triggerMode": "restock",
    "field": "available",
    "childEntity": "ticket_waitlist",
    "childRefField": "ticketId",
    "eventName": "back_in_stock",
    "cooldownDays": 30
    }
    }
  4. When someone taps "Notify me", your app/site adds a row to ticket_waitlist: { ticketId: "SHOW-A", email: "fan@example.com" }.

  5. When concert_tickets/SHOW-A available goes 0 → 50, Joryio emits a back_in_stock event 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:

  1. Parent entity series with:

    • a display field like title,
    • a numeric field episodeCount (or a string latestEpisodeId).
    • No child entity.
  2. 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.)

  3. 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 the series record

    • it must match the record's id or primary-key value (S-42).
  4. How to trigger: when your catalog ingestion bumps series/S-42 episodeCount 8 → 9 (a normal record update), Joryio queries who fired Watched Episode with series_id = S-42 in the last 90 days and emits a new_episode event for each of them.

  5. 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 restockNew episode (VOD)
Entities neededparent + child waitlistjust the parent
Audienceticket_waitlist rows (opt-in)Watched Episode event (behavior)
audienceSourcechild_entityevent
Watched fieldavailable (stock)episodeCount / latestEpisodeId
triggerModerestock (0 → >0)increase / changed
Emitted eventback_in_stocknew_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 - set excludeEvent to 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:

  1. Entry trigger: new_episode.
  2. Wait 2 days.
  3. Condition / behavior split: has NOT done Watched Episode where episode = {{ 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 a name label) are available as event.* 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.