Accept events before processing
Make commands idempotent
Use a stable key for every Message send. Derive reply keys from the triggeringevent_id.
Keep replies on REST
At least one saved webhook subscription selects Webhook delivery. Having no saved subscriptions selects WebSocket delivery. Relay sends each event through one path and keeps the sameevent_id when the path changes.
Replies use POST /v1/chats/{chatId}/messages. WebSocket ACK frames never
contain replies.
Treat IDs as opaque
Do not infer resource type, authorization, or ordering from UUID bits. Keep cursors unchanged and use them only with the operation that returned them.Upload media before sending
Allocate the Attachment, upload the exact declared bytes, confirm it is ready, then send the Message. Keep upload URLs and download URLs out of logs.Respect membership visibility
A Contact can read only the periods when it belonged to a Chat. Do not cache or expose group history that Relay did not return to that Contact.Handle duplicates
Webhook retries and WebSocket replay are at least once. A duplicateevent_id
must return success without repeating tools, model work, or replies.

