Skip to main content
These plugins add alert channels for services the built-in channels do not cover. Each is a small out-of-process program on the public plugin contract: it runs in a locked-down container, receives only the event and its own config, and gets the credential per call. They have no private channel to the control plane. Already built in, so no plugin exists for them: Slack, Discord, Telegram, Teams, PagerDuty, Opsgenie, ntfy, Pushover, Gotify, Mattermost, Rocket.Chat, Google Chat, Webex, Lark, email, Resend and generic webhooks. The built-in email channel sends through the control plane’s own SMTP settings, so a per-channel SMTP relay is not offered as a plugin. Plugins need APP_EXPERIMENTAL=plugins (see plugins).

The plugins

Brand marks: the manifest has no icon field yet, so the dashboard shows the monogram tile for these. A thesvg mark can be wired when the marketplace listing carries one.

Config fields

Config is plugin-wide and non-secret, given at install with --config <file>. Anything secret goes in the channel target instead. allow_http is for a lab server on plain http. Without it, any non-https address is refused. An address with embedded credentials is always refused.

Use one

Build, sign and trust (for a package you built yourself), install, then add a channel:
Alert rules then deliver through plugin:twilio-sms like any other channel, with the same delivery history.

DNS provider plugins

cloudflare-dns is the first plugin of kind dns_provider: it lists zones and creates, replaces, deletes and verifies records in Cloudflare. It needs no config and no credential of its own. The control plane hands it the Cloudflare DNS token you already stored under Settings on every call, and the plugin never keeps or logs it. Outbound host: api.cloudflare.com. By default the built-in client writes DNS records. To use the plugin instead, build, install, approve and enable it, then:
  • Plugins page: on the plugin’s row, press Test connection (it lists your zones with the stored token), then Use for DNS records.
  • Domains page: pick the plugin under DNS record writer in the domain automation card.
  • CLI: levelrail-cli plugins test cloudflare-dns, then levelrail-cli plugins dns-provider cloudflare-dns. plugins dns-provider --off returns to the built-in client, and with no argument it shows the current choice.
  • API: POST /api/v1/plugins/{name}/test and GET or PUT /api/v1/settings/dns-provider-plugin (writes need root).
Then the domain Fix button, go-live, add-domain automation and the records editor all write through the plugin. There is no automatic fallback: if the plugin is down or paused, the fix reports it, so you always know which code wrote a record. Every write is verified afterwards, and a record the plugin accepted but cannot see is reported as unconfirmed and stays undoable. The Cloudflare proxy toggle is not applied to records the plugin writes yet. See ADR 038.

Self-hosted services

Zulip and Matrix run on your own server, so the shipped manifest lists a placeholder host (zulip.example.com, matrix.example.com). Hosts a plugin declares are shown at approval but are not yet enforced per host (see plugins), so the placeholder gives the plugin network access and the plugin itself only ever calls the base_url or homeserver you configure. To declare your real host, copy the manifest, change outbound_hosts, sign it with your own key and install that copy. The image digest stays the same.

Failure handling

Every plugin maps service answers the same way: 401 and 403 are permanent authentication failures, 429 waits out a short Retry-After and retries (a longer wait is reported as retryable), 5xx and network errors retry twice and then report retryable, and other 4xx are permanent with a short response excerpt. The credential is redacted from every message. Services that answer HTTP 200 with an error code in the body (DingTalk, WeCom) are treated as failures too.

Building and signing

scripts/plugins-build.sh build|sign|all [name...] compiles each plugin for Linux, builds an image named <registry>/levelrail-plugin-<name>:local and writes dist/plugins/<name>/plugin.yaml (digest pinned) and plugin.sig. Without PLUGINS_KEY it signs with a throwaway key that exists for local tests only. It never pushes an image. Publishing is the maintainer’s step: push each image, set PLUGINS_DIGEST_<NAME> (or write dist/plugins/<name>/digest) to the registry digest, and run sign with PLUGINS_KEY pointing at the real publisher key.

Not yet

  • Plugin icons and a marketplace listing for these plugins.
  • DingTalk signature security and Matrix end-to-end encrypted rooms.
Last modified on October 20, 2026