Repository navigation
[CHA-5933] feat: support scheduled messages - #278
Conversation
Message.send gains scheduledAt, and its response carries the stored scheduled message. A new ScheduledMessage model lists (query), edits (update), cancels (delete) and sends early (sendNow) the scheduled messages of any user. Event reads scheduled_message and message_id for the scheduled_message.* events. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v1 API returns a scheduled message's custom fields as top-level keys of its message object, as for regular messages, not under "custom". Drop the custom field so they land in getAdditionalFields(), as on Message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
||
| /** On scheduled_message.sent, the ID of the message that was sent. */ | ||
| @Nullable | ||
| @JsonProperty("message_id") |
There was a problem hiding this comment.
[Should Fix] This typed field also takes message_id from events other than scheduled_message.sent. The backend puts a top-level message_id on every message webhook (message.new, message.updated, ...) and on reminder.* and poll.* events. Today Jackson puts that key in getAdditionalFields(); after this change it goes to getMessageId() and is no longer in the map.
Why it matters: a backend that parses webhooks into Event and reads getAdditionalFields().get("message_id") gets null after upgrading, with no compile error.
Fix: keep the typed field, but make the Javadoc say it is the ID of the message the event is about (on scheduled_message.sent, the sent message), and mention the move from getAdditionalFields() to getMessageId() in the commit message so the changelog carries it.
There was a problem hiding this comment.
Thanks — #278 merged before this was addressed, so the fix is in a follow-up: #279 (commit 8b59fc5).
Rather than only documenting the move, it keeps full backward compatibility: messageId is no longer a Jackson-mapped field; getMessageId()/setMessageId() read and write additionalFields["message_id"]. So getAdditionalFields().get("message_id") keeps working on message.*/reminder.*/poll.* events, JSON round-trips exactly as in 1.50.0, and getMessageId() works on every event including scheduled_message.sent. Tests cover a message.new event with message_id and a scheduled_message.sent event.
Event.messageId from #278 was a typed Jackson field, so a top-level message_id on message.*, reminder.* and poll.* webhooks moved out of getAdditionalFields() and getAdditionalFields().get("message_id") returned null after upgrading. getMessageId()/setMessageId() now read and write the additional-fields map instead, so parsing and serialization match 1.50.0 and both accessors return the ID on every event. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
TLDR
Backends can now schedule a chat message to be sent later, on behalf of any user, and then list, edit, cancel or send early those scheduled messages.
Message.send(...).scheduledAt(date)stores the message instead of sending it; the newScheduledMessageclass covers the rest. Risk is low: the change is additive (one new optional request field, two new optional response fields, one new model and service). The backend endpoints are behind a per-app flag and not yet released, so merge after backend CHA-5924 ships.Ticket
CHA-5933
CLA
Description of the pull request
Backend endpoints are behind a per-app flag and not yet released; merge after backend CHA-5924 ships.
API surface
Message.send(type, id).scheduledAt(date)POST /channels/{type}/{id}/messagewithscheduled_at; response gainsscheduled_messageScheduledMessage.query().userId(..).filter(..).sort(..).limit(..).next(..)POST /scheduled_messages/query(user_idis required server-side; filtercidandstatus; onlyscheduled_atascending; limit 1-100, default 20; cursornext)ScheduledMessage.update(id).message(..).scheduledAt(..)PATCH /scheduled_messages/{id}(either or both)ScheduledMessage.delete(id)DELETE /scheduled_messages/{id}(cancel, or remove a failed one)ScheduledMessage.sendNow(id)POST /scheduled_messages/{id}/send_nowScheduledMessagemirrors the backendScheduledMessageResponse:id,cid,user,message(content without an ID;mentioned_usersholds user IDs),scheduled_at,status,failure_reason,created_at,updated_at, andchannel(query results only). Status values are exposed asSTATUS_*constants.EventgainsscheduledMessageandmessageId(the sent message's ID onscheduled_message.sent).docs/messages/scheduled_messages.md.Not added, on purpose:
include_scheduled_messageson GetOrCreateChannel: the backend marks itignore_if_server_side, so it does nothing for a server SDK.chat_scheduled_messages_enabled, max lead time): not exposed through the app settings API.CHANGELOG.md: generated at release time.How to verify
./gradlew test --tests '*ScheduledMessageTest*'passes (8 serialization tests, no API credentials needed)../gradlew spotlessCheck javadocpasses.ScheduledMessageIntegrationTest(schedule, query, update, delete, send now against a real app) runs only withSTREAM_SCHEDULED_MESSAGES_ENABLED=true, because the feature is behind a per-app flag that is off on the CI app. It has not been run yet: no app with the feature is available until the backend ships.🤖 Generated with Claude Code