Skip to content

fix: keep reading back legacy transmission ids (2.8.1) - #21

Open
voj-tech-j wants to merge 2 commits into
mainfrom
fix/legacy-transmission-id
Open

voj-tech-j wants to merge 2 commits into
mainfrom
fix/legacy-transmission-id

Conversation

@voj-tech-j

Copy link
Copy Markdown
Contributor

A bug I shipped in 2.8.0. getScheduled() now throws for legacy transmission ids:

InvalidValueException: Request ID cannot be empty.

What happened

The API keeps an old-shape fallback for ids handed out before Lettr held scheduled emails itself: those are answered from delivery events, and that response has no request_id.

ScheduledEmail::from() read it unconditionally, so RequestId rejected the empty string. 2.8.0 fixed reading back sch_ ids and broke the legacy path in the same change.

requestId now falls back to transmission_id, which is the id the caller addressed the email with — so it is always the id that identifies what you asked about. A test covers the full legacy payload.

Who this affects

Anyone calling getScheduled() with a transmission id stored before the API change, whose delivery events are still inside the 10-day retention window. Narrow and closing, but the API supports that path deliberately, so the SDK should too.

sch_ ids, schedule(), cancelScheduled() and listScheduled() are unaffected.

How it was found

Running the example-rust suite against the live API while porting the same change to the Rust client — its get_scheduled test uses a legacy id. The Rust client had the identical flaw; it's fixed there in lettr-rust#21, which is still open.

Notes

304 tests pass, PHPStan and Pint clean.

CHANGELOG.md and VERSION are in this PR rather than a separate chore: one, since it's a one-line patch — happy to split if you'd rather keep the convention.

🤖 Generated with Claude Code

voj-tech-j and others added 2 commits September 20, 2026 15:47
An id handed out before Lettr held scheduled emails itself is answered from
delivery events, in a shape with no `sch_` id. ScheduledEmail::from() read
request_id unconditionally, so RequestId rejected the empty string and
getScheduled() threw for that whole path - 2.8.0 fixed sch_ ids and broke this
one in the same change.

requestId now falls back to the transmission id, which is the id the caller
addressed the email with, so it is always the id that identifies what you
asked about.

Found by running the example-rust suite against the same endpoint in the Rust
client, which hit the identical flaw.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The legacy read path reports the provider's vocabulary, not Lettr's: a
delivered email comes back state "delivered", which is not one of the five
ScheduledEmailState cases, so ::from() threw ValueError. Verified against the
live API, which answers "delivered" for an id that has delivery events.

The existing legacy test used state "sent" — valid in both vocabularies, which
is exactly why it missed this. It now uses "delivered", and an unrecognised
value resolves to Unknown so a state added later cannot break reads again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant