Skip to content

fix(email-schedule-rescheduler): instance-based SDK client + live-verified docs (DEV-1141) - #211

Merged
sonamg-droid merged 2 commits into
mainfrom
fix/email-schedule-rescheduler-live
Sep 24, 2026
Merged

sonamg-droid merged 2 commits into
mainfrom
fix/email-schedule-rescheduler-live

Conversation

@sonamg-droid

Copy link
Copy Markdown
Contributor

Follow-up to #210 (DEV-1141). Live-review of the merged sample found that live mode was broken and one CI gate was stale. This PR fixes the code and documents the verified platform limitation.

Code fixes (live mode crashed on the first call)

app.py used the SDK 2.x static pattern, which does not exist in telnyx==4.181.0 (AttributeError: module 'telnyx' has no attribute 'EmailMessage'); demo mode (DEMO_MODE=true) masked all of it:

  • telnyx.api_key = ... + telnyx.EmailMessage.create/retrieve/delete_schedule → telnyx.Telnyx(api_key=...) instance client (client.email_messages.*)
  • except telnyx.error.TelnyxError → from telnyx import APIError (telnyx.error was removed in v4)
  • message.id / message.scheduled_at → response.data.id / response.data.scheduled_at (responses are wrapped)
  • scheduled_at exact-string compare → instant-based _same_instant() (live SDK returns a parsed datetime; the API normalizes timestamps to microsecond-Z format — the old check could never pass)
  • Cleanup (DELETE schedule) now runs in finally so a failed step can never leave a scheduled email behind — previously the message would have actually sent
  • Step-3 assertion now checks 422 + non-empty errors array referencing the scheduled_at field (live API does not echo the timestamp value; verified on POST which shares the validation)

Live verification (2026-09-24, account shared-domain sender)

Acceptance criterion Result Evidence
POST /v2/email_messages + future scheduled_at → 202 + message ID ✅ passes live 202, status: "scheduled", message id returned
PATCH /v2/email_messages/{id}/schedule → 200 + updated scheduled_at ❌ blocked (platform) live API returns 404 code 10005 ("URL could not be found") on the documented path
Past scheduled_at → 422, no immediate send ❌ blocked same reason can't reach a 422 on PATCH; the identical rejection does work on POST (422 "scheduled_at must be in the future", pointer /data/attributes/scheduled_at)
GET /v2/email_messages/{id} reflects new scheduled_at ⚠️ untestable until PATCH ships GET works; SDK .data.scheduled_at verified

Route-level proof that PATCH is not deployed: DELETE /v2/email_messages/{id}/schedule on an invalid UUID returns 10001 "The requested email was not found" (route exists), while PATCH on the same path returns 10005 "The requested resource or URL could not be found" (route missing) — for both a valid scheduled message and an invalid UUID.

The reschedule route is documented in the Send Email guide and OpenAPI spec (RescheduleEmailMessage) but is not served in production yet. README.md/API.md/GUIDE.md now carry a Known limitation note; app.py prints a clear BLOCKED: message, cancels the message in cleanup, and exits instead of failing with a misleading error.

Repo-gate fixes

  • Regenerated catalog.json (out of sync after PR Add email-schedule-rescheduler (DEV-1141) #210 — gen_llms_txt.py --check failed on main)
  • README: fixed dead Related Examples links (email-sender / email-webhook-handler don't exist) → ai-email-agent-python / email-inbox-demo; corrected Troubleshooting rows; DEMO_MODE marked optional; shared-domain from-address restriction documented
  • API.md: response shapes corrected to live/spec behavior (status: "scheduled", error code 10015, 409 row, no fabricated updated_at field)
  • .env.example: removed stray markdown fences
  • GUIDE.md: stale 2.x SDK snippets replaced with instance-based usage
  • smoke_test.py: 20/20 tests incl. new _same_instant coverage

All gates pass locally: verify.py, rewrite_repo_links.py --check, gen_llms_txt.py --check; python -m py_compile app.py clean.

…ified docs (DEV-1141)

Live mode previously used the SDK 2.x static pattern (telnyx.api_key,
telnyx.EmailMessage.*) which does not exist in telnyx==4.181.0, so every
API call raised AttributeError and demo mode masked it.

- Instance-based client: telnyx.Telnyx(api_key=...) with
  client.email_messages.create/retrieve/delete_schedule
- except telnyx.error.TelnyxError -> from telnyx import APIError
- Unwrap responses: response.data.id / response.data.scheduled_at
- Compare scheduled_at as instants (SDK returns datetime; API
  normalizes to microsecond-Z format)
- Always run DELETE schedule cleanup (finally) so a failed step never
  leaves a scheduled email behind
- Step 3 asserts 422 + non-empty errors referencing the scheduled_at
  field (live API does not echo the timestamp value)

Live verification (2026-09-24, shared-domain sender):
- POST /v2/email_messages with future scheduled_at -> 202 + id (AC#1 OK)
- DELETE /v2/email_messages/{id}/schedule -> 200, status=cancelled
- PATCH /v2/email_messages/{id}/schedule -> 404 code 10005 on both a
  valid scheduled message and an invalid UUID, while DELETE on the same
  path works: the reschedule route is documented (docs + OpenAPI
  RescheduleEmailMessage) but not deployed yet. AC#2-4 blocked
  platform-side; README/API.md/GUIDE document the known limitation and
  the sample prints a BLOCKED message and cancels cleanly.

Also: regenerate catalog.json (sync gate was stale after PR #210),
fix dead Related Examples links, correct API.md response shapes
(status scheduled, code 10015, 409 row), .env.example fences, and
GUIDE.md SDK snippets; smoke tests now 20/20 including _same_instant.
@sonamg-droid
sonamg-droid merged commit 4f8eb8b into main Sep 24, 2026
11 checks passed
pull Bot pushed a commit to bryanwills/telnyx-code-examples that referenced this pull request Sep 25, 2026
…ve known-limitation notes (DEV-1141)

The PATCH /v2/email_messages/{id}/schedule route (RescheduleEmailMessage)
is now deployed to production. Live verification 2026-09-25, all four
acceptance criteria pass end-to-end via app.py (DEMO_MODE=false):

- POST /v2/email_messages + future scheduled_at -> 202 + message ID
- PATCH /v2/email_messages/{id}/schedule -> 200, scheduled_at updated
- PATCH with past timestamp -> 422 code 10015 ("scheduled_at must be in
  the future", pointer /data/attributes/scheduled_at); message status
  stays scheduled — not sending/queued
- GET /v2/email_messages/{id} reflects the rescheduled value
- DELETE schedule cleanup -> 200, status=cancelled

Cleans up the now-stale Known limitation notes from PR team-telnyx#211 (README,
API.md, GUIDE.md, app.py docstring), replaces the unreachable 404
BLOCKED branches with a 409 conflict handler per the documented
semantics, and records the live-verified behavior.

Gates: verify.py PASS, rewrite_repo_links --check PASS,
gen_llms_txt --check PASS (llms.txt + catalog.json in sync),
pytest 20/20, py_compile clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant