An adapter turns a notification decision into a side effect: an SMTP session, an HTTP POST, a process, a Telegram message. jaque has four, one per contact type, and none of them is trusted to remember anything. Every decision is written to the event log before an attempt is made and its outcome is written after, so whoever delivers can be killed, restarted or replaced by another process and resume from the log with no duplicate that already landed and no notification still owed. Delivery is at least once, by design, and idempotent by notification ID.

Status: the path is tested end to end against a fake receiver, including a flaky one, but no deployment has paged a human yet; see the status page. JAQUE_TELEGRAM_TOKEN is the environment form of -telegram-token by the general rule that every flag has a JAQUE_ prefixed variable, documented in CLI flags.

1. The four adapters

Adapter Contact type Mechanism
email email net/smtp with STARTTLS and optional PLAIN auth, or the system mail binary
webhook webhook HTTP POST to the contact's url
command command a top-level command template run as a process, the Nagios model
telegram telegram the Bot API's sendMessage to the contact's chat_id

The command adapter is the primitive: it is how Nagios has always delivered, and it is what the email adapter falls back to when no relay is configured. Anything not listed is reached through webhook or command; there is no plugin interface for adapters. The contact side of this table is specified in Contacts and policies.

2. Durability through the log

A notification has three events and a stable identity.

A notification has three events: NotificationRequested is appended before the adapter is called, and either NotificationSent or NotificationFailed is appended afterengine decidesdeliverer attemptslogNotifyDecisionNotificationRequestedappended BEFOREthe attemptadapter.Send(...)NotificationSentappended on successorNotificationFailedappended onpermanent failureThe request is on the log before any adapter runs, so a crash mid-send is recoverable.
Text version
engine decides            deliverer attempts            log
--------------            ------------------            ---
NotifyDecision  ------->  NotificationRequested  ----->  appended BEFORE the attempt
                          adapter.Send(...)
                          NotificationSent       ----->  appended on success
                      or  NotificationFailed     ----->  appended on permanent failure

NotificationRequested is appended before any adapter is called. The deliverer, whichever process that is, folds the log on startup to find every NotificationRequested without a terminal NotificationSent or NotificationFailed and resumes exactly those. The notification ID is derived from the object, the kind, the level and the instant the problem began, never from the current time, so a re-notification of the same problem and a replayed request after a crash produce the same ID and are deduplicated against it.

The guarantee is at least once. If ownership of delivery moves between processes while an attempt is in flight, the new owner may repeat a send that the old one had started; it will never skip one. Retries use backoff and stay inside one delivery shard, so two attempts for one ID never run concurrently.

3. Where delivery runs: -notify-delivery

The engine that owns an object always decides whether to notify and writes that decision to the log. Who performs the side effect is a separate switch.

-notify-delivery Who delivers
embedded (default) the same process that decided, under -target all or -target engine
external one or more -target notifier processes, each pulling its share of open notifications from the log

Under external, several notifiers split the work by object ownership over their own membership bucket; Cluster and coordination describes the split. A -target notifier process requires -notify-delivery external on the engine upstream, or it finds nothing to deliver.

In either mode delivery is sharded by object across -notify-workers goroutines (default 4), each with a bounded queue of -notify-queue notifications (default 64). A slow receiver fills its shard's queue and then blocks the notifier's log follower; nothing is dropped for being slow. The block is visible as jaque_notifier_blocked_total counting up and jaque_eventlog_consumer_lag{follower="notifier"} growing, and jaque_notifier_pending shows how many notifications sit in the shards.

4. SMTP

-smtp-host, -smtp-port (default 25), -smtp-user, -smtp-pass, -smtp-from

With -smtp-host set, email contacts are delivered over SMTP with STARTTLS; -smtp-user empty disables authentication. With -smtp-host empty, email contacts are delivered by running the system mail binary through the command adapter, which suits a host that already has local mail delivery configured and nothing else.

5. Telegram

A telegram contact carries only a chat_id. The bot token is one per jaque instance and is a process secret, set once via -telegram-token or JAQUE_TELEGRAM_TOKEN, the same posture as SMTP credentials. With the token empty, every telegram contact fails permanently rather than silently.

To obtain a token, talk to @BotFather, send /newbot and keep the token it returns. To find a chat_id, message the bot (or add it to a group), then read https://api.telegram.org/bot<token>/getUpdates: the chat.id on the message you sent is the value for the contact.

6. Security considerations

Every credential an adapter needs -- the SMTP password, the Telegram bot token -- is a process-level flag or environment variable and never part of the CUE config, so a configuration can be reviewed and shared without carrying a secret. The command adapter runs processes on the delivering host with the arguments the commands map specifies; that map is trusted configuration. Webhook and Telegram deliveries are outbound HTTPS to operator-chosen endpoints; the notifier opens no listener of its own beyond /metrics on -listen.