Eventum Logo

Eventum

Alert Simulation: Scheduled Telegram Alerts

Alert simulation for testing a monitoring pipeline — generate randomized-severity alerts on a cron schedule and deliver them to a Telegram chat through the Bot API, exercising the pipeline without a real incident.

A critical alert that never reaches the on-call phone, a dashboard tile that miscounts what arrived, an escalation that never fires on an unacknowledged warning — each of these bugs waits for a real incident to surface, and surfaces at the cost of one. Firing realistic alerts on a schedule surfaces them on demand instead, exercising every piece downstream of the trigger: the chat, the paging rotation, the dashboard count, the escalation path.

The generator below fires exactly those alerts — randomized severity, service, and metric value, on a cron schedule — and delivers each over HTTP to a live Telegram chat through the Bot API. A miniature alerting pipeline in a few files, hitting a real notification channel instead of a mock.

Alert simulation and the pipeline it exercises

A detection rule that matches, a threshold that trips, or a health check that fails all produce the same next step: an alert, structured no differently from any other event Eventum generates — a severity, a service, a message, a timestamp. Testing that a rule fires correctly says nothing about what happens after — the notification routing, on-call paging, dashboard counting, and escalation the opening paragraph lists, everything downstream of the alert itself. A real incident exercises that whole chain eventually — at whatever severity and service it happens to produce.

Firing realistic alerts on a schedule exercises the same chain on demand, at whatever severity mix and service spread a test needs, instead of waiting on a rule to match or an incident to happen. Telegram is one concrete chat to land these in — nothing below depends on Telegram past the request body and the endpoint URL, and the same pipeline reaches Slack, Discord, or PagerDuty just as directly.

What you'll build

The generator uses:

  • cron input — fires every 2 minutes.
  • all picking mode — renders the one alert template on every tick, using conditional Jinja2 logic to vary message format and value range by severity.
  • HTTP output — posts each rendered alert to the Telegram Bot API as its own request. See Send test data to an API endpoint for the delivery mechanics behind it.
  • Secrets — bot token stored securely in the keyring.
  • Parameters — chat ID passed through startup.yml.

Prerequisites

  • Eventum installed
  • A Telegram bot token (create one via @BotFather)
  • The chat ID of the target chat (send a message to your bot, then query https://api.telegram.org/bot<TOKEN>/getUpdates to find it)

Project structure

eventum.yml
startup.yml
generator.yml
alert.jinja

Build it

Create the project directory

mkdir -p eventum/generators/alerts/templates
cd eventum

Write the alert template

The template produces a JSON body for the Telegram sendMessage API. It uses module.rand to pick a random severity and service, then builds a Markdown-formatted message with an emoji indicator.

generators/alerts/templates/alert.jinja
{% set severity = module.rand.weighted_choice(["info", "warning", "critical", "resolved"], [0.4, 0.35, 0.2, 0.05]) %}
{% set service = module.rand.choice(["api-gateway", "auth-service", "db-primary", "cache-redis", "worker-queue", "nginx-lb"]) %}
{% set icons = {"info": "ℹ️", "warning": "⚠️", "critical": "🔴", "resolved": "✅"} %}
{% set metric = module.rand.choice(["CPU usage", "memory usage", "request latency", "error rate", "disk I/O", "connection pool"]) %}
{% if severity == "critical" %}
{% set value = module.rand.number.integer(90, 100) %}
{% elif severity == "warning" %}
{% set value = module.rand.number.integer(70, 89) %}
{% else %}
{% set value = module.rand.number.integer(10, 69) %}
{% endif %}
{
  "chat_id": {{ params.chat_id }},
  "parse_mode": "Markdown",
  "text": "{{ icons[severity] }} *{{ severity | upper }}* | `{{ service }}`\n\n{{ metric }}: *{{ value }}%*\nHost: `{{ module.faker.locale.en.hostname() }}`\nTime: {{ timestamp.strftime('%H:%M:%S UTC') }}"
}

The message looks like this in Telegram:

🔴 CRITICAL | nginx-lb

connection pool: 95% Host: desktop-00.miller.info Time: 13:34:09 UTC

Configure the generator

The cron expression */2 * * * * 0 fires at second 0 of every 2nd minute — seconds are the last field, after the weekday, so the trailing 0 is the second and the leading */2 the minute. The HTTP output posts to the Telegram API endpoint, with success_code: 200 matching what sendMessage actually returns on success — the http output's own default of 201 assumes an endpoint that treats each POST as creating a new resource, which Telegram's does not.

generators/alerts/generator.yml
input:
  - cron:
      expression: "*/2 * * * * 0"
      count: 1

event:
  template:
    mode: all
    params:
      chat_id: ${params.chat_id}
    templates:
      - alert:
          template: templates/alert.jinja

output:
  - stdout: {}
  - http:
      url: "https://api.telegram.org/bot${secrets.telegram_token}/sendMessage"
      method: POST
      success_code: 200
      headers:
        Content-Type: "application/json"
      formatter:
        format: plain

The plain formatter passes the template output as-is — the template already produces a valid JSON request body. Swapping in the http output's own default of json-batch here would collect every alert a batch accumulates into a single JSON array and post that array as one request, which Telegram's sendMessage endpoint does not accept; see Send test data to an API endpoint for the full per-event versus per-batch tradeoff behind that choice. The stdout output is included for local debugging; remove it in production.

Configure the application

eventum.yml
server:
  host: "0.0.0.0"
  port: 9474

path:
  startup: /home/user/eventum/startup.yml
  generators_dir: /home/user/eventum/generators
  logs: /home/user/eventum/logs
  keyring_cryptfile: /home/user/eventum/cryptfile.cfg

All path.* values must be absolute paths. Adjust to match your actual project location.

The startup file passes the chat ID as a parameter:

startup.yml
- id: alerts
  path: alerts/generator.yml
  params:
    chat_id: "123456789"

Replace 123456789 with your actual Telegram chat ID.

Store the bot token

The bot token is sensitive — store it in the keyring:

eventum-keyring set telegram_token

Enter your bot token when prompted (e.g., 110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw).

Run it

eventum run -c eventum.yml

Every 2 minutes, an alert message appears in your Telegram chat. The terminal shows the same JSON body on stdout:

The alert below was captured by pointing this generator's output at stdout alone and swapping the cron input for a bounded static input, so it rendered immediately in sample mode (--live-mode false) instead of waiting on the real 2-minute cadence. The deployed config above, run with eventum run, produces the identical JSON shape at that real cadence, delivered to Telegram instead of the terminal.

{
  "chat_id": 123456789,
  "parse_mode": "Markdown",
  "text": "⚠️ *WARNING* | `api-gateway`\n\nconnection pool: *81%*\nHost: `srv-41.cole-williams.com`\nTime: 13:34:09 UTC"
}

Going further

  • Any HTTP-reachable destination — as the intro notes, the request body and the URL are the only Telegram-specific parts. Point the same http output at a Slack Incoming Webhook, a Discord Webhook, or a PagerDuty Events API integration instead, reshaping the template's JSON to match the destination's expected payload. See Send test data to an API endpoint for the delivery mechanics this generalizes.
  • Escalation schedule — use multiple inputs with different cron expressions: check every 2 minutes during business hours, every 10 minutes overnight.
  • Incident sequences — switch to FSM mode to model alert → acknowledged → investigating → resolved workflows.
  • Silence window — use the cron start/end fields to suppress alerts during maintenance windows.

What's next

FAQ

On this page