Channels and connectors¶
Cue decides and prepares; delivery is yours to plug in. A channel is a named
route — push, sms, email, ops-alerts… — and its connector (the provider
setting) is what actually hands the message over. Rules, broadcasts and recipient
addresses refer to channels by name, so you can change how a route is delivered without
touching any rule.
The most flexible connector is webhook: Cue POSTs the fully rendered, signed message to
your service, and your service sends it however you like.
[channels.push]
provider = "webhook"
url = "https://notifications.internal.example/push"
# secret from CUE_CHANNELS__PUSH__SECRET
[channels.sms]
provider = "twilio" # or a bundled connector
account_sid = "AC…"
from_number = "+15550100"
Equivalent environment variables: CUE_CHANNELS__PUSH__PROVIDER=webhook,
CUE_CHANNELS__PUSH__URL=…. Configuration is validated at start-up;
cuectl check prints the configured channels.
Addresses¶
A recipient has any number of addresses per channel — every phone, browser and tablet:
POST /v1/recipients/user-42/addresses
{"channel": "push", "value": "<FCM registration token>", "label": "Pixel 9"}
A message goes to all enabled addresses of the first channel that works. When a provider reports an address as permanently invalid (unregistered token, blocked bot, unreachable number), the address is disabled with a reason and skipped from then on; re-adding it re-enables it.
Fallback and retries¶
For each planned channel, in order:
- Success on any address → message
sent. - Transient error (timeouts, HTTP 429/5xx) → the delivery job is retried with
exponential backoff (honouring
Retry-After). On the last attempt Cue falls back to the next channel instead. - Permanent error → the next channel is tried immediately.
If no channel succeeds the message is failed with the reasons in status_reason.
Bundled connectors¶
fcm — Firebase Cloud Messaging¶
Requires pip install 'cue-notify[fcm]'. Address: FCM registration token.
| Option | Default | |
|---|---|---|
credentials_file / credentials_json |
— | Service-account key (exactly one). |
project_id |
from the key | |
android_priority |
high |
normal or high. |
sound |
default |
null for silent. |
ttl_seconds |
— | Android time-to-live. |
The push data payload contains your template's data, url, plus
cue_message_id and cue_tracking_token for engagement tracking.
twilio — SMS¶
Address: E.164 phone number.
| Option | |
|---|---|
account_sid, auth_token |
Credentials. |
from_number or messaging_service_sid |
Sender. |
status_callback |
Optional Twilio status webhook URL. |
include_title |
Prefix the body with the title (default false). |
smtp — e-mail¶
Address: e-mail address. Uses only the standard library.
| Option | Default |
|---|---|
host, port |
—, 587 |
security |
starttls (tls for implicit TLS on 465, none) |
username, password |
— |
from_address, from_name, reply_to |
— |
Sends a text part, plus an HTML part when the template has html. With
api.public_url set, messages in categories people may leave carry
List-Unsubscribe and List-Unsubscribe-Post headers for one-click unsubscribe.
telegram — Telegram bots¶
Address: the chat id. Option: bot_token, disable_notification, link_preview,
api_url (for a self-hosted Bot API server).
webhook — HTTP¶
Posts JSON to url, or — when url is not set — to the address itself (useful for
per-user Slack, Discord or Teams incoming webhooks).
Per-recipient URLs are untrusted input, so by default they must use https, must
resolve only to public IP addresses (no localhost, private ranges or cloud metadata
endpoints), and the connection is pinned to the address that was checked. Response bodies
from such URLs are never stored. allow_http and allow_private_networks relax this for
internal deployments; a fixed url is operator configuration and is trusted.
The body:
{
"id": "0192…",
"channel": "email",
"address": "ann@example.com",
"locale": "en",
"content": {"title": "Your order shipped", "body": "…", "url": "…", "thread": "order-A1"},
"metadata": {"cue_message_id": "0192…", "cue_tracking_token": "…"},
"links": {"preferences": "https://notify.example.com/preferences/…",
"unsubscribe": "https://notify.example.com/preferences/…/unsubscribe?category=marketing"}
}
links is empty unless api.public_url is set; unsubscribe is only
present for categories people may leave. Put it in a List-Unsubscribe header if your
service sends e-mail.
With secret set, requests carry:
Cue-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
Idempotency-Key: <message id>
Verify the signature and reject timestamps older than a few minutes.
console¶
Logs messages instead of sending them. For development and demos.
Your own connector¶
Any package can add a connector — see custom channels.