Skip to main content

Ephemeral Alerts

Client-side alerts that run custom evaluation logic in your application. You define the condition as a function — the SDK handles timing, recovery, and notification delivery. Use ephemeral alerts when RelayX alerts built-in operators aren't enough.

Access via app.alert.

warning

Ephemeral alerts only evaluate data while your application is running. If your app stops, the evaluator stops — no alerts will fire until it's restarted.

API

createEphemeral()

Create a client-side alert with custom evaluation logic.

await app.alert.createEphemeral(params)

Returns the alert object with listen(), setEvaluator(), and stop() methods attached

const alert = await app.alert.createEphemeral({
name: "custom_check",
type: "EPHEMERAL",
config: {
topic: {
source: "TELEMETRY",
device_ident: "sensor_01",
last_token: "cpu_usage",
},
duration: 30,
recovery_duration: 15,
recovery_eval_type: "VALUE",
cooldown: 60,
},
notification_channel: [channelId],
});

Example response:

{
"name": "custom_check",
"type": "EPHEMERAL",
"config": {
"topic": {
"source": "TELEMETRY",
"device_ident": "sensor_01",
"last_token": "cpu_usage"
},
"duration": 30,
"recovery_duration": 15,
"recovery_eval_type": "VALUE",
"cooldown": 60
},
"notification_channel": ["69cb22a4ed4d9fe786145d01"]
}

The returned object also has listen(), setEvaluator(), and stop() methods attached.

Topic Config

FieldTypeDescription
sourcestring"TELEMETRY", "COMMAND", or "EVENT"
device_identstringDevice identifier, or "*" for all devices
last_tokenstringMetric/command/event name, or "*" for all

Recovery Eval Type

The recovery_eval_type field controls how the alert resolves after firing. Defaults to "VALUE".

ValueBehavior
"VALUE"The evaluator must continuously return false for the full recovery_duration before the alert resolves. Data must keep flowing — silence alone won't resolve it.
"TIMER"A countdown starts when the alert is in a fired or acknowledged state. If no data arrives for the recovery_duration, the alert resolves automatically. If data does arrive and the evaluator returns false for the recovery_duration, it also resolves normally.

Use "VALUE" when you need certainty that the condition has genuinely cleared — the evaluator must actively confirm it.

Use "TIMER" when silence itself means the problem is gone — for example, if a device stops sending error events, the alert should auto-resolve.

setEvaluator()

Define the custom evaluation function. Called on every incoming data point — return true to fire, false to clear. Only one evaluator can be attached per ephemeral alert — calling this again will throw an error.

alert.setEvaluator(fn)

The function receives a rolling state object. As new data arrives, the rolling state is updated — it always contains the latest value for each metric. Previous metrics are preserved even when new ones arrive.

For example, if temperature arrives at t=1 and humidity arrives at t=30, the rolling state at t=30 looks like:

{
"sensor_01": {
"temperature": { "value": 22.5, "timestamp": 1774690200000 },
"humidity": { "value": 61.3, "timestamp": 1774690229000 }
}
}

The evaluator is called every time a new data point arrives, with the full rolling state.

alert.setEvaluator((rollingState) => {
const temp = rollingState["sensor_01"]?.temperature?.value;
const humidity = rollingState["sensor_01"]?.humidity?.value;

// fire if both conditions are met
return temp > 80 && humidity > 70;
});

listen()

Start the evaluator and subscribe to alert state changes.

await alert.listen(callbacks)
CallbackDescription
onFire / on_fireAlert condition met — alert has fired
onResolved / on_resolvedAlert condition cleared — alert resolved
onAck / on_ackA specific device's alert was acknowledged
onAckAll / on_ack_allAll devices' alerts were acknowledged
onError / on_errorEvaluator threw an error
await alert.listen({
onFire: (data) => console.log("FIRED:", data),
onResolved: (data) => console.log("RESOLVED:", data),
onAck: (data) => console.log("ACK:", data),
onAckAll: (data) => console.log("ACK_ALL:", data),
onError: (err) => console.error("Error:", err),
});

stop()

Stop the evaluation engine and reset its state.

await alert.stop()

updateEphemeral()

Update an ephemeral alert's configuration.

await app.alert.updateEphemeral(params)
const updated = await app.alert.updateEphemeral({
id: alertId,
config: { duration: 60 },
});

Example response:

{
"name": "custom_check",
"type": "EPHEMERAL",
"config": {
"topic": {
"source": "TELEMETRY",
"device_ident": "sensor_01",
"last_token": "cpu_usage"
},
"duration": 60,
"recovery_duration": 15,
"recovery_eval_type": "VALUE",
"cooldown": 60
},
"notification_channel": ["69cb22a4ed4d9fe786145d01"]
}

The following operations work the same for both RelayX and ephemeral alerts. State changes (ack, mute, unmute) are globally synced — if one App SDK instance acknowledges or mutes an alert, all other instances see the change.

delete()

Permanently remove an alert.

await app.alert.delete(alertId)
await app.alert.delete(alertId);

list()

Retrieve all alerts.

await app.alert.list()
const alerts = await app.alert.list();

Example response:

[
{
"name": "high_temp",
"type": "THRESHOLD",
"metric": "temperature",
"config": {
"scope": { "type": "DEVICE", "value": "69ca39e4ed4d9fe786145b48" },
"operator": ">",
"value": 85,
"duration": 300,
"recovery_duration": 120
},
"notification_channel": ["69cb22a4ed4d9fe786145d01"]
}
]

get()

Retrieve a single alert by name.

await app.alert.get(name)
const alert = await app.alert.get("high_temp");

Example response:

{
"name": "high_temp",
"type": "THRESHOLD",
"metric": "temperature",
"config": {
"scope": { "type": "DEVICE", "value": "69ca39e4ed4d9fe786145b48" },
"operator": ">",
"value": 85,
"duration": 300,
"recovery_duration": 120,
"cooldown": 600
},
"notification_channel": ["69cb22a4ed4d9fe786145d01"]
}

history()

Query the history of alert state changes.

await app.alert.history(params)
ParameterTypeDescription
namestringThe alert name
device_identsstring[]Device identifiers to query
startstringStart time (ISO 8601)
endstring(optional) End time (ISO 8601). Defaults to now
const history = await app.alert.history({
name: "custom_check",
device_idents: ["sensor_01"],
start: "2026-03-01T00:00:00.000Z",
end: "2026-03-02T00:00:00.000Z",
});
History retention

Alert history retention depends on your plan. For example, the free plan retains 3 days of history. Queries outside your retention window will return empty results.

ack()

Acknowledge a specific device's alert.

await app.alert.ack(params)
ParameterTypeDescription
device_identstringThe device identifier
alert_idstringThe alert ID
acked_bystringWho is acknowledging (e.g., operator name)
ack_notesstring(optional) Notes about the acknowledgement
await app.alert.ack({
device_ident: "sensor_01",
alert_id: alertId,
acked_by: "operator_jane",
ack_notes: "Investigating issue",
});

ackAll()

Acknowledge an alert across all devices.

await app.alert.ackAll(params)
ParameterTypeDescription
alert_idstringThe alert ID
acked_bystringWho is acknowledging
ack_notesstring(optional) Notes about the acknowledgement
await app.alert.ackAll({
alert_id: alertId,
acked_by: "operator_jane",
ack_notes: "Maintenance window",
});

mute()

Silence an alert so it stops delivering notifications.

await app.alert.mute(params)
ParameterTypeDescription
idstringThe alert ID
mute_config.typestring"FOREVER" or "TIME_BASED"
mute_config.mute_tillstring(TIME_BASED only) ISO 8601 timestamp to unmute at
Mute TypeBehavior
"FOREVER"The alert stays muted until explicitly unmuted via unmute(). The alert still evaluates but notifications are suppressed.
"TIME_BASED"The alert is muted until the specified mute_till timestamp, then automatically unmutes.
// mute forever
await app.alert.mute({
id: alertId,
mute_config: { type: "FOREVER" },
});

// mute until a specific time
await app.alert.mute({
id: alertId,
mute_config: {
type: "TIME_BASED",
mute_till: "2026-04-01T00:00:00.000Z",
},
});

unmute()

Re-enable a muted alert.

await app.alert.unmute(alertId)
await app.alert.unmute(alertId);