Almost every CronAlert user asks the same question in their first week: "how do I make this monitor alert that channel?" The honest answer is that you don't, and the reason is a design decision worth understanding before you fight it. Alert channels in CronAlert belong to a team, not to a monitor. Every enabled channel in a team fires for every monitor in the team, down and recovered alike. Routing isn't a property of the monitor; it's a property of where the monitor lives.

This guide explains that model, what it buys you, the two ways to get per-group routing anyway, which channel types are on which plan, and where the controls that reduce noise actually sit. It's the page the rest of our alerting guides point at when they say "channels are team-wide."

The model

  • Teams own everything. Monitors, alert channels, status pages, and API keys all belong to a team. Every account starts with a personal team; you can create more.
  • Channels are enabled or disabled, nothing else. An alert channel has a type (email, Slack, Discord, webhook, push, Teams, Telegram, PagerDuty), its configuration, and an enabled toggle. There is no list of monitors on it.
  • Every incident goes to every enabled channel in the team. When a monitor goes down, one incident opens and one down alert goes to each enabled channel; when it recovers, one recovery alert goes to each. The same applies to heartbeat and TCP monitors.
  • A team with no enabled channels is warned about. The dashboard shows a banner when a team has no channels or all of them are disabled, because the alternative is monitors that silently alert no one.

The benefit is the absence of a failure mode that bites in more flexible systems: a monitor that exists, is checking, is failing, and is attached to no channel, so nobody hears. In CronAlert that can't happen within a team. The cost is that "this monitor, that channel" has to be expressed differently.

Pattern 1: teams as routing groups

The intended way to route is to make the team the group. A team per audience or severity, each with the channels that audience wants:

TeamMonitorsChannels
ProductionPublic site, API, checkout, loginPagerDuty, Slack #incidents, email
InternalAdmin panels, dashboards, stagingSlack #ops only
BillingStripe webhooks, invoicing jobsTelegram group for the billing on-call, email
HomeNAS, Plex, Home AssistantDiscord, push to your phone

Creating teams costs nothing on any plan; you switch between them with the team switcher, and each has its own monitors list, channels, status pages, and API keys. The monitor cap applies per team, so a free account's 25 monitors can be spread across teams. What the Team plan adds is other people: up to 10 members per team, with owner, admin, and member roles, and unlimited members on Business. For a solo operator, teams are purely a routing and organization tool and need no upgrade.

Two practical notes. A monitor belongs to the team it was created in and can't be moved, so switch to the right team before creating it (or re-create it there; the REST API and the import page make bulk re-creation quick). And the plan that governs a team is its owner's plan: channel types, monitor caps, and check intervals follow the owner, so a member on a free personal plan can use the Telegram channel on a team owned by a Pro account.

Pattern 2: routing inside a team with a relay

Sometimes one team is the right shape (one on-call group, one set of people) but a subset of monitors should also reach somewhere specific: billing failures to the billing Telegram, the home lab to Discord, staging to nowhere at 3 AM. For that, add a webhook channel pointed at a small relay and let the relay decide. CronAlert posts every event to it as JSON with the monitor's name and URL, the incident times, and the failing check's details, signed with a secret you choose. A relay of twenty or thirty lines, hosted as a free Cloudflare Worker, reads a convention from the monitor's name and forwards accordingly:

// Sketch: route by a [tag] prefix in the monitor name
const tag = a.monitor.name.match(/^\[([a-z0-9-]+)\]/i)?.[1]?.toLowerCase();   // "[billing] Stripe webhooks" → "billing"
if (tag === "billing") await postToTelegram(env.BILLING_CHAT, text);
if (tag === "home")    await postToDiscord(env.HOME_WEBHOOK, text);
if (tag === "staging") return new Response(null, { status: 204 });          // deliberately silent
await postToSlack(env.OPS_WEBHOOK, text);                                     // everything else

The routing then lives in the monitor names, where anyone looking at the list can see it, and the only code is the Worker. Complete, copy-pasteable relays with signature verification are in the webhook integrations guide and in the posts for ntfy and Gotify, Splunk On-Call, Opsgenie, and Apprise, which fans one relay out to a hundred services with tags. The webhook channel is on the free plan.

Which channels are on which plan

ChannelPlanNotes
EmailFreeAny address; the default channel most teams keep as the paper trail.
SlackFreeIncoming webhook. Also reaches Mattermost, Rocket.Chat, and Zulip.
DiscordFreeChannel webhook; color-coded embeds.
WebhookFreeA URL plus an optional signing secret. The relay pattern above; no custom headers or templates.
PushFreeBrowser push to a phone or desktop; see the iOS and Android guides for Do Not Disturb.
Microsoft TeamsPro+Workflows webhook with Adaptive Cards.
TelegramPro+Bot token and chat ID.
PagerDutyPro+Native Events API v2 channel: triggers on down, resolves on recovered.

Where the noise controls live

Routing is per team; everything that decides whether an alert fires is per monitor, and that's where to look when a channel is too loud:

  • Failure threshold on the monitor: how many consecutive failed checks before an incident opens. Paid plans default to two for 1-minute checks. See reducing false positives.
  • Maintenance windows on the monitor: planned downtime that opens no incident. See maintenance windows.
  • Heartbeat grace on heartbeat monitors: how late a ping may be. See choosing a grace period.
  • Pausing a monitor: no checks, no alerts, until you resume.

Disabling a channel is the only channel-level control, and it's team-wide: everything in the team stops reaching that destination. If you find yourself wanting to disable a channel for some monitors, that's the signal to move those monitors to another team.

Testing a route

Every channel has a Send test alert button, which exercises the destination (the webhook URL, the bot token, the push subscription) without waiting for an outage. Use it after creating a channel and after any change to a relay. Then, once, run a real fire drill: point a spare monitor in the team at a URL that fails, confirm the down alert reaches every enabled channel, fix it, and confirm the recovery does too. Relays in particular tend to be built against the down payload and break on recovery, which has no status code and does have a resolution time.

Frequently asked questions

Can I assign a channel to specific monitors?

No. Channels are team-wide. Route by team, or with a webhook relay that reads the monitor's name.

Do teams for routing need a paid plan?

No. Create as many teams as you like on any plan; inviting people is Team plan and above.

Can a monitor be moved between teams?

Not in place; create it in the right team, by hand or through the API.

Which channels are free?

Email, Slack (and Slack-compatible), Discord, webhook, push. Teams, Telegram, and PagerDuty are Pro and above.

Do recovery alerts go to the same channels?

Yes, every enabled channel gets both the down and the recovered event for every incident.

Route by where it lives

Once the model clicks, routing in CronAlert is a question of organization rather than configuration: decide which audience each monitor belongs to, put it in that team, and give the team the channels that audience wants. For the exceptions, a relay and a naming convention. Create a free account, make a second team, and move your noisiest monitors into it. Related reading: team uptime monitoring for roles and members, fighting alert fatigue, incident response workflows, webhook alert integrations, and choosing between HTTP, TCP, and heartbeat monitors.