This walkthrough uses a local TypeScript scheduler with a browser dashboard. Calls, messages, and outbound webhooks are simulated by default; scheduling and saved execution records are real.
I work with Telnyx. The code is available here: https://github.com/team-telnyx/telnyx-code-examples/tree/main/edge-cron-scheduler
How the Agent SDK, KV, and SQL fit together
One named scheduler actor owns the job registry. Job definitions are stored in actor key-value storage. Each execution receives a separate SQL record, so a new run does not overwrite the result of the previous run.
The Agent SDK registers a recurring polling task with every(60, "poll"). When the poller finds a due job, it dispatches execution through queue("execute", task). Cron parsing determines the next matching calendar time; the polling interval determines when the application checks for work.
Dashboard or authenticated HTTP client
|
CronAgent("scheduler")
| |
KV job registry recurring poll
|
queued execution
|
SQL claim and outcome
|
call / SMS / webhook
The local runner uses the same Agent SDK with a disk-backed SQLite adapter. It serializes HTTP and alarm turns, stores the registry and execution history, and resumes saved timers after restart. Its role is to host the sample locally. The walkthrough demonstrates that concrete configuration and does not imply that a browser tab is responsible for running the scheduler.
Run the demo locally
Use Node.js 22.13 or newer. The sample includes a lockfile, so npm ci installs the recorded dependency versions.
git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/edge-cron-scheduler
npm ci
cp .env.example .env
npm start
Open the dashboard address printed in the terminal. With an automatically generated local token, the startup link includes a fragment that connects the current browser tab. The page removes that fragment from the address bar. If SCHEDULER_TOKEN is configured, enter it in the connection form instead; the server does not print the configured credential.
The default mode simulates outbound calls, messages, and webhooks. The scheduler, stored jobs, and execution history still run. No Telnyx account credentials are needed for this local simulation. Read the visible mode label before interpreting a result: a simulated success demonstrates the workflow, not delivery to a real recipient.
The server binds to loopback. Keep the generated local database in its ignored data directory, and keep account configuration in the ignored environment file. The repository contains example destinations rather than customer contact data.
Create a daily reminder in the dashboard
Start with Load demo jobs. The dashboard creates examples for a customer check-in call, a team SMS reminder, and a webhook heartbeat. Their every-minute schedules make automatic execution visible during a short walkthrough.
Choose Create job to add a reminder of your own. Enter a name, select SMS, supply an example destination and message, then choose the daily schedule. The expression 0 9 * * * means 09:00 UTC each day. Saving the job creates its first future scheduled occurrence; it does not immediately send a message.
The next-due field is useful when checking that a schedule matches the intended clock time. All schedules use UTC. Five fields are accepted; seconds and per-job timezone overrides are not part of this interface. For a weekly or less frequent action, use the custom cron input and inspect the resulting next run before leaving it enabled.
A manual run is a separate action. Run now queues one eligible job, while Run all queues the jobs that have no dependencies. This makes it possible to demonstrate execution without waiting until the next daily occurrence.
Use the authenticated HTTP API
The dashboard uses the same API available to scripts. Export the scheduler token in your own terminal before making authenticated requests. The placeholder below must be replaced locally; it is not a shared credential.
export SCHEDULER_TOKEN='paste-your-local-token'
curl -sS http://127.0.0.1:8787/jobs -H "Authorization: Bearer $SCHEDULER_TOKEN" -H 'Content-Type: application/json' -d '{"id":"daily-reminder","name":"Daily reminder","cron":"0 9 * * *","type":"sms","target":"+18005550102","payload":{"text":"Your daily check-in is ready."}}'
Queue that job manually with POST /jobs/daily-reminder/run, then inspect GET /logs. A manual request returns an accepted response with a run identifier. Read the execution history to determine the eventual outcome; accepting the queue request is not the same as completing the work.
The public root route serves only the HTML shell. Data and management routes require the bearer token. The browser keeps its token in session storage for the current tab, and Disconnect clears the token and displayed job data.
Read execution history and demonstrate a failure
Each execution records an identifier, scheduled time, start and finish times, status, result, and notification outcome. Open Details to inspect those fields. Filtering to failed runs is useful when a busy schedule has produced several successful executions around a single error.
The Try a failure control creates and queues a demo webhook job that intentionally fails. The result records the simulated failure and a simulated SMS alert. This provides a repeatable way to demonstrate the error path without contacting a real endpoint or sending a real notification.
The notification has its own outcome because failure reporting can fail too. A webhook error and an unsuccessful alert attempt are two facts worth retaining. The sample attempts an alert once; it does not include a separate service that retries alerts indefinitely.
For calls and messages in live mode, a success result means the API accepted the request. The sample does not consume final delivery webhooks. An answered call, delivered SMS, or human response would need its own downstream tracking.
Understand repeats, restarts, and dependencies
A stable run identifier and a SQL claim suppress repeated execution of the same queued task. This does not establish exactly-once effects across the network. If the process stops after recording a running execution, the external outcome may be uncertain. Inspect that record before requesting another attempt.
After downtime, an overdue job runs once for its oldest pending slot and skips intermediate missed slots. That avoids a burst of catch-up calls or messages on restart. It also means this design is not a complete replay system for every missed occurrence.
Dependencies are supported for jobs with the same schedule. A dependent job requires its parent to succeed for the same scheduled slot; a failed dependency produces a skipped execution. Manual runs are restricted to jobs without dependencies. These rules give the sample a small, explicit execution model that can be reasoned about from its stored records.
Top comments (0)