Scheduling an action is only part of the job. An operator also needs to know whether it ran, what happened, and whether a failure alert was attempted. This sample puts recurring communication and execution history in one small application.
What does the scheduler do?
The sample manages recurring calls, SMS messages, and HTTPS webhooks through one dashboard. A job contains a name, a destination, an action type, and a five-field cron expression. The backend stores that definition, checks for due work, queues an execution, and saves its outcome. Failed jobs can trigger an SMS alert, whose outcome is also recorded.
The dashboard connects directly to the scheduler API. It lets an operator create or remove jobs, request immediate runs, filter execution history, and inspect individual results. Recording view compacts the layout for a screen-shared walkthrough. These controls operate on backend state; the cards are not a static mockup.
This is a TypeScript example built with the Telnyx Agent SDK. Telnyx AI Communications Infrastructure supplies the communications APIs used by the live call and messaging paths. The included local runner makes the scheduling and storage workflow accessible without first configuring a phone number or making an outbound request.
Two practical uses for recurring communication
A daily customer check-in is one straightforward use case. An operator can create a recurring SMS reminder for a known destination, choose a daily schedule, and inspect the corresponding execution records. A successful API request and a delivered text are different events, so the history describes request acceptance rather than claiming the recipient read the message.
The second use case is an operations heartbeat. A scheduled HTTPS webhook can trigger a routine check in another service. If that request fails, the scheduler can attempt an SMS notification to the configured operations destination. The execution and alert outcomes remain visible together, which makes troubleshooting easier than searching unrelated terminal output.
Both examples fit recurring schedules. Appointment-specific timing, contact imports, opt-out handling, and application-specific escalation rules would require additional work around this sample. Start with one recurring action, validate its destination and schedule, and then decide what surrounding business workflow should create or remove it.
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.
Connect live Telnyx communication when needed
The live paths use the Telnyx Node SDK to request an outbound call or send a message. Configure TELNYX_API_KEY, a sender number, and the relevant connection and notification settings in the local environment. Only the exact setting DEMO_MODE=false enables outbound requests.
Live webhooks require HTTPS and an exact hostname in WEBHOOK_HOSTS. Redirects are rejected. That makes the destination policy explicit rather than following an arbitrary redirect from a configured endpoint.
For local verification, run npm test. The suite exercises scheduling, storage, restart behavior, dependencies, authentication, and communication request shapes. Against a running demo server, npm run test:http checks repeated automatic occurrences. Keep the token and server address consistent between the server and test process.