Using the CLI
export NOTIFLOW_BOT_TOKEN=123456789:AAHdqTcv...export NOTIFLOW_CHAT_ID=-1001234567890
notiflow send --message "deploy finished"notiflow send -m "deploy finished" -c @my_channelThe message body can come from four places:
notiflow send --message "inline text"notiflow send --message-file release-notes.mdnotiflow send --stdin < build.log./deploy.sh | notiflow send --stdinnotiflow send -m - # `-` is a synonym for --stdin--stdin and --message-file behave like --message: verbatim text, no placeholder
substitution and no escaping. A trailing newline from a pipeline is stripped, because
almost nobody means to send one.
To use placeholders, pass a template instead:
notiflow send --template '{{.StatusEmoji}} deployed `{{.ShortSha}}` to prod'Outside a workflow the repository, branch, commit and author come from the local git checkout, so the same template works in both places.
Statuses and filtering
Section titled “Statuses and filtering”--status defaults to success. Wire it to what actually happened:
if ./deploy.sh; then status=success; else status=failure; finotiflow send --status "$status" --template '{{.StatusEmoji}} deploy: {{.Status}}'--notify-on filters, exactly as in the Action:
notiflow send --status "$status" --notify-on failure,cancelledA filtered run exits 0, prints skipped: status not in notify_on, and never opens a
connection.
Rewrite a message you sent earlier, typically to turn “deploying…” into a result:
id=$(notiflow send -m "deploying…" --json | jq -r .message_id)./deploy.shnotiflow edit --message-id "$id" -m "deploy finished"--silent and --thread-id are dropped on an edit; Telegram rejects them there.
render
Section titled “render”Renders a template and prints it. No token, no network — the fast way to work out why your escaping looks wrong:
notiflow render --template 'branch `{{.Branch}}`' --explain# template: message_template# parse_mode: MarkdownV2# truncated: falsebranch `feature/fix\-thing`--set overrides one placeholder, which is how you test a template against values you do
not currently have:
notiflow render -T '{{.StatusEmoji}} {{.Workflow}} on {{.Repo}}' \ --status failure --set Workflow=Nightly --set Repo=owner/namewhoami
Section titled “whoami”Checks the token against getMe:
notiflow whoami# Notiflow (@notiflow_bot, id=123456789)
notiflow whoami --json | jq .usernameExits 1 with Telegram’s own description when the token is rejected — the first thing to run when a workflow reports 401.
Output formats
Section titled “Output formats”notiflow send -m hi # one human-readable linenotiflow send -m hi --json # a single JSON objectnotiflow send -m hi --output github # $GITHUB_OUTPUT entries and a step summaryThe JSON object is stable and safe to parse:
{"ok":true,"skipped":false,"message_id":4242,"http_status":200,"error":null, "chat_id":"-1001234567890","attempts":1,"dry_run":false}--quiet suppresses the human line but leaves --json and --output github intact, so a
script can stay silent without losing its machine-readable result.
Dry runs
Section titled “Dry runs”--dry-run does everything except the request, and prints the exact JSON body that would
have been posted:
notiflow send -m "check me" --dry-runThat is the quickest way to confirm what --parse-mode, --silent and --thread-id are
really doing to the request.
Timeouts and retries
Section titled “Timeouts and retries”notiflow send -m hi --timeout 30 --connect-timeout 10 --retries 5 --max-retry-after 120Defaults: 15s total, 5s to connect, 3 retries after the first attempt, and a 60s cap on a
server-requested retry_after. See Reliability for what gets
retried and what does not.