Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions docs/messages/scheduled_messages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
Scheduled messages let a user write a message now and have it sent later. A server-side client can schedule, list, edit, cancel and send early the scheduled messages of any user.

Scheduled messages must be enabled for your app. Until the scheduled message is sent it is visible only to the user who scheduled it (and to your backend); the channel's other members see nothing until the message is sent.

## Scheduling a Message

Pass `scheduledAt` to a regular send. The message is not sent: it is stored and sent at that time. The send time must be at least 1 minute and at most the app's maximum lead time (120 days by default) away.

```java
// Backend SDK
Date in1Hour = new Date(System.currentTimeMillis() + 60 * 60 * 1000);

MessageSendResponse response =
Message.send("messaging", "general")
.message(MessageRequestObject.builder().text("Good morning!").userId("john").build())
.scheduledAt(in1Hour)
.request();

ScheduledMessage scheduled = response.getScheduledMessage();
scheduled.getId(); // ID of the scheduled message
scheduled.getStatus(); // "scheduled"
scheduled.getScheduledAt(); // when it will be sent
```

`response.getMessage()` holds the message as it will be sent; it is not in the channel yet. Slash commands cannot be scheduled, and `scheduledAt` cannot be combined with `pending(true)` (`pending(false)` is accepted).

## Listing Scheduled Messages

Lists one user's scheduled messages across channels: pending ones first, then failed ones, each by `scheduled_at`. `userId` is required. Each item carries a summary of its channel.

```java
ScheduledMessageQueryResponse page =
ScheduledMessage.query()
.userId("john")
.filter(
ScheduledMessagesFilter.builder()
.cid("messaging:general") // optional: one channel only
.status("scheduled") // optional: scheduled, sending, failed
.build())
.limit(100) // 1 to 100, defaults to 20
.request();

List<ScheduledMessage> items = page.getScheduledMessages();

// Next page
if (page.getNext() != null && !page.getNext().isEmpty()) {
ScheduledMessage.query().userId("john").next(page.getNext()).request();
}
```

Only sorting by `scheduled_at` ascending is supported.

## Editing a Scheduled Message

Replace the message, move the send time, or both. A new `message` replaces the stored one as a whole; the sender stays the user who scheduled it.

```java
ScheduledMessage.update(scheduled.getId())
.message(MessageRequestObject.builder().text("Good morning, everyone!").build())
.scheduledAt(new Date(System.currentTimeMillis() + 2 * 60 * 60 * 1000))
.request();
```

## Canceling a Scheduled Message

Cancels a scheduled message so it is never sent, or removes one that failed.

```java
ScheduledMessage canceled = ScheduledMessage.delete(scheduled.getId()).request().getScheduledMessage();
canceled.getStatus(); // "canceled"
```

## Sending a Scheduled Message Now

Moves the send time to now and queues the send.

```java
ScheduledMessage.sendNow(scheduled.getId()).request();
```

Editing, canceling and sending now return a 409 error while the message is being sent.

## Statuses and Failures

| Status | Meaning |
| ----------- | ------------------------------------------------------------------------------- |
| `scheduled` | Waiting for its send time |
| `sending` | Being sent |
| `failed` | The send failed; `getFailureReason()` says why. It can only be deleted |
| `canceled` | Canceled; returned by `delete` |
| `sent` | Sent; reported only by the `scheduled_message.sent` event |

Failure reasons: `banned`, `no_permission`, `channel_frozen`, `channel_deleted`, `user_deleted`, `moderation_blocked`, `hook_rejected`, `expired`, `internal_error`, `invalid_message`, `pending_not_supported`.

## Events and Webhooks

| Event | Webhook | When |
| --------------------------- | ------- | -------------------------------------------------------------------- |
| `scheduled_message.created` | No | A message was scheduled |
| `scheduled_message.updated` | No | A scheduled message was edited or sent now |
| `scheduled_message.deleted` | No | A scheduled message was canceled |
| `scheduled_message.sent` | Yes | A scheduled message was sent; `getMessageId()` is the sent message ID |
| `scheduled_message.failed` | Yes | A scheduled message failed to send |

`Event.getScheduledMessage()` holds the scheduled message on all of them. The sent message itself also triggers the usual `message.new`.

## Permissions

Scheduling needs the `ScheduleMessage` permission (channel capability `schedule-message`) in addition to the permission to send the message.
10 changes: 10 additions & 0 deletions src/main/java/io/getstream/chat/java/models/Event.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ public class Event {
@JsonProperty("created_at")
private Date createdAt;

/** Set on scheduled_message.* events. */
@Nullable
@JsonProperty("scheduled_message")
private ScheduledMessage scheduledMessage;

/** On scheduled_message.sent, the ID of the message that was sent. */
@Nullable
@JsonProperty("message_id")
private String messageId;

@NotNull @JsonIgnore private Map<String, Object> additionalFields = new HashMap<>();

@JsonAnyGetter
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/io/getstream/chat/java/models/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,17 @@ public static class MessageSendRequestData {
@JsonProperty("pending_message_metadata")
private Map<String, Object> pendingMessageMetadata;

/**
* When set, the message is not sent now: it is stored and sent at this time, and the response
* carries {@link MessageSendResponse#getScheduledMessage()}. Must be at least 1 minute and at
* most the app's maximum lead time (120 days by default) away. Requires the scheduled messages
* feature on the app. Cannot be combined with {@code pending(true)}; {@code pending(false)} is
* accepted. Slash commands cannot be scheduled.
*/
@Nullable
@JsonProperty("scheduled_at")
private Date scheduledAt;

public static class MessageSendRequest extends StreamRequest<MessageSendResponse> {
@NotNull private String channelId;

Expand Down Expand Up @@ -1405,9 +1416,18 @@ protected Call<StreamResponseObject> generateCall(Client client) {
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public static class MessageSendResponse extends StreamResponseObject {
/**
* The sent message. When the request had {@code scheduledAt}, the message as it will be sent;
* it is not in the channel yet.
*/
@NotNull
@JsonProperty("message")
private Message message;

/** The scheduled message. Set only when the request had {@code scheduledAt}. */
@Nullable
@JsonProperty("scheduled_message")
private ScheduledMessage scheduledMessage;
}

@Data
Expand Down
Loading
Loading