Notification Engine
Receive events on a webhook, drop duplicates, keep only the critical and high severity ones, format a one-line message, and POST it to a notification endpoint, retrying when the endpoint fails.
Components: http_server input → dedupe (memory cache) → mapping
(severity filter) → mapping (message format) → http_client output with
retries.
Needs: an HTTP endpoint you control that accepts a JSON POST and answers
2xx. The job sends it the formatted message only.
The body is {"text": ...} plus two fields, the shape Slack incoming webhooks
accept, but no message was sent to Slack, email or any other real service.
Point the job at your real receiver and check that messages arrive before you
depend on it.
Complete job
name: notification-engine
type: pipeline
selector:
match_labels:
pipeline_role: notify
config:
input:
http_server:
address: "127.0.0.1:8088"
path: /events
allowed_verbs: [POST]
pipeline:
processors:
- dedupe:
cache: seen_events
key: '${! json("id") }'
- mapping: |
root = if ["critical", "high"].contains(this.severity) {
this
} else { deleted() }
- mapping: |
root.text = "[%s] %s: %s (event %s at %s)".format(
this.severity.uppercase(), this.source, this.message,
this.id, this.ts
)
root.event_id = this.id
root.severity = this.severity
cache_resources:
- label: seen_events
memory: {}
output:
http_client:
url: "https://RECEIVER_HOST/notify"
verb: POST
headers:
Content-Type: application/json
retries: 5
retry_period: 200ms
max_in_flight: 1
The selector sends the job to a node labelled pipeline_role: notify; set
that label on your node, or change the selector to a label it already carries.
With no matching node, the job is stored and never runs.
Replace https://RECEIVER_HOST/notify with your endpoint. The webhook listens
on 127.0.0.1:8088 of the node that runs the job, so only processes on
that node can reach it; change address to accept events from elsewhere.
Send it test events
Save these eight events on the node as events.json. Two are critical, one is
high, one repeats an earlier id, and the rest are below the threshold:
[
{
"id": "evt-001",
"severity": "critical",
"source": "pump-4",
"message": "Discharge pressure above limit",
"ts": "2026-09-17T12:00:00Z"
},
{
"id": "evt-002",
"severity": "info",
"source": "pump-4",
"message": "Heartbeat",
"ts": "2026-09-17T12:00:05Z"
},
{
"id": "evt-003",
"severity": "high",
"source": "chiller-2",
"message": "Coolant temperature rising",
"ts": "2026-09-17T12:01:00Z"
},
{
"id": "evt-004",
"severity": "low",
"source": "gateway-1",
"message": "Clock skew 40ms",
"ts": "2026-09-17T12:01:30Z"
},
{
"id": "evt-001",
"severity": "critical",
"source": "pump-4",
"message": "Discharge pressure above limit",
"ts": "2026-09-17T12:00:00Z"
},
{
"id": "evt-005",
"severity": "warning",
"source": "chiller-2",
"message": "Filter change due",
"ts": "2026-09-17T12:02:00Z"
},
{
"id": "evt-006",
"severity": "critical",
"source": "gateway-1",
"message": "Uplink lost",
"ts": "2026-09-17T12:03:00Z"
},
{
"id": "evt-007",
"severity": "info",
"source": "gateway-1",
"message": "Uplink restored",
"ts": "2026-09-17T12:04:00Z"
}
]
Deploy the job, then post each event to the webhook from the node. This needs
curl and jq:
expanso-cli job deploy notification-engine.yaml
jq -c '.[]' events.json | while IFS= read -r ev; do
curl -s -o /dev/null -w '%{http_code}\n' -X POST \
-H 'Content-Type: application/json' --data "$ev" \
http://127.0.0.1:8088/events
done
Each post should print 200.
Check it
Your receiver should get exactly three messages. Each body is a JSON object
with event_id, severity and text. The text values the test receiver
recorded were:
[CRITICAL] pump-4: Discharge pressure above limit (event evt-001 at 2026-09-17T12:00:00Z)[HIGH] chiller-2: Coolant temperature rising (event evt-003 at 2026-09-17T12:01:00Z)[CRITICAL] gateway-1: Uplink lost (event evt-006 at 2026-09-17T12:03:00Z)
This job keeps running and listening until you stop it.
What the run proved
- 8 events were accepted. Exactly the 3 critical or high events were delivered, and the repeated event was delivered once.
- The receiver answered
503to the first two attempts. Both were retried, for 5 attempts in total, and every message arrived. - The job was deployed through Expanso Cloud to a labelled node running
expanso-edgev2.1.21. The receiver was a local test server on the same host.
Limits
- Slack, email, Discord and PagerDuty were not contacted. There is no
dedicated component for any of them; they are reached with
http_client. - The
memorydedupe cache is lost when the job restarts, so an event resent after a restart is delivered again. The RSS feed engine shows afilecache that survives restarts.
Related
- Build by Job: the task-to-components matrix
http_serverinput andhttp_clientoutput