4 min read
Trigger.dev is the right tool for user-created monitoring

When building a monitoring system where users define their own checks, “ping my API every 5 minutes” or “alert me if this price drops below $X”, you need infrastructure that can handle dynamic, user-defined schedules at scale. Three tools come up naturally in this space: Cloudflare Cron Triggers, Cloudflare Queues, and trigger.dev. They solve adjacent problems but are not interchangeable.

Cloudflare Cron Triggers

Cloudflare Cron lets you run a Worker on a fixed schedule, defined at deploy time in wrangler.toml.

[triggers]
crons = ["*/5 * * * *"]

What it’s good for: Infrastructure-level tasks you control, like purging caches, sending daily digests, or running DB maintenance. The schedule is static and owned by you, the developer.

Why it breaks for user-created monitoring: Each user would need their own cron trigger, but Cloudflare limits you to a small number of cron schedules per Worker, and there’s no API to create them dynamically at runtime. You’d have to re-deploy your Worker every time a user creates or updates a monitor, which is a non-starter.

Cloudflare Queues

Cloudflare Queues is a message queue where producers push messages and consumers process them. You can simulate scheduled work by having a recurring producer push jobs and a consumer execute them.

What it’s good for: Decoupling work, handling bursts, and retrying failed jobs. It’s a solid primitive for async processing.

Why it’s a poor fit for user monitoring: Queues don’t have a native concept of “run this job every N minutes for user X”. You’d need to build a scheduler on top, something that wakes up, looks at a database of user monitors, and enqueues jobs at the right time. At that point you’re maintaining a scheduler rather than using one, and you’ve traded one problem for a bigger one.

trigger.dev

trigger.dev is a background job and workflow platform with first-class support for dynamic, programmatically-created schedules.

import { schedules } from "@trigger.dev/sdk/v3";

// Create a schedule for a user's monitor at runtime
await schedules.create({
  task: "check-monitor",
  cron: "*/5 * * * *",
  externalId: `user-${userId}-monitor-${monitorId}`,
  deduplicationKey: `monitor-${monitorId}`,
});

When the user deletes or pauses their monitor, you call schedules.del(). When they change the frequency, you call schedules.update(). It’s just an API call.

Why it wins for user-created monitoring:

  1. Dynamic schedules at runtime. No deploys required. Users can create, update, and delete their monitors and the schedule follows immediately.

  2. Per-schedule identity. Each schedule has an externalId you control, so you can map it back to the user and monitor in your database without any ceremony.

  3. Built-in deduplication. The deduplicationKey ensures you don’t accidentally create duplicate schedules if your user hits “save” twice.

  4. Managed retries and observability. Failed runs are retried automatically, and you get a full run history per task and per schedule, which feels invaluable when debugging “why didn’t my alert fire?”.

  5. Fan-out ready. When you have 10,000 users each with 3 monitors, trigger.dev handles the fan-out and you stop thinking about queues, workers, or concurrency limits.

The mental model

Cloudflare CronCloudflare Queuetrigger.dev
Schedule defined byDeveloper at deploy timeYou build it yourselfDeveloper at runtime via API
Dynamic user schedulesNoManual plumbingYes, first-class
Retry/observabilityBasicManualBuilt-in
Scales with user countNoWith effortYes

Cloudflare Cron is for your recurring tasks. Cloudflare Queue is a building block. trigger.dev is the right abstraction when your users are the ones defining what runs and when.

If you’re building a monitoring product, whether that’s uptime checks, price alerts, data sync jobs, or anything else where users create their own schedules, invest in trigger.dev early. The alternative is hand-rolling a scheduler, and that’s a rabbit hole that swallows sprints.