Scheduling an email is straightforward until the event behind it changes.
An appointment moves. A webinar starts later. A delivery window slips. The email that was correct when you created it is now scheduled for the wrong time.
A common workaround is to cancel the original message and create another one. That means a new message ID, another pass through your send logic, and an awkward failure window where you could leave both messages scheduled or neither one.
The email-schedule-rescheduler Python example uses the Telnyx Email API to update the existing scheduled message instead.
The complete flow is:
Create a scheduled email
↓
Reschedule the same message
↓
Verify its new delivery time
↓
Reject an invalid past timestamp
↓
Cancel the schedule for cleanup
Schedule the original email
Create the message with POST /v2/email_messages and provide a future ISO 8601 timestamp in scheduled_at:
import os
from datetime import datetime, timedelta, timezone
import requests
API_BASE = "https://api.telnyx.com/v2"
HEADERS = {
"Authorization": f"Bearer {os.environ['TELNYX_API_KEY']}",
"Content-Type": "application/json",
}
def iso_utc(value: datetime) -> str:
return value.isoformat().replace("+00:00", "Z")
scheduled_at = datetime.now(timezone.utc) + timedelta(hours=2)
response = requests.post(
f"{API_BASE}/email_messages",
headers=HEADERS,
json={
"from": os.environ["TELNYX_EMAIL_FROM"],
"to": [os.environ["TELNYX_EMAIL_TO"]],
"subject": "Appointment reminder",
"text_body": "Your appointment is coming up.",
"scheduled_at": iso_utc(scheduled_at),
},
)
response.raise_for_status()
message_id = response.json()["data"]["id"]
The API returns 202 Accepted with the message in the scheduled state. The message has an ID, but delivery waits until scheduled_at.
Move the delivery time with one PATCH
When the underlying appointment changes, update the message's schedule:
new_time = datetime.now(timezone.utc) + timedelta(hours=4)
response = requests.patch(
f"{API_BASE}/email_messages/{message_id}/schedule",
headers=HEADERS,
json={"scheduled_at": iso_utc(new_time)},
)
response.raise_for_status()
The Email API returns 200 when the reschedule succeeds. Only the delivery time changes. The message ID, content, recipients, tags, and metadata remain attached to the same resource.
That makes the operation easier to reason about than cancel-and-recreate, especially if your system associates audit records or delivery processing with the original message ID.
Do not silently convert mistakes into immediate sends
The example intentionally tries to move the message into the past:
past_time = datetime.now(timezone.utc) - timedelta(hours=1)
response = requests.patch(
f"{API_BASE}/email_messages/{message_id}/schedule",
headers=HEADERS,
json={"scheduled_at": iso_utc(past_time)},
)
assert response.status_code == 422
The API rejects missing, invalid, or non-future scheduled_at values with 422. It does not reinterpret the request as "send immediately."
That distinction gives your application control over what happens next. It can ask for a corrected time, notify an operator, or cancel the message instead of unexpectedly delivering stale information.
Verify the stored schedule
The reschedule response contains the updated message representation. The sample also retrieves the message to verify the stored value:
response = requests.get(
f"{API_BASE}/email_messages/{message_id}",
headers=HEADERS,
)
response.raise_for_status()
stored_time = response.json()["data"]["scheduled_at"]
print(stored_time)
There is no separate rescheduled event. The updated scheduled_at field in the response is the confirmation, so checking the resource is a useful way to make the state visible in your workflow.
Clean up the demo
Cancel the schedule before the email is sent:
response = requests.delete(
f"{API_BASE}/email_messages/{message_id}/schedule",
headers=HEADERS,
)
response.raise_for_status()
A successful cancellation returns 200 with the message in the cancelled state.
Run it safely in demo mode
The repository example defaults to DEMO_MODE=true. It prints the request flow without calling the live API, which lets you inspect the behavior before supplying credentials or sending an email.
git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/email-schedule-rescheduler
cp .env.example .env
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python app.py
For a live run, configure:
TELNYX_API_KEY=<your API key>
TELNYX_EMAIL_FROM=<verified sender>
TELNYX_EMAIL_TO=<recipient>
DEMO_MODE=false
The sample uses the Telnyx Python SDK where the operation is available and direct HTTP for the schedule PATCH. That also makes the exact request shape easy to adapt in another language.
Where this pattern helps
Rescheduling the same message is useful for:
- appointment and patient reminders
- webinar and event updates
- payment reminders
- delivery-window notifications
- reservation changes
- time-sensitive internal alerts
The reusable idea is simple: keep one message resource, mutate its future delivery time, verify the new state, and fail explicitly when the requested schedule is invalid.
Top comments (0)