Guides

Messages

Read and organize messages from code: check what's unread, search everything, archive what's done and snooze what can wait.

These requests take an API token, not a send key. A read-only token can list, read, search and watch. Changing anything takes full access.

List your messages

Ask for what's unread:

Terminal
curl "https://api.ripplepush.com/v1/messages?unread=true" \
  -H "Authorization: Bearer $RIPPLE_KEY"

Ripple returns a page of messages, newest activity first, with a total and a nextCursor for the next page:

200 OK
{
  "items": [
    {
      "messageId": "msg_0575320416fe4544b646044392618fbd",
      "source": { "kind": "system", "id": "src_9fddbdcb785848c6b10b068facd32d87", "label": "Inbox" },
      "title": "Deployment complete",
      "preview": "Version 1.2 is ready.",
      "alert": "standard",
      "readAt": null,
      "archivedAt": null,
      "snooze": null
    }
  ],
  "total": 1,
  "nextCursor": null
}

A message has more fields than this; the API reference lists them all. Three filters narrow the list:

FilterShows
inall, the default: everything not archived or snoozed. archive: archived messages. snoozed: snoozed messages, soonest to return first.
sourceIdOne source's messages. Inbox is a source too; its ID is in the list of sources, with kind system.
unreadtrue for unread messages, false for read ones

A message's source says where it belongs. Its sound is the one its sender chose, or null when it plays its source's or the account's.

How threads appear

A thread appears once in a list, as its latest message. Its thread field describes the whole conversation, and previousMessage shows the update before the latest, if there is one. A thread is read or unread as a whole.

Read a message or a thread

To open one message in full, with its body and history:

Terminal
curl https://api.ripplepush.com/v1/messages/msg_0575320416fe4544b646044392618fbd \
  -H "Authorization: Bearer $RIPPLE_KEY"

For a message in a thread, the response also has the thread's newest messages in timeline. To read further back, pass its timelineCursor as cursor to GET /v1/threads/{threadId}. To read the thread up to the message you asked for, pass requestedMessage.cursor instead.

Search looks through titles, bodies, thread names and source names. Send the query as JSON:

Terminal
curl https://api.ripplepush.com/v1/messages/search \
  -H "Authorization: Bearer $RIPPLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "release notes"}'

Unlike a list, search covers everything but Trash, archived and snoozed messages included. The best matches come first. These fields narrow the results:

FieldNarrows to
inall, archive or snoozed
sourceIdsAny of these sources, up to 100
after, beforeA date range
alertquiet, standard or important
unreadtrue for unread, false for read

Send "sort": "newest" to order by date. Filters also work without a query, which makes search a flexible list, newest first:

JSON
{ "in": "archive", "sourceIds": ["src_abc123", "src_def456"], "after": "2026-10-01T00:00:00Z" }

A message you just sent can take a moment to show up in search.

Get the next page

Lists, searches and threads page the same way. While a response's nextCursor isn't null, send it back as cursor, with the same filters, for the next page.

  • A list or a thread takes it in the address: ?cursor=…
  • A search takes it in the body: "cursor": "…"

limit sets the page size, up to 100. Pass cursors back exactly as you got them; one from a different list or search gets 400 invalid_cursor.

Note

A search returns at most about 1,000 results. Past that, the response says "truncated": true and stops giving a nextCursor. Narrow the query or filters to reach the rest.

Watch for new messages

To act on messages as they arrive, without asking over and over, keep a stream open. Ripple sends each new message the moment it accepts it, as Server-Sent Events:

Terminal
curl -N https://api.ripplepush.com/v1/messages/stream \
  -H "Authorization: Bearer $RIPPLE_KEY"
200 OK
id: AQ2zvT3fX0Rk8yq1c8h0b2NtJ4kT3PnRj6UeB-wJ2pYc1nq9Vd0aXg2nN4lD7c0F
event: message
data: {"messageId":"msg_2fe56d9f692c4febb604bb2c2c884ae7","title":"Deploy failed","alert":"important",...}

: keep-alive

Add sourceId for one source's new messages. Each message event's data is one line of JSON: the message as reading it shows it, without sourceState or sourceDeletedAt. A new message in a thread comes with the thread's title in thread.title and the update before it in previousMessage. Only arrivals send events, not reading, archiving, snoozing, moving or Trash, and a message already in Trash when its event would go out is skipped. Ignore events with other names.

A stream starts at the present, and the connection can close at any time, such as after 15 minutes. Every event has an id; one without data only moves your place forward. To pick up where you left off, reconnect with the last id in a Last-Event-ID header, and every new message since then arrives, for up to 30 days. Many SSE clients do this for you.

ErrorWhat to do
410 last_event_id_expiredThe Last-Event-ID is too old or unusable. Reconnect without it, and list messages to catch up.
429 message_stream_limitToo many streams are open. Wait the Retry-After seconds and try again.
Tip

ripple messages watch does all of this for you.

Organize messages

To mark messages read or unread, archive or unarchive them, or move them to Trash, post their IDs to the matching address, up to 100 at a time:

Terminal
curl https://api.ripplepush.com/v1/messages/archive \
  -H "Authorization: Bearer $RIPPLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"messageIds": ["msg_abc123", "msg_def456"]}'
AddressDoes
/v1/messages/readMarks read
/v1/messages/unreadMarks unread
/v1/messages/archiveArchives
/v1/messages/unarchiveBrings back from the archive
/v1/messages/trashMoves to Trash, where messages stay 30 days before they're deleted for good

Each response's changedCount counts only what that request changed. A message in a thread acts on the whole thread, and every message keeps its source. Trashed messages can be restored until they're deleted.

Move a message to another source

A message outside a thread can move to another source. Post this to /v1/messages/move:

JSON
{ "moves": [{ "messageId": "msg_abc123", "sourceId": "src_abc123" }] }

Threads stay in the source they started in.

Snooze a message

Snoozing hides a message until a time you choose, then brings it back with an alert. Send the return time, with its offset from UTC, to the message's snooze address:

Terminal
curl -X PUT https://api.ripplepush.com/v1/messages/msg_abc123/snooze \
  -H "Authorization: Bearer $RIPPLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"returnAt": "2026-10-24T09:00:00-04:00"}'

You can also send timeZone, the zone to show the time in when the snooze is edited later, and alert, how the message returns. Ripple fills in sensible values for either one you leave out.

To bring a message back early, send a DELETE to the same address, with no body.

Tip

To act only on the message as you last saw it, send its revision as expectedRevision: in a snooze's body, or as ?expectedRevision= when you cancel one. If the message has changed, Ripple answers 409 message_changed with the message as it is now in current, and changes nothing.

Restore from Trash

Trash keeps deleted messages, threads and sources for 30 days. Each item's purgeAt says when it will be deleted for good.

ToSend
See what's thereGET /v1/trash, with type=message, thread, source or content (messages and threads) to narrow it
Search itPOST /v1/trash/search, with the same fields as message search except in
Open an itemGET /v1/trash/messages/{messageId} or GET /v1/trash/threads/{threadId}
Restore an itemPOST /v1/trash/{kind}/{id}/restore
Delete it for goodDELETE /v1/trash/{kind}/{id}, with no body

For restoring and deleting, kind is messages, threads or sources.

Warning

Deleting from Trash can't be undone. To make sure the item is still the one you looked at, send its deletedAt as ?deletedAt=; a restore takes it in its JSON body. If it no longer matches, Ripple answers 404 not_found and does nothing.

When a request fails

Errors have the same shape everywhere: a code to match on and a message for people, as in Send messages. A few add a field, such as current on 409 message_changed.

If a request times out, or fails on Ripple's side, the change may still have happened. Read the current state before trying again. A busy search answers 503 with search_busy or search_timeout; wait the Retry-After seconds and try again.

The API reference has every operation's fields, limits and errors.