Reminders sound simple until you try to make them behave like a real product.
Sending one SMS later is easy. The harder part is what happens after the reminder goes out.
Did the person reply? Did they ask to snooze it? Did they say "remind me in two hours"? Should the next reminder wait 30 minutes, an hour, or longer? Where does that state live? What wakes the workflow back up without a separate queue, cron job, worker, and database?
That is what this example is about.
scheduled-reminder-agent is a TypeScript example that runs on Telnyx Edge Compute with the Telnyx Agent SDK. It sends scheduled SMS reminders, stores reminder state per phone number, uses Telnyx AI Inference to detect snooze intent, and adapts the next reminder time with exponential backoff.
Code: https://github.com/team-telnyx/telnyx-code-examples/tree/main/scheduled-reminder-agent
What we are building
The app exposes a simple endpoint:
POST /remind
You send it a recipient, message, and delay. The Edge Compute function routes that request to a ReminderAgent actor keyed by the recipient's phone number.
From there, the actor owns the lifecycle:
POST /remind
-> ReminderAgent.scheduleReminder()
-> this.schedule(delay, "sendReminder")
-> Telnyx Messaging sends the SMS
-> inbound SMS reply reaches /webhooks/sms
-> Telnyx AI Inference detects snooze vs acknowledge
-> this.schedule(nextDelay, "sendReminder") if snoozed
The important detail is that the reminder is not stored in a stateless webhook handler. It lives with the actor that owns that recipient's reminder workflow.
Why the Agent SDK matters
The Agent SDK gives this example three primitives that are awkward to stitch together manually:
- durable state with
this.setState()andthis.getState() - durable scheduled tasks with
this.schedule() - zero-credential Telnyx API access with
this.env.TELNYX
The state tracks active reminders, snooze count, whether the app is waiting for a reply, and the adaptive base delay.
The scheduled task fires when it is time to send the SMS.
The Telnyx binding sends the SMS and calls AI Inference without hardcoding an API key in the application code.
That combination turns a reminder into a small stateful agent instead of a loose collection of cron jobs and callbacks.
The reminder flow
When a client calls POST /remind, the app validates the message and recipient, finds the actor for that recipient, and calls:
await stub.scheduleReminder(message, delayMinutes * 60, from, to);
Inside the actor, scheduleReminder() creates a reminder record and schedules sendReminder():
await this.schedule(delaySeconds, "sendReminder", { id }, { id: `send-${id}` });
When the scheduler fires, sendReminder() sends the SMS:
await this.env.TELNYX.messages.send({
from: state.fromNumber,
to: state.phoneNumber,
text: smsText,
});
Then it opens a reply window by scheduling replyTimeout().
So the workflow can continue even after the original HTTP request is long gone.
Snooze detection with AI
The user does not need to reply with a perfect command.
They might say:
snooze
not now
remind me later
in two hours
busy, try again after lunch
The actor sends the reply to Telnyx AI Inference and asks for JSON:
{
"intent": "snooze",
"delay_minutes": 120
}
If the model detects an acknowledgement, the reminder is marked done.
If it detects a snooze, the actor schedules the reminder again.
Adaptive timing
The example uses exponential backoff when the user asks to snooze but does not give an exact time.
The default progression is:
30 minutes
1 hour
2 hours
4 hours
8 hours
That means the agent gets less pushy over time instead of sending the same reminder over and over at a fixed interval.
If the user specifies a time, like "snooze for 2 hours," that explicit delay wins.
This is a small behavior, but it makes the reminder feel more like an assistant and less like a timer.
Testing it
After deploying with telnyx-edge ship, you can schedule a reminder:
curl -X POST https://scheduled-reminder-agent-<id>.telnyxcompute.com/remind \
-H "Content-Type: application/json" \
-d '{
"to":"+15551230000",
"from":"+15559870000",
"message":"Time for your meeting",
"delay_minutes":5
}'
For local workflow testing, the sample also includes debug routes:
curl -X POST https://scheduled-reminder-agent-<id>.telnyxcompute.com/debug/reply \
-H "Content-Type: application/json" \
-d '{
"from":"+15551230000",
"text":"snooze for 2 hours"
}'
And you can inspect actor state:
curl "https://scheduled-reminder-agent-<id>.telnyxcompute.com/debug/state?from=+15551230000"
Where this pattern goes next
This is useful beyond a basic reminder app.
The same pattern can support:
- medication reminders
- appointment nudges
- payment follow-ups
- maintenance reminders
- onboarding check-ins
- delivery updates
- renewal workflows
The core pattern is durable communications state plus scheduled work.
Before using this in production, I would add webhook signature verification, SMS opt-out handling, idempotency keys, timezone-aware scheduling, stronger retry handling, and alerting for failed sends or missed reminders.
But the shape is already here.
If an agent needs to contact someone later, it needs more than a prompt. It needs state, timers, reply handling, and a way to wake itself back up.