Skip to content

Events and rules

Events

An event is a fact about a recipient: order.shipped, payment.failed, user.signed_up. Send events from your backend with POST /v1/events:

Field Required Description
name yes Letters, digits and ._-:/. Dots namespace nicely: billing.invoice.overdue.
recipient yes An external id ("user-42") or a profile to create/update inline.
data no Any JSON object. Available to rule conditions and templates.
occurred_at no When it happened (defaults to receipt time).
idempotency_key recommended Retries with the same key are ignored.
source no The producing system (billing, crm…). Idempotency keys are unique per source. Defaults to the API key's name.

Ingestion is fast and durable: the event and its processing job are written in one transaction, then evaluated asynchronously by the worker. The recipient is created automatically if it does not exist yet. An inline profile may set locale, timezone and attributes; adding addresses inline additionally requires the recipients:write scope, because addresses decide where messages are delivered.

If an event cannot be processed even after retries, it is marked failed with the reason in error.

GET /v1/events/{id} returns the event with its outcome — one entry per rule that was considered:

[
  {"rule": "vip-sms", "matched": false, "reason": "condition_false"},
  {"rule": "default-push", "matched": true, "status": "queued", "message_id": "…"}
]

Rules

A rule connects an event to a template:

{
  "key": "large-payment-alert",
  "name": "Alert on large payments",
  "event": "payment.*",
  "condition": {">": [{"var": "event.data.amount"}, 1000000]},
  "template": "large-payment",
  "category": "transactional",
  "channels": ["push", "sms"],
  "priority": 10,
  "stop_processing": true,
  "delay_seconds": 0,
  "cooldown_seconds": 600,
  "rollout_percent": 100
}
Field Meaning
event Event name, with shell-style wildcards (payment.*, *).
condition JSON Logic over {event, recipient}. null always matches.
channels Channels to try, in fallback order.
priority Higher is evaluated first.
stop_processing When this rule matches, lower-priority rules are skipped (default true). Set false to let several rules fire for one event.
delay_seconds Wait before sending (e.g. "remind me in an hour").
cooldown_seconds At most one message from this rule per recipient per window.
importance low, normal (default), high or critical — see importance and expiry.
expires_after_seconds Drop the message if it cannot be delivered within this time.
digest Group matching events into one message per window instead of one per event — see digests.
rollout_percent Deterministic percentage rollout; the same recipient always gets the same answer, and raising the percentage only adds people.
enabled Disable without deleting.

Writing conditions

The condition sees this document:

{
  "event": {"name": "payment.received", "source": "billing", "data": {…}, "occurred_at": "…"},
  "recipient": {"external_id": "u1", "locale": "en", "timezone": "Europe/Berlin", "attributes": {…}}
}

Some patterns:

{"and": [
  {">=": [{"var": "event.data.amount"}, 100]},
  {"in": [{"var": "recipient.attributes.plan"}, ["pro", "enterprise"]]}
]}
{"!": {"var": "recipient.attributes.onboarded"}}
{"some": [{"var": "event.data.items"}, {"==": [{"var": "category"}, "electronics"]}]}

All standard JSON Logic operators are supported (var, missing, if, comparison, arithmetic, map/filter/reduce/all/some/none, in, cat, substr, …), plus starts_with, ends_with, lower and upper. Conditions are validated when saved, and are evaluated safely — no code execution, bounded nesting.

Dry runs

POST /v1/rules/evaluate evaluates rules and delivery policy for a hypothetical event without storing or sending anything. Use it in CI for your rule definitions, or to answer "would this user get that?". Digest rules report "reason": "digested".