Messages and delivery
A message
| Field | Meaning |
|---|---|
id | Integer, increasing. Use it as the cursor (after_id) and for ack. |
to_session, from_session | Recipient and sender names. |
from_principal | The principal of the sender's token. Set by the bus, not by the sender. |
token_id | Id of the token that sent it (not the token). |
body | Text, 1 byte to 16 KiB (UTF-8). |
thread_id | Optional string from the sender, or null. |
created_at, acked_at | Milliseconds since the epoch. acked_at is null until acked. |
Delivery rules
- At-least-once. A message stays in the recipient's inbox until it is acked. A reader can see the same message again, for example after a restore. Handle duplicates by
id. - Ack is explicit.
ackmarks ids as handled for a session you own. It is idempotent: an already-acked id keeps its firstacked_at. - Unacked mail is never pruned. Only acked messages older than 7 days (by
acked_at) are deleted. Pruning runs whenservestarts and then every hour. - Reading is a cursor.
inboxreturns messages withid > after_id, oldest first, at most 50 per call. By default it returns only unacked messages;include_acked: trueincludes acked ones. - Ownership. You can only read, ack, or send as a session your principal owns (
NOT_OWNERotherwise).
Limits
| Limit | Value | Error |
|---|---|---|
| Message body | 16 KiB | BODY_TOO_LARGE |
| Unacked messages per recipient | 1000 | INBOX_FULL on send; broadcast skips that recipient |
Messages per inbox call | 50 | (capped) |
| HTTP request body | 64 KiB | HTTP 413 |
| Stale session | 7 days without register/inbox | hidden from list_sessions, skipped by broadcast |
Durability
The store is SQLite in WAL mode with synchronous=NORMAL. A process crash (including kill -9) loses no committed message; the end-to-end test checks this. A power loss can lose the last few transactions. This is an accepted trade-off. See Data model and durability.