Skip to content

feat: TelegramNotifier — recommended default notifier (rc2)#20

Merged
cipher813 merged 1 commit into
mainfrom
feat/telegram-notifier
May 13, 2026
Merged

feat: TelegramNotifier — recommended default notifier (rc2)#20
cipher813 merged 1 commit into
mainfrom
feat/telegram-notifier

Conversation

@cipher813
Copy link
Copy Markdown
Owner

Summary

Adds Telegram as the recommended default notifier in flow-doctor 0.5.0rc2.

Why Telegram becomes the default: for single-dev and small-team ops, Telegram beats SMTP / SES / Slack on setup cost (two-minute @BotFather flow vs. SMTP app password / SES verified identity / Slack workspace admin), routing (per-chat or per-thread is built in), and UX (mobile push is automatic). One bot fans out to N flows via chat_id and optional message_thread_id (forum topics in supergroups). Bot tokens rotate cleanly via @BotFather.

SMTP / SES (via existing EmailNotifier) / Slack / GitHub / S3 stay as alternates — the change is which notifier the README + builder examples lead with. No deprecations.

This also kills the orphan telegram_webhook_url field that was hiding in RemediationConfig with no first-class notifier behind it (will be cleaned up in a follow-up rc once we audit consumers).

Usage

from flow_doctor import FlowDoctor, TelegramNotifierConfig

fd = (
    FlowDoctor.builder("morning-signal")
    .add_notifier(TelegramNotifierConfig(
        bot_token=os.environ["FLOW_DOCTOR_TELEGRAM_BOT_TOKEN"],
        chat_id=-1001234567890,
        message_thread_id=42,  # optional — forum topic routing
    ))
    .with_dedup(cooldown_minutes=60)
    .build()
)

Or via env-only:

export FLOW_DOCTOR_TELEGRAM_BOT_TOKEN=...
export FLOW_DOCTOR_TELEGRAM_CHAT_ID=-1001234567890
fd = FlowDoctor.builder("morning-signal").add_notifier(
    TelegramNotifierConfig()  # all fields pulled from env
).build()

Implementation notes

  • Bot API sendMessage via urllib (no requests dep — keeps the install lean).
  • /getMe preflight at __init__ so a revoked token fails fast; bypassed by FLOW_DOCTOR_SKIP_PREFLIGHT=1 (same env-var contract as the other notifiers).
  • 4096-char Telegram message limit handled with a [truncated] sentinel.
  • send() returns "telegram:<chat_id>[:<thread>]" as the action target — never the bot token. Stored in actions.target for the audit trail.
  • Numeric FLOW_DOCTOR_TELEGRAM_CHAT_ID env value auto-coerces to int (negative for supergroups / channels); @channelusername style stays str.

Test plan

  • pytest tests/ → 376/376 pass (356 prior + 20 new Telegram tests).
  • New tests cover: typed config round-trip through legacy NotifyChannelConfig, env-var coercion (int vs @channel str), missing-field ConfigError shape (mentions @BotFather + getUpdates), urllib POST URL + payload (chat_id / text / parse_mode / message_thread_id / disable_notification gating), target-id format, never-raises on API ok=false + network failure, message-length truncation, ActionType.TELEGRAM_ALERT dispatch end-to-end, preflight skip + 401-style auth failure.
  • One pre-existing assertion in test_builder used "telegram" as an example unknown channel type — updated to "pagerduty".
  • PyPI publish of 0.5.0rc2 after merge.
  • Morning-signal cutover lands on top of 0.5.0rc2 in a separate PR (alpha-engine-morning-signal repo).

🤖 Generated with Claude Code

Telegram beats SMTP / SES / Slack on every axis that matters for
single-dev and small-team ops: two-minute @Botfather setup, no app
password / verified-identity dance, per-flow routing via chat_id or
forum message_thread_id, mobile push for free, clean token rotation.
This rc promotes it to the recommended default; SMTP / SES (via the
existing EmailNotifier) / Slack / GitHub / S3 stay as alternates for
consumers that need them.

- flow_doctor/notify/telegram.py: TelegramNotifier (Bot API
  sendMessage POST, urllib-only to keep core dep-free, 4096-char
  Telegram-limit truncation with [truncated] sentinel, /getMe
  preflight that respects FLOW_DOCTOR_SKIP_PREFLIGHT). send() returns
  the non-secret target identifier "telegram:<chat_id>[:<thread>]"
  so action.target never persists the bot token.

- flow_doctor/notify/configs.py: TelegramNotifierConfig joins the
  Field(discriminator="type") union. Accepts chat_id as int (typical,
  negative for supergroups / channels) or str ("@channelusername" for
  public channels).

- flow_doctor/core/config.py: NotifyChannelConfig extended with
  bot_token / chat_id / message_thread_id / parse_mode (default
  Markdown) / disable_notification fields so yaml-driven configs and
  the to_channel_config() lift path both work. _parse_notify_dicts
  forwards the new keys.

- flow_doctor/core/client.py: _init_notifiers handles type="telegram"
  with bot_token + chat_id ConfigError messages pointing at @Botfather
  and the getUpdates lookup. FLOW_DOCTOR_TELEGRAM_BOT_TOKEN +
  FLOW_DOCTOR_TELEGRAM_CHAT_ID env fallbacks (with TELEGRAM_BOT_TOKEN
  / TELEGRAM_CHAT_ID as conventional aliases); numeric env chat_ids
  coerce to int, @channelusername stays str. _send_notifications
  dispatches to ActionType.TELEGRAM_ALERT.

- flow_doctor/core/models.py: TELEGRAM_ALERT joins the ActionType enum.

- flow_doctor/{__init__,notify/__init__}.py: TelegramNotifierConfig
  re-exported at both package roots.

- Version bump 0.5.0rc1 → 0.5.0rc2 in pyproject.toml + __init__.py.
  CHANGELOG entry documents the rationale + setup recipe.

20 new tests cover: round-trip through legacy NotifyChannelConfig,
builder integration, env-var coercion (numeric → int, @channel →
str), missing-field ConfigError shape, urllib POST URL + payload
(chat_id / text / parse_mode / message_thread_id / disable_notification
gating), target-id format on success, never-raises behavior on API
ok=false and network failures, truncation, ActionType dispatch
end-to-end through _send_notifications, and preflight skip + auth
failure paths.

Suite: 376/376 pass (356 prior + 20 new). One pre-existing assertion
in test_builder used "telegram" as an example unknown channel type —
updated to "pagerduty" since telegram is now a first-class member of
the discriminated union.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cipher813 cipher813 merged commit e4ec653 into main May 13, 2026
1 check passed
@cipher813 cipher813 deleted the feat/telegram-notifier branch May 13, 2026 20:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant