DM references through Ting
A DM service can hand every update to Ting, which pushes it to its recipients' local endpoints.
DM does this only when its operator configures Ting (DM_TING_URL); otherwise updates are read
from DM's sync feed and nothing arrives through Ting. GET /api/v1/delivery (or dm whoami)
says which: transport is sync or ting.
DM's own WebSocket endpoints are retired (/api/v1/ws answers 410 delivery_moved).
What DM hands to Ting
Each item is a reference, not the message:
{"type": "dm.syncChanged", "for": "4143123f-b494-481c-adbf-c14b14cfccc0", "key": "<delivery uuid>",
"data": {"schema_version": 2, "event": "message.created", "conversation_id": "c:ada::si:scout",
"message_id": "00a", "delivery_id": "<delivery uuid>", "delivery_sequence": 41},
"metadata": {"isi": "deliberate"}}
for is the recipient's permanent uuid. conversation_id is the conversation's address when the update was made
(c:ada::si:scout, g:launch-week, or the UUID of a conversation of several); every DM route takes it. event is one
of message.created, message.updated, message.deleted, message.delivered, message.read, message.failed.
metadata.isi is present only for a Silicon recipient addressed through an ISI route. Conversation events (a group
renamed, an invitation, a member removed) are not handed to Ting; read them from the sync feed with
include=conversations (receive messages).
Handling a batch
A generic consumer receives Ting batches for every app, not only DM. For DM items:
- Authenticate the hook (its secret and id) and load the account the hook belongs to: a
TingReceiverContext { app_id: "dm", account_uuid, account_id, kind }stored when the hook was attached. Local webhook items may omitfor, so the raw JSON never decides the recipient. ting::validate_batch(raw, &receiver, &me)checks each DM item without I/O: the schema, the key, the metadata and, when present,for. Items of other apps come backUnrelated; references from before DM moved to Silicon Accounts (schema 1) are rejected asLegacyReference.Client::hydrate_ting_batch(raw, &receiver)does both and fetches each referenced message under the account's current permissions:Message,Inaccessible(403/404), orFailed(retry later).
Neither marks anything delivered or read, acknowledges a Ting, or stores a queue: the consumer
deduplicates (on delivery_id), accepts the whole batch, and sends DM receipts when the message
reached the device or was read. A Ting sequence number is not a sync cursor: recover history with
the sync feed.
Live receiver sessions (Ting 0.4)
A Ting sign-in credential and a receiver session have different lifetimes. The
credential survives a closed window; the receiver exists only while its socket
is live. ready.session_id identifies that connection. Reconnecting creates a
new ID, so never save it as an account address, sync cursor or stable destination.
DM sends ordinary message updates with the recipient's immutable UUID in for
and without session_id. All of the recipient's eligible devices receive
those updates. Targeting one tab would lose that route when the tab closes and
would exclude the recipient's other devices. DM does not send private messages
to anonymous sessions. Future activity tied to one window can use a targeted
session only with a separately verified account binding and an explicit policy
for what happens when that window closes.
The website opens one cookie-authenticated Ting receiver per open DM tab when
TING_BROWSER_ORIGIN is configured and the DM service enables Ting. It checks
/v1/me against the DM account UUID and kind, then confirms registered.for on
that socket before watching. A mismatched account, anonymous registration, or
old greeting without session_id cannot report a connected receiver. The
credential remains in Ting's HttpOnly cookie; DM credentials never go to Ting.
Settings → Notifications reports the connection and links to Ting sign-in when
needed. Returning to DM rechecks the account.
Direct tings, silent_digest and inbox hints wake DM's HTTP sync reader. The
reader keeps its own opaque cursor and fetches under current DM permissions.
It also keeps its long poll: conversation events currently have no Ting handoff,
and silent, lost or unavailable notifications must not strand DM history. A
burst coalesces into one wakeup; message hydration finishes before the cursor
moves. No direct event sends a hook ACK, marks a Ting read, or marks a DM message
read. Browser notification preferences are still local to DM.
Native integrations keep using Ting's shared daemon and stable webhook IDs;
DM starts no extra native receiver or incoming queue. ting sessions list --json
shows the account's active receivers; its session IDs are diagnostic, not hook
IDs. For an explicitly managed direct Ting receiver, validate frames with
ting::validate_session_batch(raw, current_session_id, &receiver, &me) before
hydrating. It accepts tings and silent_digest, rejects another session and
hook events, and requires the recipient UUID on every DM item. Revalidate on
reconnect and recover through DM sync; direct sessions do not replay like hooks.
Upgrading the producer
Ting 0.4 requires two-part names. Register dm.syncChanged with the schema in
deploy/ting-sync-changed.schema.json before deploying this producer. The
reference data remains schema 2. /api/v1/contracts and /api/v1/delivery
announce ting_notification_type and the receiver lifetime/recovery policy in
ting_receiver_sessions. These describe the integration, not a live connection. Readers accept both this name and the older
dm.sync.changed so retained history remains usable.
Already prepared handoffs retain their exact body and key across retries. Do not rewrite their type in place or invent a new key after an uncertain result. Ting 0.4 refuses new sends of legacy names: inspect those handoffs and Ting's sent history before any deliberate replay migration. Check type-specific recipient preferences during rollout too: registering a new name must not be assumed to migrate rules attached to the old name. Recipient controls remain owned by Ting. This change does not silently discard or duplicate them; DM history and sync remain available.
A confirmed Ting blocked result is recorded as ting_recipient_blocked and is
not retried, enrolled again, or counted as Delivered. The message remains in DM.
Silent sends remain accepted and follow Ting's daily schedule. Neither outcome
changes DM's explicit Read receipts.